I am trying to build a dynamic TextField list in flutter but did not understand at how I can handle it.
My list looks like this:
My code structure is:
ListView.builder(
shrinkWrap: true,
itemCount: itemList.length,
itemBuilder: (context, index) {
if(itemList.length == null) {
return _buildProgressIndicator();
} else {
return singleItemList(index);
}
})
Widget singleItemList(int index) {
if((itemList.length -1) < index)
{
return Container(child: null);
} else {
dynamic singleItem = itemList[index];
String counter = (index+1).toString();
return
Container(
decoration: BoxDecoration(
color: Colors.white,
),
child: Row(
children:[
Expanded(
flex: 1,
child: Text(counter,style: orderBookItemListCounter(),)
),
Expanded(
flex: 3,
child: TextField(
controller: _addProductToCart(counter),
decoration: InputDecoration(
labelText: "Qty",
),
)
),
])
);
}
}
My product list is not fixed may change the number of products,
I would like to capture Qty as per product ID.
Thanks
:)
You have several options depending how you architecture your application or where you have your central state.
I propose you here a solution that updates a local map variable. Alternatively, you could send an event/stream to the place your store is located.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Item> itemList = [
Item("ID1", "First product"),
Item("ID2", "Second product"),
];
Map<String, int> quantities = {};
void takeNumber(String text, String itemId) {
try {
int number = int.parse(text);
quantities[itemId] = number;
print(quantities);
} on FormatException {}
}
Widget singleItemList(int index) {
Item item = itemList[index];
return Container(
decoration: BoxDecoration(
color: Colors.white,
),
child: Row(
children: [
Expanded(flex: 1, child: Text("${index + 1}")),
Expanded(
flex: 3,
child: TextField(
keyboardType: TextInputType.number,
onChanged: (text) {
takeNumber(text, item.id);
},
decoration: InputDecoration(
labelText: "Qty",
),
),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Demo")),
body: Center(
child: ListView.builder(
shrinkWrap: true,
itemCount: itemList.length,
itemBuilder: (context, index) {
if (itemList.isEmpty) {
return CircularProgressIndicator();
} else {
return singleItemList(index);
}
}),
),
);
}
}
class Item {
final String id;
final String name;
Item(this.id, this.name);
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<TextEditingController> _controllers = new List();
List<Item> itemList = [
Item("ID1", "product 1"),
Item("ID2", "product 2"),
Item("ID3", "product 3"),
Item("ID4", "product 4"),
Item("ID5", "product 5"),
Item("ID6", "product 6"),
Item("ID7", "product 7"),
Item("ID8", "product 8"),
Item("ID9", "product 9"),
Item("ID10", "product 10"),
Item("ID11", "product 11"),
Item("ID12", "product 12"),
Item("ID13", "product 13"),
Item("ID14", "product 14"),
Item("ID15", "product 15"),
Item("ID16", "product 16"),
Item("ID17", "product 17"),
Item("ID18", "product 18"),
];
Map<String, int> quantities = {};
void takeNumber(String text, String itemId) {
try {
int number = int.parse(text);
quantities[itemId] = number;
print(quantities);
} on FormatException {}
}
Widget singleItemList(int index, TextEditingController controllertxt) {
Item item = itemList[index];
return Container(
decoration: BoxDecoration(
color: Colors.white,
),
child: Row(
children: [
Expanded(flex: 1, child: Text("${index + 1}")),
Expanded(
flex: 3,
child: new TextField(
controller: controllertxt,
keyboardType: TextInputType.number,
onChanged: (text) {
takeNumber(text, item.id);
},
decoration: InputDecoration(
labelText: "Qty",
),
),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Demo")),
body: Center(
child: ListView.builder(
shrinkWrap: true,
itemCount: itemList.length,
itemBuilder: (context, index) {
_controllers.add(new TextEditingController());
if (itemList.isEmpty) {
return CircularProgressIndicator();
} else {
return singleItemList(index,_controllers[index]);
}
}),
),
);
}
}
class Item {
final String id;
final String name;
Item(this.id, this.name);
}
Related
I am trying to implement Flutter's Tab Bar with 3 tabs and an AnimatedList inside those tabs. I want to use the same list and filter the list according to each tab (past tasks, today's tasks, and future tasks), however during my implementation of the tab bar together with the animatedlist I am getting an error regarding a duplicate global key in the widget tree. https://pastebin.com/iAW6DH9m . What would be the best way to deal with this error? Thank you for any help.
edit: I tried using this method to fix this. Multiple widgets used the same GlobalKey while it did fix my error I was then unable to access "currentstate" method on the key to be able to add more items to the list. I then tried a similar method using using GlobalKey and it resulted in a similar error of duplicate global keys.
This is my tab bar implementation
import 'package:flutter/material.dart';
import 'search_widget.dart';
import 'animatedlist_widget.dart';
class Dashboard extends StatefulWidget {
#override
_DashboardState createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
centerTitle: true,
actions: <Widget>[
new IconButton(icon: new Icon(Icons.grid_on), onPressed: null)
],
title: new Text('Dashboard'),
elevation: 0,
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
_onFabPress(context);
},
child: new Icon(Icons.add)),
body: Scaffold(
appBar: new SearchWidget(
onPressed: () => print('implement search'),
icon: Icons.search,
title: 'Search',
preferredSize: Size.fromHeight(50.0),
),
body: DefaultTabController(
length: 3,
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Container(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: new TabBar(
unselectedLabelColor: Colors.black45,
labelColor: Colors.white,
indicator: CustomTabIndicator(),
tabs: <Widget>[
new Tab(text: "Past"),
new Tab(text: "Today"),
new Tab(text: "Future")
]),
),
),
),
body: new TabBarView(
children: <Widget>[
AnimatedTaskList(),
AnimatedTaskList(),
AnimatedTaskList()
],
)
),
),
),
);
}
void _onFabPress(context) {
AnimatedTaskList().addUser();
}
/*showModalBottomSheet(
context: context,
builder: (BuildContext bc) {
return Container(
child: new Wrap(children: <Widget>[
new TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter Task Title')),
new TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter Task Details',
)),
]));
});
}*/
}
class CustomTabIndicator extends Decoration {
#override
BoxPainter createBoxPainter([onChanged]) {
// TODO: implement createBoxPainter
return new _CustomPainter(this, onChanged);
}
}
class _CustomPainter extends BoxPainter {
final CustomTabIndicator decoration;
_CustomPainter(this.decoration, VoidCallback onChanged)
: assert(decoration != null),
super(onChanged);
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
// TODO: implement paint
assert(configuration != null);
assert(configuration.size != null);
final indicatorHeight = 30.0;
final Rect rect = Offset(
offset.dx, (configuration.size.height / 2) - indicatorHeight / 2) &
Size(configuration.size.width, indicatorHeight);
final Paint paint = Paint();
paint.color = Colors.blueAccent;
paint.style = PaintingStyle.fill;
canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(30)), paint);
}
}
This is my animatedlist class:
import 'package:flutter/material.dart';
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
class AnimatedTaskList extends StatefulWidget {
void addUser() {
int index = listData.length;
listData.add(
TaskModel(
taskTitle: "Grocery Shopping",
taskDetails: "Costco",
),
);
_listKey.currentState
.insertItem(index, duration: Duration(milliseconds: 500));
}
#override
_AnimatedTaskListState createState() => _AnimatedTaskListState();
}
class _AnimatedTaskListState extends State<AnimatedTaskList> {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: SafeArea(
child: AnimatedList(
key: _listKey,
initialItemCount: listData.length,
itemBuilder:
(BuildContext context, int index, Animation animation) {
return Card(
child: FadeTransition(
opacity: animation,
child: ListTile(
title: Text(listData[index].taskTitle),
subtitle: Text(listData[index].taskDetails),
onLongPress: () {
//todo delete user
},
)));
})),
);
}
}
class TaskModel {
TaskModel({this.taskTitle, this.taskDetails});
String taskTitle;
String taskDetails;
}
List<TaskModel> listData = [
TaskModel(
taskTitle: "Linear Algebra",
taskDetails: "Chapter 4",
),
TaskModel(
taskTitle: "Physics",
taskDetails: "Chapter 9",
),
TaskModel(
taskTitle: "Software Construction",
taskDetails: "Architecture",
),
];
I fixed my issue by moving
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
into my _AnimatedTaskListState class, and adding a constructor and private key to my AnimatedTaskList class
final GlobalKey<AnimatedListState> _key;
AnimatedTaskList(this._key);
#override
_AnimatedTaskListState createState() => _AnimatedTaskListState(_key);
then in my tab bar implementation I changed it to reflect my new constructor
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 1"));
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 2"));
AnimatedTaskList(GlobalKey<AnimatedListState>(debugLabel: "key 3"));
I want to get value key which contains in Widget build(), but it says 'Undefined'. Also, I need to get this value to another class. How can I do it?
I`ve tried to just take this value, but it says undefined error
String newValue = s; // It says Undefined
I also tried to get this value to another class but this method gives more errors :c
myCard c = myCard();
String classValue = c.s; // It says 'Only static members can be accessed in initializers' and 'The getter 's' isn`t defined for the class 'myCard' '
Here`s part of main.dart file
class MyCard extends StatefulWidget {
#override
myCard createState() => myCard();
}
class myCard extends State<MyCard> {
int myCount = count - 1;
void click() {
setState(() {
print(titlecard);
Navigator.push(context, MaterialPageRoute(
builder: (context) => setNewText())
);
});
}
#override
Widget build(BuildContext context) {
Key s = Key(myCount.toString()); // I want to get this value
return Center(
key: s,
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(titlecard[int.parse(s)]),
subtitle: Text(textcard),
),
ButtonTheme.bar( // make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('Change Text'),
onPressed: click,
),
FlatButton(
child: const Text('LISTEN'),
onPressed: () {
print(s);
},
),
],
),
),
],
),
),
);
}
}
class setNewText extends StatefulWidget {
#override
SetNewText createState() => SetNewText();
}
class SetNewText extends State<setNewText> {
myCard c = myCard();
HomePageState s = HomePageState();
String v = c.s; // To here
final titleController = TextEditingController();
final textController = TextEditingController();
final formkey = GlobalKey<FormState>();
List<String> titles = [''];
void _submit() {
setState(() {
if (formkey.currentState.validate()) {
formkey.currentState.save();
Navigator.pop(context);
titlecard.removeAt(count-s.myCount);
titlecard.insert(count-s.myCount, titleController.text);
textcard = textController.text;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Title'),
),
body: Column(
children: <Widget>[
Card(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Form(
key: formkey,
child: Column(
children: <Widget>[
TextFormField(
controller: titleController,
decoration: InputDecoration(
labelText: 'Title'
),
validator: (value) => value.length < 1 ? 'Invalid Title' : null,
onSaved: (value) => value = titleController.text,
),
TextFormField(
controller: textController,
decoration: InputDecoration(
labelText: 'Text'
),
validator: (text) => text.length < 1 ? 'Invalid Text' : null,
onSaved: (text) => text = textController.text,
)
],
),
),
),
),
FlatButton(
textColor: Colors.deepPurple,
child: Text('SUBMIT'),
onPressed: _submit,
),
],
)
);
}
}
Since you're just listening for values, one way of doing this is by listening to the value using Streams. You can initialize the class where the value can be Streamed and access it anywhere. Do note that once the Stream has been closed, you can only create a new Stream.
Here's a sample.
import 'dart:async';
import 'package:flutter/material.dart';
class SampleStream extends StatefulWidget {
const SampleStream({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<SampleStream> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<SampleStream> {
Counter counter = Counter();
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: counter.showCount,
builder: (context, AsyncSnapshot<int> snapshot) {
int count = snapshot.hasData ? snapshot.data! : 0;
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Text(
'You clicked the button $count times'),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.plus_one),
onPressed: () {
counter.setCount(++count);
},
),
);
});
}
#override
void dispose() {
super.dispose();
counter.disposeCount();
}
}
class Counter {
final _count = StreamController<int>.broadcast();
Stream<int> get showCount => _count.stream;
setCount(int count) {
debugPrint('Stream sink: $count');
_count.sink.add(count);
}
disposeCount() {
_count.close();
}
}
In this demo, Counter was initialized in _MyHomePageState and can only be accessed in the same class. Calling counter.setCount(int) updates the stream and the stream value can be listened to using StreamBuilder to fetch the snapshot.
So basically there is this product edit page which behaves differently uppon called.
If the product is being created for first time, then it is shown in a tab view controller.
if the product is being updated, its body is returned in scaffold.
here are some screenshots
when I submit through create product, i encounter no error.
But when I submit through update product, though the logic works, i get a short red screen with
like this
error Another exception was thrown: No Material widget found.
Here is the code for the screen
`
import 'package:flutter/material.dart';
import 'package:academy_app/models/products.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:academy_app/scoped-model/Products.dart';
class ProductEdit extends StatefulWidget {
ProductEdit();
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return ProductEditState();
}
}
class ProductEditState extends State<ProductEdit> {
String title;
String description;
double price;
Product product;
final focusnode2 = FocusNode();
final focusnode3 = FocusNode();
Map<String, dynamic> formData = {
"name": null,
"desc": null,
"price": null,
"image": "asset/foood.jpg"
};
GlobalKey<FormState> formkey = GlobalKey<FormState>();
Widget buildTitle(productItem) {
return TextFormField(
initialValue: productItem != null ? productItem.title : "",
validator: (String value) {
if (value.isEmpty || value.length < 3) {
return 'title cannot be empty';
}
},
textInputAction: TextInputAction.next,
decoration: InputDecoration(labelText: "Title"),
onFieldSubmitted: (String value) {
FocusScope.of(context).requestFocus(focusnode2);
},
onSaved: (String valuee) {
setState(() {
formData["name"] = valuee;
});
},
);
}
Widget buildDesc(productItem) {
return TextFormField(
initialValue: productItem != null ? productItem.description : "",
validator: (String value) {
if (value.isEmpty || value.length < 3) {
return 'Cant have that short description';
}
},
textInputAction: TextInputAction.next,
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(focusnode3);
},
focusNode: focusnode2,
maxLines: 3,
decoration: InputDecoration(labelText: "Description"),
onSaved: (String valuee) {
setState(() {
formData["desc"] = valuee;
});
},
);
}
Widget buildPrice(productItem) {
return TextFormField(
initialValue: productItem != null ? productItem.price.toString() : "",
textInputAction: TextInputAction.done,
focusNode: focusnode3,
decoration: InputDecoration(labelText: " How much"),
keyboardType: TextInputType.number,
onFieldSubmitted: (value) {
focusnode3.unfocus();
},
validator: (value) {
if (!RegExp(r'^(?:[1-9]\d*|0)?(?:\.\d+)?$').hasMatch(value)) {
return ' Enter numbers only';
}
},
onSaved: (String valuee) {
setState(() {
formData["price"] = double.parse(valuee);
});
},
);
}
void submitForm(Function addProduct, Function updateProduct, int index) {
if (!formkey.currentState.validate()) {
return;
}
formkey.currentState.save();
setState(() {
if (index == null) {
addProduct(Product(
price: formData["price"],
title: formData["name"],
description: formData["desc"],
image: "asset/foood.jpg"));
} else {
updateProduct(
Product(
price: formData["price"],
title: formData["name"],
description: formData["desc"],
image: "asset/foood.jpg"),
);
}
Navigator.pushReplacementNamed(context, '/');
});
}
Widget buildSubmitButton() {
return ScopedModelDescendant<ProductsModel>(
builder: (BuildContext context, Widget, ProductsModel) {
return RaisedButton(
child: Text("Save"),
onPressed: () => submitForm(ProductsModel.addProduct,
ProductsModel.updateProduct, ProductsModel.selected_index));
},
);
}
Widget _buildPageContent(BuildContext context, Product product) {
final double deviceWidth = MediaQuery.of(context).size.width;
final double targetWidth = deviceWidth > 550.0 ? 500.0 : deviceWidth * 0.95;
final double targetPadding = deviceWidth - targetWidth;
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
margin: EdgeInsets.all(10.0),
child: Form(
key: formkey,
child: ListView(
padding: EdgeInsets.symmetric(horizontal: targetPadding / 2),
children: <Widget>[
buildTitle(product),
buildDesc(product),
buildPrice(product),
SizedBox(
height: 10.0,
),
buildSubmitButton()
],
),
),
),
);
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<ProductsModel>(
builder: (context, Widget child, ProductsModel) {
product = ProductsModel.getproduct();
return ProductsModel.selected_index == null
? _buildPageContent(context, product)
: Scaffold(
appBar: AppBar(
title: Text("Update Item"),
),
body:_buildPageContent(context, product) ,
);
},
);
}
}
`
why am i getting that red screen error? i confused about passing the contexts. why arent the textfiled accessing the material parent through in scaffold?
As the error suggests that no material widget found, you need to wrap the Container of the _buildPateContent function into Material Widget. Here is the change that you can do:
Widget _buildPageContent(BuildContext context, Product product) {
final double deviceWidth = MediaQuery.of(context).size.width;
final double targetWidth = deviceWidth > 550.0 ? 500.0 : deviceWidth * 0.95;
final double targetPadding = deviceWidth - targetWidth;
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Material(
child: Container(
margin: EdgeInsets.all(10.0),
child: Form(
key: formkey,
child: ListView(
padding: EdgeInsets.symmetric(horizontal: targetPadding / 2),
children: <Widget>[
buildTitle(product),
buildDesc(product),
buildPrice(product),
SizedBox(
height: 10.0,
),
buildSubmitButton()
],
),
),
),
));
}
try wrapping you app in materialApp or wrap TextField in material Widget.
In my case I forgot to wrap my widget with Scaffold widget. A lot of widgets need to be wrapped with it to work properly. So change this
Widget build(BuildContext context) {
return YourScreenContent();
}
to this
Widget build(BuildContext context) {
return Scaffold(
body: YourScreenContent(),
);
}
I had same issue.
I got the error when I had code like this.
Widget _getLCSBar(int index) {
return Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 0.1),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_getLikeButton(index),
_getCommentButton(index),
_getShareButton(index),
],
));
}
What I have done is wrapped it with Material
Widget _getLCSBar(int index) {
return Material(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 0.1),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_getLikeButton(index),
_getCommentButton(index),
_getShareButton(index),
],
)));
}
Problem Solved!
Just wrap the Gesture Detector inside Material:
return Material(
child: GestureDetector(
...
),
);
I'm using floatingActionButton to increase TextForm Fields. i.e the fields increase by 1 once the button is tapped. The fields are actually increased on tap of the button but so confused on how to get values for each fields generated.
My problems:
When the user selects a value in the dropdown, all the values in the other generated dropdown fields changes to the new one. How do I solve this?
I'd like to add all the number value of the each of the generated Grade field together and also add the value of each of the generated Course Unit field together. i.e Add(sum) the value of all Grade fields the user generated.
See my full code below:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'My Grade Point',
theme: new ThemeData(primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _isLoading = false;
final formKey = new GlobalKey<FormState>();
final scaffoldKey = new GlobalKey<ScaffoldState>();
String _course;
String _grade;
String _unit;
String _mygp;
String _units;
String _totalgrade;
int counter = 1;
void _submit() {
final form = formKey.currentState;
if (form.validate()) {
setState(() => _totalgrade = _grade);
form.save();
}
}
Widget buildfields(int index) {
return new Column(
children: <Widget>[
new TextFormField(
onSaved: (String value) {
setState((){
_course = value;
});
},
validator: (val) {
return val.isEmpty
? "Enter Course Title $index"
: null;
},
decoration: new InputDecoration(labelText: "Course Title"),
),
new Row(
children: <Widget>[
new Expanded(
child: new TextFormField(
onSaved: (value) {
setState((){
_grade = value;
});
},
keyboardType: TextInputType.number,
decoration: new InputDecoration(labelText: "Grade"),
),
),
new Expanded(
child: new DropdownButton<String>(
onChanged: (String value) { setState((){
_unit = value;
});
},
hint: new Text('Course Unit'),
value: _unit,
items: <String>["1", "2", "3", "4", "5"].map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
),
),
],
),
],
);
}
#override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
var loginBtn = new RaisedButton(
onPressed: _submit,
child: new Text("CALCULATE"),
color: Colors.primaries[0],
);
var showForm = new Container(
padding: new EdgeInsets.all(20.0),
child: new Column(
children: <Widget>[
new Expanded(child: new Form(
key: formKey,
child: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return buildfields(index); },
itemCount: counter,
scrollDirection: Axis.vertical,
),
),
),
_isLoading ? new CircularProgressIndicator() : loginBtn
],
),
);
return new Scaffold(
appBar: new AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: new Text(_totalgrade.toString()),
),
body: showForm,
floatingActionButton: new FloatingActionButton(
onPressed: () {
setState(() {
counter++;
});
},
child: new Icon(Icons.add),
),
);
}
}
When the user selects a value in the dropdown, all the values in the other generated dropdown fields changes to the new one. How do I solve this?
The reason why DropdownButton children in ListView updates synchronously is because it fetches all its value from the _unit variable. I suggest using a List<Object> to manage the data of ListView items.
i.e.
class Course {
var title;
var grade;
var unit;
}
...
List<Course> _listCourse = [];
I'd like to add all the number value of the each of the generated Grade field together and also add the value of each of the generated Course Unit field together. i.e Add(sum) the value of all Grade fields the user generated.
With ListView data being managed in List<Course>, data inputted in the fields can be set in onChanged()
// Course Grade
TextFormField(
onChanged: (String value) {
setState(() {
_listCourse[index].grade = value;
});
},
)
and the values can be summed up with the help of a foreach loop.
int sumGrade = 0;
_listCourse.forEach((course) {
// Add up all Course Grade
sumGrade += num.tryParse(course.grade);
});
Here's a complete working sample based from the snippet you've shared.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'My Grade Point',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class Course {
var title;
var grade;
var unit;
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _isLoading = false;
final formKey = new GlobalKey<FormState>();
final scaffoldKey = new GlobalKey<ScaffoldState>();
String _course;
int _grade;
String _unit;
String _mygp;
String _units;
int _totalGrade;
int counter = 1;
List<Course> _listCourse = [];
#override
void initState() {
// Initialize empty List
_listCourse.add(Course());
super.initState();
}
void _submit() {
debugPrint('List Course Length: ${_listCourse.length}');
int sumGrade = 0;
_listCourse.forEach((course) {
debugPrint('Course Title: ${course.title}');
debugPrint('Course Grade: ${course.grade}');
// Add up all Course Grade
sumGrade += num.tryParse(course.grade);
debugPrint('Course Unit: ${course.unit}');
});
final form = formKey.currentState;
if (form.validate()) {
setState(() => _totalGrade = sumGrade);
form.save();
}
}
Widget buildField(int index) {
return new Column(
children: <Widget>[
new TextFormField(
onChanged: (String value) {
setState(() {
// _course = value;
_listCourse[index].title = value;
});
},
validator: (val) {
return val.isEmpty ? "Enter Course Title $index" : null;
},
decoration: new InputDecoration(labelText: "Course Title"),
),
new Row(
children: <Widget>[
new Expanded(
child: new TextFormField(
onChanged: (value) {
setState(() {
// _grade = value;
_listCourse[index].grade = value;
});
},
keyboardType: TextInputType.number,
decoration: new InputDecoration(labelText: "Grade"),
),
),
new Expanded(
child: new DropdownButton<String>(
onChanged: (String value) {
setState(() {
// _unit = value;
_listCourse[index].unit = value;
});
},
hint: new Text('Course Unit'),
value: _listCourse[index].unit,
items: <String>["1", "2", "3", "4", "5"].map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
),
),
],
),
],
);
}
#override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
var loginBtn = new RaisedButton(
onPressed: _submit,
child: new Text("CALCULATE"),
color: Colors.primaries[0],
);
var showForm = new Container(
padding: new EdgeInsets.all(20.0),
child: new Column(
children: <Widget>[
new Expanded(
child: new Form(
key: formKey,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return buildField(index);
},
itemCount: counter,
scrollDirection: Axis.vertical,
),
),
),
_isLoading ? new CircularProgressIndicator() : loginBtn
],
),
);
return new Scaffold(
appBar: new AppBar(
title: new Text(_totalGrade.toString()),
),
body: showForm,
floatingActionButton: new FloatingActionButton(
onPressed: () {
setState(() {
// Add an empty Course object on the List
_listCourse.add(Course());
counter++;
});
},
child: new Icon(Icons.add),
),
);
}
}
Say we created a Chip object and TextField object like below. How do you add a Chip to the inside of the TextField?
new Chip(
label: new Text('Peyton Smith'),
)
new TextField(
)
Is it possible to combine them to get something like in the Material spec where typing in something into a Material TextField adds a Chip?
What you are looking for was actually provided in the comment.
Here is a simple implementation using InputChip:
InputChip(
avatar: CircleAvatar(
backgroundColor: Colors.grey.shade800,
child: Text('AB'),
),
label: Text('Aaron Burr'),
onPressed: () {
print('I am the one thing in life.');
}
)
A material design input chip.
Input chips represent a complex piece of information, such as an
entity (person, place, or thing) or conversational text, in a compact
form.
Input chips can be made selectable by setting
onSelected,
deletable by setting
onDeleted,
and pressable like a button with
onPressed.
They have a
label,
and they can have a leading icon (see
avatar)
and a trailing icon
(deleteIcon).
Colors and padding can be customized.
Here is my simple interpretation of InputChip:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: _toContainer(),
),
),
Divider(
color: Colors.blueGrey,
height: 10.0,
),
Align(
alignment: Alignment.centerLeft,
child: _subjectContainer(),
),
Divider(
color: Colors.blueGrey,
height: 10.0,
),
Align(
alignment: Alignment.centerLeft,
child: _messageContainer(),
),
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Widget _messageContainer() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Text(
'Message',
style: TextStyle(color: Colors.black, fontSize: 18.0),
),
),
);
}
Widget _toContainer() {
return Wrap(
spacing: 5.0,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 8.0, right: 8.0),
child: Container(
child: Text(
'To',
style: TextStyle(color: Colors.black, fontSize: 18.0),
),
),
),
Container(
child: _profileChips("Scott Hill",
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80"),
),
],
);
}
Widget _subjectContainer() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Text(
'Subject',
style: TextStyle(color: Colors.black, fontSize: 18.0),
),
),
);
}
Widget _profileChips(String myName, String myImage) {
return Material(
child: InputChip(
avatar: CircleAvatar(
backgroundColor: Colors.blueGrey,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
fit: BoxFit.fill,
image: NetworkImage(myImage),
)),
),
),
label: Text(myName),
labelStyle: TextStyle(
color: Colors.black, fontSize: 14.0, fontWeight: FontWeight.bold),
onPressed: () {},
onDeleted: () {},
),
);
}
}
Output:
And for a fully functional example, I've tested the answer in here.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// See: https://twitter.com/shakil807/status/1042127387515858949
// https://github.com/pchmn/MaterialChipsInput/tree/master/library/src/main/java/com/pchmn/materialchips
// https://github.com/BelooS/ChipsLayoutManager
void main() => runApp(ChipsDemoApp());
class ChipsDemoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primaryColor: Colors.indigo,
accentColor: Colors.pink,
),
home: DemoScreen(),
);
}
}
class DemoScreen extends StatefulWidget {
#override
_DemoScreenState createState() => _DemoScreenState();
}
class _DemoScreenState extends State<DemoScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Material Chips Input'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: const InputDecoration(hintText: 'normal'),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ChipsInput<AppProfile>(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search), hintText: 'Profile search'),
findSuggestions: _findSuggestions,
onChanged: _onChanged,
chipBuilder: (BuildContext context,
ChipsInputState<AppProfile> state, AppProfile profile) {
return InputChip(
key: ObjectKey(profile),
label: Text(profile.name),
avatar: CircleAvatar(
backgroundImage: NetworkImage(profile.imageUrl),
),
onDeleted: () => state.deleteChip(profile),
onSelected: (_) => _onChipTapped(profile),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
);
},
suggestionBuilder: (BuildContext context,
ChipsInputState<AppProfile> state, AppProfile profile) {
return ListTile(
key: ObjectKey(profile),
leading: CircleAvatar(
backgroundImage: NetworkImage(profile.imageUrl),
),
title: Text(profile.name),
subtitle: Text(profile.email),
onTap: () => state.selectSuggestion(profile),
);
},
),
),
),
],
),
);
}
void _onChipTapped(AppProfile profile) {
print('$profile');
}
void _onChanged(List<AppProfile> data) {
print('onChanged $data');
}
Future<List<AppProfile>> _findSuggestions(String query) async {
if (query.length != 0) {
return mockResults.where((profile) {
return profile.name.contains(query) || profile.email.contains(query);
}).toList(growable: false);
} else {
return const <AppProfile>[];
}
}
}
// -------------------------------------------------
const mockResults = <AppProfile>[
AppProfile('Stock Man', 'stock#man.com',
'https://d2gg9evh47fn9z.cloudfront.net/800px_COLOURBOX4057996.jpg'),
AppProfile('Paul', 'paul#google.com',
'https://mbtskoudsalg.com/images/person-stock-image-png.png'),
AppProfile('Fred', 'fred#google.com',
'https://media.istockphoto.com/photos/feeling-great-about-my-corporate-choices-picture-id507296326'),
AppProfile('Bera', 'bera#flutter.io',
'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png'),
AppProfile('John', 'john#flutter.io',
'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png'),
AppProfile('Thomas', 'thomas#flutter.io',
'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png'),
AppProfile('Norbert', 'norbert#flutter.io',
'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png'),
AppProfile('Marina', 'marina#flutter.io',
'https://upload.wikimedia.org/wikipedia/commons/7/7c/Profile_avatar_placeholder_large.png'),
];
class AppProfile {
final String name;
final String email;
final String imageUrl;
const AppProfile(this.name, this.email, this.imageUrl);
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is AppProfile &&
runtimeType == other.runtimeType &&
name == other.name;
#override
int get hashCode => name.hashCode;
#override
String toString() {
return 'Profile{$name}';
}
}
// -------------------------------------------------
typedef ChipsInputSuggestions<T> = Future<List<T>> Function(String query);
typedef ChipSelected<T> = void Function(T data, bool selected);
typedef ChipsBuilder<T> = Widget Function(
BuildContext context, ChipsInputState<T> state, T data);
class ChipsInput<T> extends StatefulWidget {
const ChipsInput({
Key key,
this.decoration = const InputDecoration(),
#required this.chipBuilder,
#required this.suggestionBuilder,
#required this.findSuggestions,
#required this.onChanged,
this.onChipTapped,
}) : super(key: key);
final InputDecoration decoration;
final ChipsInputSuggestions findSuggestions;
final ValueChanged<List<T>> onChanged;
final ValueChanged<T> onChipTapped;
final ChipsBuilder<T> chipBuilder;
final ChipsBuilder<T> suggestionBuilder;
#override
ChipsInputState<T> createState() => ChipsInputState<T>();
}
class ChipsInputState<T> extends State<ChipsInput<T>>
implements TextInputClient {
static const kObjectReplacementChar = 0xFFFC;
Set<T> _chips = Set<T>();
List<T> _suggestions;
int _searchId = 0;
FocusNode _focusNode;
TextEditingValue _value = TextEditingValue();
TextInputConnection _connection;
String get text => String.fromCharCodes(
_value.text.codeUnits.where((ch) => ch != kObjectReplacementChar),
);
bool get _hasInputConnection => _connection != null && _connection.attached;
void requestKeyboard() {
if (_focusNode.hasFocus) {
_openInputConnection();
} else {
FocusScope.of(context).requestFocus(_focusNode);
}
}
void selectSuggestion(T data) {
setState(() {
_chips.add(data);
_updateTextInputState();
_suggestions = null;
});
widget.onChanged(_chips.toList(growable: false));
}
void deleteChip(T data) {
setState(() {
_chips.remove(data);
_updateTextInputState();
});
widget.onChanged(_chips.toList(growable: false));
}
#override
void initState() {
super.initState();
_focusNode = FocusNode();
_focusNode.addListener(_onFocusChanged);
}
void _onFocusChanged() {
if (_focusNode.hasFocus) {
_openInputConnection();
} else {
_closeInputConnectionIfNeeded();
}
setState(() {
// rebuild so that _TextCursor is hidden.
});
}
#override
void dispose() {
_focusNode?.dispose();
_closeInputConnectionIfNeeded();
super.dispose();
}
void _openInputConnection() {
if (!_hasInputConnection) {
_connection = TextInput.attach(this, TextInputConfiguration());
_connection.setEditingState(_value);
}
_connection.show();
}
void _closeInputConnectionIfNeeded() {
if (_hasInputConnection) {
_connection.close();
_connection = null;
}
}
#override
Widget build(BuildContext context) {
var chipsChildren = _chips
.map<Widget>(
(data) => widget.chipBuilder(context, this, data),
)
.toList();
final theme = Theme.of(context);
chipsChildren.add(
Container(
height: 32.0,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
text,
style: theme.textTheme.subtitle1.copyWith(
height: 1.5,
),
),
_TextCaret(
resumed: _focusNode.hasFocus,
),
],
),
),
);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
//mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: requestKeyboard,
child: InputDecorator(
decoration: widget.decoration,
isFocused: _focusNode.hasFocus,
isEmpty: _value.text.length == 0,
child: Wrap(
children: chipsChildren,
spacing: 4.0,
runSpacing: 4.0,
),
),
),
Expanded(
child: ListView.builder(
itemCount: _suggestions?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
return widget.suggestionBuilder(
context, this, _suggestions[index]);
},
),
),
],
);
}
#override
void updateEditingValue(TextEditingValue value) {
final oldCount = _countReplacements(_value);
final newCount = _countReplacements(value);
setState(() {
if (newCount < oldCount) {
_chips = Set.from(_chips.take(newCount));
}
_value = value;
});
_onSearchChanged(text);
}
int _countReplacements(TextEditingValue value) {
return value.text.codeUnits
.where((ch) => ch == kObjectReplacementChar)
.length;
}
#override
void performAction(TextInputAction action) {
_focusNode.unfocus();
}
void _updateTextInputState() {
final text =
String.fromCharCodes(_chips.map((_) => kObjectReplacementChar));
_value = TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
composing: TextRange(start: 0, end: text.length),
);
_connection.setEditingState(_value);
}
void _onSearchChanged(String value) async {
final localId = ++_searchId;
final results = await widget.findSuggestions(value);
if (_searchId == localId && mounted) {
setState(() => _suggestions = results
.where((profile) => !_chips.contains(profile))
.toList(growable: false));
}
}
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class _TextCaret extends StatefulWidget {
const _TextCaret({
Key key,
this.duration = const Duration(milliseconds: 500),
this.resumed = false,
}) : super(key: key);
final Duration duration;
final bool resumed;
#override
_TextCursorState createState() => _TextCursorState();
}
class _TextCursorState extends State<_TextCaret>
with SingleTickerProviderStateMixin {
bool _displayed = false;
Timer _timer;
#override
void initState() {
super.initState();
_timer = Timer.periodic(widget.duration, _onTimer);
}
void _onTimer(Timer timer) {
setState(() => _displayed = !_displayed);
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return FractionallySizedBox(
heightFactor: 0.7,
child: Opacity(
opacity: _displayed && widget.resumed ? 1.0 : 0.0,
child: Container(
width: 2.0,
color: theme.primaryColor,
),
),
);
}
}
Working output:
Aside from the samples above, you have the option to use flutter_chips_input plugin.
Flutter library for building input fields with InputChips as input
options.
Here is an example: