Hi I am trying to add some values from TextFormField to ListView when click on enter key .
But getting the error setState() or markNeedsBuild() called during build
My code is below .
class ScanBarcode extends StatefulWidget {
#override
_ScanBarcodeState createState() => _ScanBarcodeState();
}
class _ScanBarcodeState extends State<ScanBarcode> {
List<String> litems = [];
final TextEditingController eCtrl = new TextEditingController();
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
appBar: new AppBar(title: new Text("Dynamic Demo"),),
body: new Column(
children: <Widget>[
new TextFormField(
controller: eCtrl,
maxLines: null,
autovalidate: true,
textInputAction: TextInputAction.none,
// ignore: missing_return
validator: (value) {
if (value.isEmpty==false) {
// if(eCtrl.text.trim()!='') {
if (value.contains('\n')) {
debugPrint(value);
litems.add(value); // Append Text to the list
eCtrl.clear(); // Clear the Text area
setState(() {});
}
}
// }
},
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter a search term'
),
),
new Expanded(
child: new ListView.builder
(
itemCount: litems.length,
itemBuilder: (BuildContext ctxt, int Index) {
return new Text(litems[Index]);
}
)
)
],
)
);
}
}
I am getting below error.
setState() or markNeedsBuild() called during build
if commented
setState()
it is working fine but the values are not added to list view ..
Any help will be appreciated.
You can copy paste run full code below
You can use addPostFrameCallback
code snippet
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {});
});
working demo
full code
import 'package:flutter/material.dart';
class ScanBarcode extends StatefulWidget {
#override
_ScanBarcodeState createState() => _ScanBarcodeState();
}
class _ScanBarcodeState extends State<ScanBarcode> {
List<String> litems = [];
final TextEditingController eCtrl = TextEditingController();
#override
Widget build(BuildContext ctxt) {
return Scaffold(
appBar: AppBar(
title: Text("Dynamic Demo"),
),
body: Column(
children: <Widget>[
TextFormField(
controller: eCtrl,
maxLines: null,
autovalidate: true,
textInputAction: TextInputAction.none,
// ignore: missing_return
validator: (value) {
if (value.isEmpty == false) {
// if(eCtrl.text.trim()!='') {
if (value.contains('\n')) {
debugPrint(value);
litems.add(value); // Append Text to the list
eCtrl.clear(); // Clear the Text area
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {});
});
}
}
// }
},
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter a search term'),
),
Expanded(
child: ListView.builder(
itemCount: litems.length,
itemBuilder: (BuildContext ctxt, int Index) {
return Text(litems[Index]);
}))
],
));
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ScanBarcode(),
);
}
}
Chunhunghan's solution is a valid solution and still works. But, in my case, it's the future build & the stream builder.
What I did is, I directly assigned the Future to a FutureBuilder, which gets called every time when the build() is called, which causes the Future method to re-call & reload my UI every time I touched/typed on a TextField/TextFormField.
The Solution:
Create a Future object & assign the Future method to that future object, then assign the future object to the FutureBuilder widget.
The Code:
late Future _dataFuture;
#override
void initState() {
super.initState();
_stream = _leadDatabase.listenLeads();
_dataFuture = _fetchData();
}
//inside build()
FutureBuilder(future: _dataFuture, builder:context, dataSnapshot) {
//logics
}
The same solution is applicable for StreamBuilder.
Related
the code from this question:
Receive Response from pop navigator in Flutter
with added DateTime picker form field.
If we add DateFormField like this:
maind.dart
import 'package:flutter/material.dart';
import 'package:date_field/date_field.dart';
import 'answer.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appTitle = 'Form Validation Demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(appTitle),
),
body: ShowData(),
),
);
}
}
// Create a Form widget.
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class MyCustomFormState extends State<MyCustomForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey<FormState>,
// not a GlobalKey<MyCustomFormState>.
final _formKey = GlobalKey<FormState>();
final myController = TextEditingController();
Data stateData = Data();
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Scaffold(
body: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
DateFormField(
initialDatePickerMode: DatePickerMode.day,
enabled: true,
key:_formKey,
onSaved: (DateTime value)
{
stateData.datefield = value;
},
validator: (DateTime value){
return stateData.datefield != null ? null : 'enter date';
},
firstDate: DateTime.now().subtract(Duration(days: 180)),
lastDate: DateTime.now().add(Duration(days: 365)),
),
TextFormField(
controller: myController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
onSaved: (value){
stateData.load = value;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
onPressed: () {
// Validate returns true if the form is valid, or false
// otherwise.
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
// If the form is valid, display a Snackbar.
Navigator.pop(context,stateData);
// Scaffold.of(context)
// .showSnackBar(SnackBar(content: Text(myController.text)));
// myController.text = 'look at me';
}
},
child: Text('Submit'),
),
),
],
),
),
);
}
}
class Data {
String load;
DateTime datefield;
}
when pop happens there is exception that the validator was called on null.
and "The following assertion was thrown while finalizing the widget tree:
Multiple widgets used the same GlobalKey."
if the key field is not used, then the validator is not called upon.
why?
how do you use DateFormField? from package
date_field: "^0.1.2"
adding answer.dart
import 'package:flutter/material.dart';
import 'main.dart';
class ShowData extends StatefulWidget {
#override
_ShowDataState createState() => _ShowDataState();
}
class _ShowDataState extends State<ShowData> {
String data = 'start';
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () async {
final holder = await getFormData(context);
setState(() {
data = holder.load;
});
},
elevation: 4,
),
body:Text(data,style: TextStyle(fontSize: 80),));
}
Future<Data> getFormData(BuildContext context) async {
final answer = await Navigator.push(context,MaterialPageRoute(builder: (context)=>MyCustomForm()));
return (Future.value(answer));
}
}
You can copy paste run full code below
Step 1: remove key:_formKey
DateFormField(
initialDatePickerMode: DatePickerMode.day,
enabled: true,
//key:_formKey,
Step 2: validator use value != null not stateData.datefield != null
validator: (DateTime value) {
//return stateData.datefield != null ? null : 'enter date';
return value != null ? null : 'enter date';
},
working demo
full code
import 'package:flutter/material.dart';
import 'package:date_field/date_field.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final appTitle = 'Form Validation Demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(appTitle),
),
body: ShowData(),
),
);
}
}
// Create a Form widget.
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class MyCustomFormState extends State<MyCustomForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey<FormState>,
// not a GlobalKey<MyCustomFormState>.
final _formKey = GlobalKey<FormState>();
final myController = TextEditingController();
Data stateData = Data();
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Scaffold(
body: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
DateFormField(
initialDatePickerMode: DatePickerMode.day,
enabled: true,
//key:_formKey,
onSaved: (DateTime value) {
stateData.datefield = value;
},
validator: (DateTime value) {
//return stateData.datefield != null ? null : 'enter date';
return value != null ? null : 'enter date';
},
firstDate: DateTime.now().subtract(Duration(days: 180)),
lastDate: DateTime.now().add(Duration(days: 365)),
),
TextFormField(
controller: myController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
onSaved: (value) {
stateData.load = value;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
onPressed: () {
// Validate returns true if the form is valid, or false
// otherwise.
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
// If the form is valid, display a Snackbar.
Navigator.pop(context, stateData);
// Scaffold.of(context)
// .showSnackBar(SnackBar(content: Text(myController.text)));
// myController.text = 'look at me';
}
},
child: Text('Submit'),
),
),
],
),
),
);
}
}
class Data {
String load;
DateTime datefield;
}
class ShowData extends StatefulWidget {
#override
_ShowDataState createState() => _ShowDataState();
}
class _ShowDataState extends State<ShowData> {
String data = 'start';
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () async {
final holder = await getFormData(context);
print(holder.datefield);
setState(() {
data = holder.load;
});
},
elevation: 4,
),
body: Text(
data,
style: TextStyle(fontSize: 80),
));
}
Future<Data> getFormData(BuildContext context) async {
final answer = await Navigator.push(
context, MaterialPageRoute(builder: (context) => MyCustomForm()));
return (Future.value(answer));
}
}
This is now fixed with the version 0.2.0 of the package!
I am trying to do a simple mockup of form validation, and so far I was not able to update my State under the BlocBuilder() using the TextField()'s onChanged property.
Here's my BlocProvider()
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Log me in',
home: Scaffold(
body: BlocProvider<LoginBloc>(
create: (context) => LoginBloc(),
child: LoginScreen(),
),
),
);
}
}
Here's the main file that needs to be updated on input changes.
The '$snapshot' supposed to yields the what's coming from the LoginBloc depending on its' current state, but looks like it doesn't rebuild itself onChanged online StreamBuilder()
lass LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
final LoginBloc bloc = BlocProvider.of<LoginBloc>(context);
return BlocBuilder<LoginBloc, String>(
bloc: LoginBloc(),
builder: (context, snapshot) {
return Container(
margin: EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// emailField(bloc),
TextField(
onChanged: (_) => bloc.add(LoginEvent.username),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'example#email.com',
labelText: 'Email Address',
errorText: '$snapshot',
),
),
// passwordField(bloc),
TextField(
onChanged: (_) => bloc.add(LoginEvent.password),
keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration(
hintText: 'Password',
labelText: 'Password',
errorText: '$snapshot',
),
),
SizedBox(
height: 25,
),
// submitButton(),
],
),
);
});
}
Here's my LoginBloc()
class LoginBloc extends Bloc<LoginEvent, String> {
get initialState => '';
Stream<String> mapEventToState(LoginEvent event) async* {
switch (event) {
case LoginEvent.username:
if (state.contains('#')) {
yield 'Approved';
} else {
yield 'Please retry';
}
break;
case LoginEvent.password:
if (state.length > 3) {
yield 'Nice password';
}
yield "Please retry";
}
}
}
Thank you
There are two different instances of LoginBloc in your app. The one created and passed by the BlocProvider and the second which you have created inside the BlocBuilder.
The problem is that you are adding events into the BlocProvider's bloc and the BlocBuilder is listening to the another one.
To fix this you can do like this
Widget build(BuildContext context) {
final LoginBloc bloc = BlocProvider.of<LoginBloc>(context);
return BlocBuilder<LoginBloc, String>(
bloc: bloc, // Pass the above bloc instance
builder: (context, snapshot) {
// rest as it is
}
)
}
Bonus:- You can skip the bloc: bloc as it is an optional argument and if not provided it finds automatically from the tree using BlocProvider
You also need to manage state differently otherwise typing in one of the field will show error in both of them
Looks like the problem is in this line:
final LoginBloc bloc = BlocProvider.of<LoginBloc>(context);
You're accessing the bloc in wrong context. Remove that line and use BlocProvider directly when you want to access LoginBloc:
onChanged: (_) => BlocProvider.of<LoginBloc>(context).add(LoginEvent.username),
I tried to solve this, I looked up the answers in Stack Overflow
But I haven't solved it yet
I used the global key in the create and update pages
What I've done
I tried adding static to the global key ,but I couldn't
because I couldn't wrap the key in a refreshIndicator.
I used Navigator pushNamed instead of Navigator push
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class Update extends StatefulWidget {
#override
_UpdateState createState() => _UpdateState();
}
class _UpdateState extends State<Update> {
GlobalKey<FormState> _formKey1 = GlobalKey<FormState>(debugLabel: '_updateFormKey');
TextEditingController _titleController1 = TextEditingController();
TextEditingController _descController1 = TextEditingController();
final db = Firestore.instance;
DocumentSnapshot _currentDocument;
#override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return MaterialApp(
home: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text('update'),
),
body: _buildUpdate(context)));
}
Widget _buildUpdate(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return StreamBuilder<QuerySnapshot>(
stream: db.collection('flutter_data2').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: snapshot.data.documents.map<Widget>((doc) {
return Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(20.0),
child: Card(
elevation: 2.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0)),
child: Form(
key: _formKey1,
child: Padding(
padding: EdgeInsets.only(left: 12, right: 12),
child: Column(
children: <Widget>[
TextFormField(
controller: _titleController1,
decoration: InputDecoration(labelText: doc.data['title']),
validator: (String value) {
if (value.isEmpty) {
return 'title empty';
} else {
return null;
}
},
),
TextFormField(
controller: _descController1,
decoration: InputDecoration(labelText: doc.data['desc']),
validator: (String value) {
if (value.isEmpty) {
return 'desc empty';
} else {
return null;
}
},
),
],
),
),
),
),
),
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0)),
child: Text('update'),
color: Colors.blue,
onPressed: () async {
if (_formKey1.currentState.validate()) {
db
.collection('flutter_data2')
.document(doc.documentID)
.updateData({'title': _titleController1.text,'desc':_descController1.text});
Navigator.pop(context);
}
},
),
],
);
}).toList(),
);
} else {
return SizedBox();
}
},
);
}
}
You might really want to use some modularity here. Create your custom Form widget preferably in a different file with their own set of controllers. This way you won't have to manage the controllers explicitly. One more thing to notice is that your Button is doing the same job for every entry. In this case, you might as well add the global key inside your custom Form widget and hardcode the onPressed function there.
Here is an example
// This is a mock data. Your firebase snapshot.data will have a similar structure
List<Map<String, dynamic>> _mockData = [
{
'title':'Title 1',
'desc':'Description 1',
},
{
'title':'Title 2',
'desc':'Description 2',
},
{
'title':'Title 3',
'desc':'Description 3',
},
{
'title':'Title 4',
'desc':'Description 4',
},
];
// There are many ways to make this work.
// Instead of hardcoding the function in our custom form widget, We would like to pass a function implementation which will be called after the button in the form is pressed. This way we will have more control on what will happen when we press the button
typedef onFormData = Future<void> Function(String, String); // Future void to allow async updates // The two strings are title and description respectively.
// This is the custom form widget you need to create
class MyForm extends StatefulWidget {
final Map<String, dynamic> data; // Replace it with DocumentSnapshot data.
final onFormData onPressed; // We will use the type we defined up there. So we will be expecting a function implementation here which takes two strings, a title and a description
MyForm({#required this.data, #required this.onPressed, Key key}):super(key: key);
#override
createState() => _MyFormState();
}
// Our custom form widget is defined here
class _MyFormState extends State<MyForm> {
// Define the controllers
TextEditingController _titleController;
TextEditingController _descController;
// Create the key
GlobalKey<FormState> _formKey;
#override
void initState() {
// Initialize the values here
super.initState();
_titleController = TextEditingController();
_descController = TextEditingController();
_formKey = GlobalKey<FormState>();
}
#override
void dispose() {
// Remember that you have to dispose of the controllers once the widget is ready to be disposed of
_titleController.dispose();
_descController.dispose();
_formKey = null;
super.dispose();
}
#override
Widget build(BuildContext context) {
// Everything remains almost same here as in your code
return Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(20.0),
child: Card(
elevation: 2.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0)),
child: Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.only(left: 12, right: 12),
child: Column(
children: <Widget>[
TextFormField(
controller: _titleController, // Assign the controller
decoration:
InputDecoration(labelText: widget.data['title']), // widget.data can still be indexed like this after you replace datatype of the data to DocumentSnapshot
validator: (String value) {
if (value.isEmpty) {
return 'title is empty';
} else {
return null;
}
},
),
TextFormField(
controller: _descController,
decoration:
InputDecoration(labelText: widget.data['desc']), // Same goes here
validator: (String value) {
if (value.isEmpty) {
return 'description is empty';
} else {
return null;
}
},
),
],
),
),
),
),
),
// The button associated with this form
RaisedButton(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
child: Text('Update'),
color: Colors.blue,
onPressed: () async {
// If validation is successful, then call the on pressed function we assigned to the widget. // Check the MyWidget class
if (_formKey.currentState.validate()) {
await widget.onPressed(_titleController.text, _descController.text); // Instead of putting firebase update code here, we are passing the title and description to our main widget from where we will post
}
},
),
],
);
}
}
// Our main widget
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo'),
),
// Wrap this up in your stream builder
// I am using a listview with mock data for the sake of this example.
body: ListView.builder(
itemBuilder: (context, index) {
// We create a new instance of our custom form and we don't need to manage any controllers or keys. We just need to pass the data and what happens when we press the update button in our custom form.
// Here is why we defined a type named onFormData before.
// You can simply post updates in your form widget directly if your logic is same for each form
// We are getting the title and description info here through our custom defined Forms without managing any keys and controllers.
// Also this method is async so you can post your firebase updates from here waiting for them to complete using await
return MyForm(data: _mockData[index], onPressed: (String title, String description) async {
// Put your firebase update code here
_mockData[index]['title'] = title;
_mockData[index]['desc'] = description;
Navigator.of(context).pop(); // Go back after the updates are made as written in your example
});
},
physics: BouncingScrollPhysics(),
itemCount: _mockData.length, // Length of the data.
),
);
}
}
Before any updates:
After writing your title and description:
After pressing update, when you go back to the same screen:
Hope this helps!
I am trying to learn checkboxes in Flutter.
The problem is, when I want to use checkboxes in Scaffold(body:) it is working. But I want to use it in different places like an item in ListView.
return Center(
child: Checkbox(
value: testValue,
onChanged: (bool value) {
setState() {
testValue = value;
}
},
));
But it is not working, updating and changing anything.
Edit: I solved my problem with putting checkbox in a StatefulBuilder. Thanks to #cristianbregant
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: CheckboxListTile(
title: const Text('Animate Slowly'),
value: _valueCheck,
onChanged: (bool value) {
setState(() {
_valueCheck = value;
});
},
secondary: const Icon(Icons.hourglass_empty),
),
);
});
Try these maybe:
return Center(
child: CheckboxListTile(
title: const Text('Animate Slowly'),
value: _valueCheck,
onChanged: (bool value) {
setState(() {
_valueCheck = value;
});
},
secondary: const Icon(Icons.hourglass_empty),
),
);
and remember that if you are using it in a dialog or bottomsheet you need to wrap the Checkbox Widget in a Stateful builder because the state does not update.
Checkboxes require you have a Scaffold or Material as their parent. Without either of these, you get this helpful error message:
The following assertion was thrown building Checkbox(dirty, state: _CheckboxState#1163b):
No Material widget found.
Checkbox widgets require a Material widget ancestor.
In material design, most widgets are conceptually "printed" on a sheet of material.
In Flutter's material library, that material is represented by the Material widget. It is the Material widget that renders ink splashes, for instance. Because of this, many material library widgets require that there be a Material widget in the tree above them.
Once you have a material ancestor, you can place the ListView as it's child and it should show fine:
class SettingsPage extends StatefulWidget {
#override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
var _foo = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Toggle Foo'),
Checkbox(
value: _foo,
onChanged: (bool value) {
setState(() => _foo = value);
},
),
],
),
],
),
);
}
}
Seems like you will have to use both initState and dispose.
See my code example below:
class SettingsOrder extends StatefulWidget {
#override
_SettingsOrderState createState() => _SettingsOrderState();
}
class _SettingsOrderState extends State<SettingsOrder> {
List options = [];
List<bool> newoptions = [];
int selectedoption;
bool checkedstatus;
bool initialcall;
Future getproductlist(selectedoption, checkedstatus, initialcall) async{
List updatedlist = [];
final arguments = ModalRoute.of(context).settings.arguments as Map;
int itempos = 0;
options.clear();
if(initialcall == false){
for(var item in arguments['options']){
updatedlist.add({
'checkbox' : newoptions[itempos]
});
itempos++;
}
} else {
for(var item in arguments['options']){
updatedlist.add({
'checkbox' : checkedstatus
});
newoptions.add(false);
itempos++;
}
}
setState(() {
options = updatedlist;
});
}
#override
void initState(){
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
getproductlist(0, false, true);
return Scaffold(
body: SingleChildScrollView(
child: Container(
width: double.infinity,
child: ListView.builder(
primary: false,
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (BuildContext context, int index){
return Container(
child: Theme(
data: ThemeData(
unselectedWidgetColor: Colors.grey
),
child: CheckboxListTile(
controlAffinity: ListTileControlAffinity.trailing,
title: Text(options[index]['name']),
value: options[index]['checkbox'],
onChanged: (newvalue){
int indexposition = index;
newoptions.removeAt(indexposition);
newoptions.insert(indexposition, newvalue);
getproductlist(indexposition, newvalue, false);
},
activeColor: Color.fromRGBO(0, 130, 214, 1),
checkColor: Colors.white,
),
),
);
}
),
),
),
);
}
There is no clear answer on how to implement a checkbox tile in a dialog and set the state to work.
A print statement is working in setting the state of the checkbox is not changing, but other statements are working. Where can I find the answer?
I am using a dialog with multiple check boxes for multi select. Is there another of implementing multiselect in Flutter?
child: TextFormField(
decoration: InputDecoration(
labelText: 'Team Leader',
labelStyle: TextStyle(color: Colors.black)),
controller: teamLeaderController,
enabled: false,
style: TextStyle(color: Colors.black),
),
onTap: () {
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return CheckBoxDialog(context, teamLeader,
"Choose Team Leader", teamLeaderController, onSubmit);
});
}),
class CheckBoxState extends State<CheckBoxDialog> {
BuildContext context;
List<String> places;
String title;
TextEditingController con;
bool state;
CheckBoxState(this.context, this.places, this.title, this.con);
#override
void initState() {
super.initState();
state = false;
}
#override
Widget build(BuildContext context) {
return new AlertDialog(
title: new Text(title),
content:
Column(children: getMultiSelectOption(context, places, con, state)),
actions: <Widget>[
FlatButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
}),
FlatButton(
child: Text('Ok'),
onPressed: () {
widget.onSubmit("");
Navigator.of(context).pop();
})
],
);
}
List<Widget> getMultiSelectOption(BuildContext context, List<String> places,
TextEditingController con, bool state) {
List<Widget> options = [];
List<String> selectedList = [];
for (int i = 0; i < places.length; i++) {
options.add(CheckboxListTile(
title: Text(places[i]),
value: selectedList.contains(places[i]),
onChanged: (bool value) {
print("on change: $value title: ${places[i]}");
setState(() {
if (value) {
selectedList.add(places[i]);
} else {
selectedList.remove(places[i]);
}
print("contains: ${selectedList.contains(places[i])}");
print("status: $value");
});
}));
}
return options;
}
}
Suppose you have a Dialog with some Widgets such as RadioListTile, DropdowButton… or anything that might need to be updated WHILE the dialog remains visible, how to do it?
Look at this example here.
https://www.didierboelens.com/2018/05/hint-5-how-to-refresh-the-content-of-a-dialog-via-setstate/
Suppose you have a Dialog with some Widgets such as RadioListTile, DropdowButton… or anything that might need to be updated WHILE the dialog remains visible, how to do it?
Difficulty: Beginner
Background
Lately I had to display a Dialog to let the user select an item from a list and I wanted to display a list of RadioListTile.
I had no problem to show the Dialog and display the list, via the following source code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Sample extends StatefulWidget {
#override
_SampleState createState() => new _SampleState();
}
class _SampleState extends State<Sample> {
List<String> countries = <String>['Belgium','France','Italy','Germany','Spain','Portugal'];
int _selectedCountryIndex = 0;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){_showDialog();});
}
_buildList(){
if (countries.length == 0){
return new Container();
}
return new Column(
children: new List<RadioListTile<int>>.generate(
countries.length,
(int index){
return new RadioListTile<int>(
value: index,
groupValue: _selectedCountryIndex,
title: new Text(countries[index]),
onChanged: (int value) {
setState((){
_selectedCountryIndex = value;
});
},
);
}
)
);
}
_showDialog() async{
await showDialog<String>(
context: context,
builder: (BuildContext context){
return new CupertinoAlertDialog(
title: new Text('Please select'),
actions: <Widget>[
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Cancel');},
child: new Text('Cancel'),
),
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Accept');},
child: new Text('Accept'),
),
],
content: new SingleChildScrollView(
child: new Material(
child: _buildList(),
),
),
);
},
barrierDismissible: false,
);
}
#override
Widget build(BuildContext context) {
return new Container();
}
}
I was surprised to see that despite the setState in lines #34-36, the selected RadioListTile was not refreshed when the user tapped one of the items.
Explanation
After some investigation, I realized that the setState() refers to the stateful widget in which the setState is invoked. In this example, any call to the setState() rebuilds the view of the Sample Widget, and not the one of the content of the dialog. Therefore, how to do?
Solution
A very simple solution is to create another stateful widget that renders the content of the dialog. Then, any invocation of the setState will rebuild the content of the dialog.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Sample extends StatefulWidget {
#override
_SampleState createState() => new _SampleState();
}
class _SampleState extends State<Sample> {
List<String> countries = <String>['Belgium','France','Italy','Germany','Spain','Portugal'];
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){_showDialog();});
}
_showDialog() async{
await showDialog<String>(
context: context,
builder: (BuildContext context){
return new CupertinoAlertDialog(
title: new Text('Please select'),
actions: <Widget>[
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Cancel');},
child: new Text('Cancel'),
),
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Accept');},
child: new Text('Accept'),
),
],
content: new SingleChildScrollView(
child: new Material(
child: new MyDialogContent(countries: countries),
),
),
);
},
barrierDismissible: false,
);
}
#override
Widget build(BuildContext context) {
return new Container();
}
}
class MyDialogContent extends StatefulWidget {
MyDialogContent({
Key key,
this.countries,
}): super(key: key);
final List<String> countries;
#override
_MyDialogContentState createState() => new _MyDialogContentState();
}
class _MyDialogContentState extends State<MyDialogContent> {
int _selectedIndex = 0;
#override
void initState(){
super.initState();
}
_getContent(){
if (widget.countries.length == 0){
return new Container();
}
return new Column(
children: new List<RadioListTile<int>>.generate(
widget.countries.length,
(int index){
return new RadioListTile<int>(
value: index,
groupValue: _selectedIndex,
title: new Text(widget.countries[index]),
onChanged: (int value) {
setState((){
_selectedIndex = value;
});
},
);
}
)
);
}
#override
Widget build(BuildContext context) {
return _getContent();
}
}