Flutter Bottomsheet Modal not rerendering - dart

On my grid items when I click, it opens a ModalBottomSheet and listed with filter chips of strings. When you click a filter chip value, the value is updated but the widget does not re-render. The app is a StatefulWidget.
I have called the function setState.
What I expect is filterchips becomes checked and unchecked on selection.
void _showBottom(index){
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context){
return new Container(
padding: new EdgeInsets.all(27.0),
child: new Column(
children: <Widget>[
new Text('Some headline', style: new TextStyle( fontWeight: FontWeight.bold, fontSize: 22),),
getFilterChipsWidgets(index),
],
),
);
}
);
}
Widget getFilterChipsWidgets(index)
{
List<Widget> tags_list = new List<Widget>();
for(var i=0; i<list[index]["tags"].length; i++) {
var _isSelected = true;
FilterChip item = new FilterChip(
label: Text("Filtertext",),
selected: _isSelected,
onSelected: (bool newValue) {
setState(() {
_isSelected = !_isSelected;
debugPrint(_isSelected.toString());
});
},
);
tags_list.add(item);
}
return new Row(children: tags_list);
}

You need to add height for the root node of the bottom sheet. So change your container to have a fixed height.
Container(
height: 300.0, // Add height
padding: new EdgeInsets.all(27.0),
child: new Column(
children: <Widget>[
new Text('Some headline',
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 22),),
getFilterChipsWidgets(index),
],
),
I usually calculate this dynamically based on the widgets I'm passing in. But this should get you started.
Edit
The comment I gave below was the actual answer.
You should wrap all the widgets in the bottom sheet into it's own stateful widget and set your values in there.

Modal Bottom sheet seems to call the constructor whenever any change in the UI occurs in the child widget for example changing focus inside a form.
So you need to create instances of the objects you are working with inside a parent StatefulWidget and then to reflect changes in the UI of the Model Bottom Sheet you need to call setState whenever you make changes to the data.

You need to wrap the under StatefulBuilder like this
import 'package:flutter/material.dart';
void _showBottom(index){
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context){
return StatefulBuilder( builder: (BuildContext context, StateSetter setState){
return new Container(
padding: new EdgeInsets.all(27.0),
child: new Column(
children: <Widget>[
new Text('Some headline', style: new TextStyle( fontWeight: FontWeight.bold, fontSize: 22),),
getFilterChipsWidgets(index, setState),
],
),
);
});
}
);}
Widget getFilterChipsWidgets(index, StateSetter setState)
{
List<Widget> tags_list = new List<Widget>();
for(var i=0; i<list[index]["tags"].length; i++) {
var _isSelected = true;
FilterChip item = new FilterChip(
label: Text("Filtertext",),
selected: _isSelected,
onSelected: (bool newValue) {
setState(() {
_isSelected = !_isSelected;
debugPrint(_isSelected.toString());
});
},
);
tags_list.add(item);
}
return new Row(children: tags_list);
}

Related

TextController on TextField doesn't listen to changes && OnChanged after being edited once doesn't work anymore

I want to be able to edit the TextField and after all of them are filled, a value is calculated. Every time the user change a value, the value must be recalculated.
I've tried with both the Streams and Controller. The stream was doing fine, even tho the update of the value was done with data's that were 1 change old.
i.e. height, weight and age all must be != null, when we have at first {height: 2, weight:3, age:2}, it doesn't get calculated, after one of them is changed then the result is calculated but with the above data.
The controller instead doesn't seem to listen on the variable change at all.
Here I initialize the controller in another class
class Bsmi extends StatelessWidget {
StreamController<void> valueBoxStream = StreamController<void>.broadcast();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new DefaultAppBar(context: context, title: Text(" ")),
body: new ListView(
children: <Widget>[
BsmiResult(valueBoxStream, {}),
new Container(
alignment: Alignment.topLeft,
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
),
child: ListView(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
padding: EdgeInsets.all(20.0),
itemExtent: 80.0,
children: <Widget>[
_BsmiResultState().indices("height", valueBoxStream),
_BsmiResultState().indices("weight", valueBoxStream),
_BsmiResultState().indices("age", valueBoxStream),
],
),
),
],
),
);
}
}
This is the important code
class BsmiResult extends StatefulWidget {
final StreamController<void> valueBoxStream;
const BsmiResult(this.valueBoxStream, this.data);
final Map data;
#override
_BsmiResultState createState() =>
_BsmiResultState(valueBoxStream: this.valueBoxStream, data: this.data);
}
class _BsmiResultState extends State<BsmiResult> {
_BsmiResultState({this.valueBoxStream, this.data});
StreamController<void> valueBoxStream;
final Map data;
final textFieldController = new TextEditingController();
int weight;
int age;
int height;
String _result = "0.0";
_printLatestValue() {
print("Second text field: ${textFieldController.text}");
}
void setData(data) {
if (data['type'] == 'height') {
print("I'm inside height\n");
height = int.parse(data['value']);
} else if (data['type'] == 'weight') {
print("I'm inside weight\n");
weight = int.parse(data['value']);
} else {
print("I'm inside age\n");
age = int.parse(data['value']);
}
}
#override
void initState() {
super.initState();
textFieldController.addListener(_printLatestValue);
valueBoxStream.stream.listen((_){
_calculateResult();
});
}
#override
void dispose() {
// Clean up the controller when the Widget is removed from the Widget tree
textFieldController.dispose();
super.dispose();
}
void _calculateResult() {
if ((height != null) && (height != 0) && (weight != null) && (weight != 0) && (age != null) && (age != 0)) {
print("height: " +
height.toString() +
"" +
" weight: " +
weight.toString() +
" age: " +
age.toString());
_result = ((height + weight) / age).toString();
print(_result + "\n");
} else {
_result = "0.0";
}
}
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Text("Risultato"),
new Container(
decoration: new BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
), //For the controller i don't use the StreamBuilder but a normal Text("$_result"),
child: StreamBuilder(
stream: valueBoxStream.stream,
builder: (context, snapshot) {
if (snapshot.hasData || _result != "0.0") {
print(snapshot.data);
setData(snapshot.data);
return new Text(
"$_result",
textAlign: TextAlign.end,
);
} else {
return new Text(
"0.0",
textAlign: TextAlign.end,
);
}
},
),
),
],
);
}
Row indices(String text, StreamController<void> valueBoxStream) {
return new Row(
children: <Widget>[
leftSection(text),
rightSection(text, valueBoxStream),
],
);
}
Widget leftSection(text) {
return new Expanded(
child: new Container(
child: new Text(
text,
style: TextStyle(fontSize: 18),
),
),
);
}
Container rightSection(text, valueBoxStream) {
return new Container(
child: new Flexible(
flex: 1,
child: new TextField(
controller: textFieldController,
keyboardType: TextInputType.number,
textAlign: TextAlign.end,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter(new RegExp("[0-9.]")),
LengthLimitingTextInputFormatter(4),
],
decoration: new InputDecoration(
enabledBorder: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(15),
borderSide: new BorderSide(width: 1.2),
),
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(15),
borderSide: new BorderSide(color: Colors.lightBlueAccent),
),
hintText: '0.0',
),
onChanged: (val) {
valueBoxStream.add({'type': text, 'value': val});
},
),
),
);
}
}
I should be able at this point to get Result: x.y, but I get it only once after I change the textField only once and in the same 'session'
Thanks in advance who can really explain to me why this is not working and what errors I'm making.
I'm really sorry.. but there have been so many things wrong with that code, I'm not even sure where to begin. It looks like you read all versions on how to handle state in flutter and managed to combine it all in one simple use case.. Here is one working example fix: https://gitlab.com/hpoul/clinimetric/commit/413d32d4041f2e6adb7e30af1126af4b05e72271
I'm not sure how to answer this "question" so that others could possibly benefit from it. but..
If you create a stream controller or a text view controller.. You have state.. hence, create a stateful widget (otherwise you can't for example close streams and subscriptions)
Get a handle on which widget is responsible for which state. And pass down only useful values to child views. One possible architecture is to use a ViewController and use Sinks as inputs for change events, and Streams for handling those events. So if you create a stream controller, only use a streamController.sink if you want to notify of events, and use a streamController.stream if a widget needs to listen to events
the build method is not the right place to handle events. I refactored your code that the handling of events and calculation of events is done in the listener of the stream. If you want to use a StreamBuilder you could have a StreamController and your producers directly push the Result into the sink.. so the builder method of the StreamBuilder would only need to do Text('${snapshot.data.value}')
There are probably quite a few things i'm missing in that list. but that should get you started. look at my change set. It is not perfect, but it works. You should try to understand what you are doing and reduce complexity instead of stacking everything you find on top of each other.

Aligning alert dialog button bar widgets

I'm new to flutter, so I'm trying to create a widget that shows an alert dialog. In the content of alert dialog I got SingleChildScrollView and in, so called, button bar I have a text, checkbox and a button, which I want to align(put checkbox with text on the left side, the button on the right side), but I don't know how. Tried expanded and flexible, also tried to insert row with mainAxisAlignment set to spaceEvenly, not working, could someone please help me?
Here is the code:
class TermsAndConditionsAlertDialog extends StatefulWidget {
TermsAndConditionsAlertDialogState createState() {
return new TermsAndConditionsAlertDialogState();
}
}
class TermsAndConditionsAlertDialogState
extends State<TermsAndConditionsAlertDialog> {
static bool _isChecked = false;
//TODO get the terms and conditions message
static final String _TERMS_AND_CONDITIONS_MESSAGE =
'blablabla this is a terms and conditions message and a blablababl and a bla bla and a aaaaaaaaaaaa bla';
static final String _DIALOG_TITLE = 'Terms and Conditions';
#override
Widget build(BuildContext context) {
// TODO: implement build
return new AlertDialog(
title: new Text(_DIALOG_TITLE),
content: new SingleChildScrollView(
child: new Text(
_TERMS_AND_CONDITIONS_MESSAGE,
style: new TextStyle(fontSize: 50.0),
),
),
actions: <Widget>[
new Text('Accept'),
new Checkbox(
// title: Text('Accept'),
value: _isChecked,
onChanged: (bool newValue) {
setState(() {
_isChecked = newValue;
});
},
),
new RaisedButton(
onPressed: () {
_printDialogResult();
_closeDialog();
//TODO add a method to move on with an app
},
child: new Text(
'Start',
style: TextStyle(color: Colors.white),
)),
],
);
}
void _printDialogResult() {
//simply prints the result in console
print('You selected 1');
}
void _closeDialog() {
if (_isChecked) {
Navigator.pop(context);
}
}
}[FL][1]
You want to use the content property to place your widgets because the actions will actually be wrapped in a ButtonBar and placed on the bottom right.
So a solution may be split the content of the dialog with a Column letting the SingleChildScrollView to expand to fill the viewport and placing a Row with your desired widgets on the bottom with the mainAxisAlignment: MainAxisAlignment.spaceBetween,. Since you also want to group the Text and CheckBox, another Row will do the job to gather both next to each other.
I've edited your example so you can copy/paste and try/tweak it yourself. This will produce the result below.
class TermsAndConditionsAlertDialog extends StatefulWidget {
TermsAndConditionsAlertDialogState createState() {
return new TermsAndConditionsAlertDialogState();
}
}
class TermsAndConditionsAlertDialogState extends State<TermsAndConditionsAlertDialog> {
static bool _isChecked = false;
static final String _TERMS_AND_CONDITIONS_MESSAGE =
'blablabla this is a terms and conditions message and a blablababl and a bla bla and a aaaaaaaaaaaa bla';
static final String _DIALOG_TITLE = 'Terms and Conditions';
#override
Widget build(BuildContext context) {
return new AlertDialog(
title: new Text(_DIALOG_TITLE),
content: new Column(
children: <Widget>[
new Expanded(
child: new SingleChildScrollView(
child: new Text(
_TERMS_AND_CONDITIONS_MESSAGE,
style: new TextStyle(fontSize: 50.0),
),
),
),
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Row(
children: <Widget>[
new Text('Accept'),
new Checkbox(
value: _isChecked,
onChanged: (bool newValue) {
setState(() {
_isChecked = newValue;
});
},
),
],
),
new RaisedButton(
onPressed: () {
_printDialogResult();
_closeDialog();
},
child: new Text(
'Start',
style: TextStyle(color: Colors.white),
)),
],
),
],
),
);
}
void _printDialogResult() {
print('You selected 1');
}
void _closeDialog() {
if (_isChecked) {
Navigator.pop(context);
}
}
}

flutter to enable and disable various Check_List_Tile using one boolean variable

I am new to flutter. in my project, there is a various check_list_tile depending upon the length of the List (attendance list). And I have used one Boolean variable. Now when I press on one checkbox it automatically checks all other checkboxes. Please help me in this (on tap one checkbox should not change the state of all other checkboxes except clicked). I have copied all code please check check_box_list field.
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'dart:async';
Map map_student_data;
Iterable iter_student_data,iter_student_key;
List list_student_data,list_student_key;
bool t=true,checkbox=false;
List list;
String validation="yes";
int i;
int year;
final FirebaseDatabase database = FirebaseDatabase.instance;
class IImca_attendence extends StatefulWidget {
#override
_IImca_attendenceState createState() => _IImca_attendenceState();
}
class _IImca_attendenceState extends State<IImca_attendence> {
#override
void initState(){
this.check_year();
super.initState();
}
DateTime date = DateTime.now();
Future check_year()async{
var k= await database.reference().child("NITTE/CLASS/MCA").once().then((DataSnapshot snapshot){
Map sea= snapshot.value;
Iterable iter=sea.keys;
list=iter.toList();
list.sublist(list.length-1);
list.sort();
setState(() {
year=list.length-2;
});
check();
});
}
Future check()async{
var m=await database.reference().child("NITTE/CLASS/MCA/${list[year].toString().toUpperCase()}/STUDENT").once().then((DataSnapshot currentyear){
map_student_data=currentyear.value;
iter_student_data=map_student_data.values;
iter_student_key=map_student_data.keys;
list_student_data=iter_student_data.toList();
list_student_key=iter_student_key.toList();
for(i=0;i<=list_student_data.length;i++){
bool ss=true;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("II-MCA"),
centerTitle: true,
actions: <Widget>[
IconButton(icon: Icon(Icons.refresh),onPressed: (){
setState(() {
check_year();
});
})
],
),
body:
validation=="1"?
new Center(
child: Text("STUDENT DOSE NOT EXIST IN $year",style: TextStyle(color: Colors.grey,fontWeight: FontWeight.bold,fontSize: 20),),
):
new ListView.builder(
itemCount: list_student_data==null?0
:list_student_data.length,
itemBuilder: (BuildContext context,int index){
var student_detail= ['NAME : ${list_student_data[index]['NAME']}','GENDER : ${list_student_data[index]['CURRENT CLASS']}','PHOTO : ${list_student_data[index]['PHOTO']}'];
return new Container(
child: new Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Card(
child: new Container(
child: CheckboxListTile(
title: Text("${list_student_key[index]}",style:TextStyle(fontWeight: FontWeight.bold,),),
subtitle: Text("NAME : ${list_student_data[index]['NAME']}"),
value: checkbox,
onChanged: (val){
setState(() {
checkbox=val;
if(checkbox==true){
print("${list_student_data[index]['NAME']}: i am absent");
}if(checkbox==false){
print("${list_student_data[index]['NAME']}: i am present");
}
});
},
),
padding: EdgeInsets.all(5),
),
)
],
),
),
);
}
)
);
}
}
Well... you are using a global checkbox variable, so It's quite normal that if you change it, all widget depending on its state will change accordingly.
What I suggest you to do is to add the selected state inside your model class. Just as an example, assumed you have this Student class (I know you are using firebase, but for sake of time I don't)
class Student {
var name = 'foo';
var year = '2018';
var selected = false;
Student(this.name);
}
This class has is selected state inside of it.
Now assume that your snapshot give you 3 students. Always for sake of time I've embedded a local array:
class _IImca_attendenceState extends State<IImca_attendence> {
var _students = [Student('foo'), Student('pub'), Student('beer')];
...
(Ellipses are not part of code... ;-])
I suggest you to put your state variables inside the Stateful Widget scope and not onto the Global Scope.
That said you could have:
ListView.builder(
itemCount: _students.length,
itemBuilder: (BuildContext context, int index) {
return new Container(
child: new Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Card(
child: new Container(
child: CheckboxListTile(
title: Text(
_students[index].name,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle:
Text("NAME : ${_students[index].name}"),
value: _students[index].selected,
onChanged: (val) {
setState(() {
_students[index].selected = val;
if (!_students[index].selected) {
print(
'${_students[index].name}: i am absent');
}
if (_students[index].selected) {
print(
'${_students[index].name}: i am present');
}
});
},
),
padding: EdgeInsets.all(5),
),
)
],
),
),
);
}
)
You should also use an array of bool(s) of the same length of your snapshot data students array... but I'd like to suggest to track this information directly on your Student model.
UPDATE
As you are more comfortable using array I've change my code using a complementary array of bool of the same size of your student list.
All you have to do is an array (not a single value) of boolean values, the same size of your student array, lets call this list_student_present (I instead use list_student_present2)
At the beginning you initialize this in your check function a way like that:
list_student_data = iter_student_data.toList();
// This is the array you wanna use (first all false)
list_student_present = iter_student_data.map((_) => false).toList();
And then you will use this array of bool to check the state of your checkboxes:
itemBuilder: (BuildContext context, int index) {
return new Container(
child: new Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Card(
child: new Container(
child: CheckboxListTile(
title: Text(
_students[index].name,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle:
Text("NAME : ${_students[index].name}"),
value: list_student_present[index],
onChanged: (val) {
setState(() {
list_student_present[index] = val;
if (!list_student_present[index]) {
print(
'${_students[index].name}: i am absent');
}
if (list_student_present[index]) {
print(
'${_students[index].name}: i am present');
}
});
},
),
padding: EdgeInsets.all(5),
),
)
],
),
),
);
}
Do not use my Student class (I continue using it so that I'm able to show you data without firebase), continue using your students array list from your firebase snapshot.
Full code:
import 'package:flutter/material.dart';
import 'dart:async';
Map map_student_data;
Iterable iter_student_data, iter_student_key;
List list_student_data, list_student_key, list_student_present, list_student_present2;
bool t = true;
List list;
String validation = "yes";
int i;
int year;
class Student {
var name = 'foo';
var year = '2018';
var selected = false;
Student(this.name);
}
class IImca_attendence extends StatefulWidget {
#override
_IImca_attendenceState createState() => _IImca_attendenceState();
}
class _IImca_attendenceState extends State<IImca_attendence> {
var _students = [Student('foo'), Student('pub'), Student('beer')];
#override
void initState() {
this.check_year();
super.initState();
}
DateTime date = DateTime.now();
Future check_year() async {
Map sea = {1: 'atlantic', 2: 'pacific'};
Iterable iter = sea.keys;
list = iter.toList();
list.sublist(list.length - 1);
list.sort();
setState(() {
year = list.length - 2;
});
check();
}
Future check() async {
map_student_data = {
0: {'NAME': 'foo', 'CURRENT CLASS': 'pub', 'PHOTO': ''}
};
iter_student_data = map_student_data.values;
iter_student_key = map_student_data.keys;
list_student_data = iter_student_data.toList();
// This is the array you wanna use
list_student_present = iter_student_data.map((_) => false).toList();
// This is the array for my example
list_student_present2 = _students.map((_) => false).toList();
list_student_key = iter_student_key.toList();
for (i = 0; i <= list_student_data.length; i++) {
bool ss = true;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("II-MCA"),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () {
setState(() {
check_year();
});
})
],
),
body: validation == "1"
? new Center(
child: Text(
"STUDENT DOSE NOT EXIST IN $year",
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
fontSize: 20),
),
)
: ListView.builder(
itemCount: _students.length,
itemBuilder: (BuildContext context, int index) {
return new Container(
child: new Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Card(
child: new Container(
child: CheckboxListTile(
title: Text(
_students[index].name,
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle:
Text("NAME : ${_students[index].name}"),
value: list_student_present2[index],
onChanged: (val) {
setState(() {
list_student_present2[index] = val;
if (!list_student_present2[index]) {
print(
'${_students[index].name}: i am absent');
}
if (list_student_present2[index]) {
print(
'${_students[index].name}: i am present');
}
});
},
),
padding: EdgeInsets.all(5),
),
)
],
),
),
);
}));
}
}
I really don't like this solution. What I suggest you is to create your PODOs (Plain Old Dart Object) representing your firebase models and deserialise them from your firebase snapshots.

onTap doesn't work on ListWheelScrollView children items - Flutter

I'm trying to make a list of items using ListWheelScrollView and I want to have the ability of tapping on items but it seems onTap doesn't work.
Here is a simple code
List<int> numbers = [
1,
2,
3,
4,
5
];
...
Container(
height: 200,
child: ListWheelScrollView(
controller: fixedExtentScrollController,
physics: FixedExtentScrollPhysics(),
children: numbers.map((month) {
return Card(
child: GestureDetector(
onTap: () {
print(123);
},
child: Row(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
month.toString(),
style: TextStyle(fontSize: 18.0),
),
)),
],
),
));
}).toList(),
itemExtent: 60.0,
),
)
Is there something wrong with this code ? I'm pretty sure something like this will work on a ListView or other scrolling widgets.
I solved the problem. I hope is helpful.
create a int variable in State class.
class _MenuWheelState extends State {
int _vIndiceWheel;
in the Function onSelectedItemChanged of the ListWheelScrollView set the variable:
onSelectedItemChanged: (ValueChanged) {
setState(() {
_vIndiceWheel = ValueChanged; });
},
create a GestureDetecture and put the ListWheelScrollView inside:
GestureDetector(
child: ListWheelScrollView(...
create onTap function at the GestureDetecture like this code:
// this is necessary
if (_vIndiceWheel == null) {
_vIndiceWheel = 0;
}
switch (_vIndiceWheel) {
case 0:
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return YourSecondScreen();
},
...

Flutter order of execution: build and initState

I'm trying to create a preferences menu where I have three settings (e. g. 'notifications') stored with Shared Preferences. They are applied to SwitchListTiles.
Everytime my settings tab is selected there is an error (I/flutter (22754): Another exception was thrown: 'package:flutter/src/material/switch_list_tile.dart': Failed assertion: line 84 pos 15: 'value != null': is not true.) appearing just a millisecond. After that the correct settings are displayed. This happens when I don't add a default value to the variables initialized in 'ProfileState'. If they have a default value the error disappears but the switches are 'flickering' at tab selection from the default value to the correct value in Shared Preferences.
My assumption is that my loadSettings function is executed after the build method.
How can I solve that? Any help is appreciated.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Profile extends StatefulWidget {
#override
ProfileState createState() {
return new ProfileState();
}
}
class ProfileState extends State<Profile> {
bool notifications;
bool trackHistory;
bool instantOrders;
#override
void initState() {
super.initState();
loadSettings();
}
//load settings
loadSettings() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
notifications = (prefs.getBool('notifications') ?? true);
trackHistory = (prefs.getBool('trackHistory') ?? true);
instantOrders = (prefs.getBool('instantOrders') ?? false);
});
}
//set settings
setSettings() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('notifications', notifications);
prefs.setBool('trackHistory', trackHistory);
prefs.setBool('instantOrders', instantOrders);
}
#override
Widget build(BuildContext context) {
return new ListView(
children: <Widget>[
new Row(
children: <Widget>[
new Container(
padding: new EdgeInsets.fromLTRB(20.0, 20.0, 0.0, 8.0),
child: new Text("General", style: new TextStyle(color: Colors.black54)),
)
],
),
new SwitchListTile(
title: const Text('Receive Notifications'),
activeColor: Colors.brown,
value: notifications,
onChanged: (bool value) {
setState(() {
notifications = value;
setSettings();
});
},
secondary: const Icon(Icons.notifications, color: Colors.brown),
),
new SwitchListTile(
title: const Text('Track History of Orders'),
activeColor: Colors.brown,
value: trackHistory,
onChanged: (bool value) {
setState((){
trackHistory = value;
setSettings();
});
},
secondary: const Icon(Icons.history, color: Colors.brown,),
),
new SwitchListTile(
title: const Text('Force instant Orders'),
activeColor: Colors.brown,
value: instantOrders,
onChanged: (bool value) {
setState((){
instantOrders = value;
setSettings();
});
},
secondary: const Icon(Icons.fast_forward, color: Colors.brown),
),
new Divider(
height: 10.0,
),
new Container(
padding: EdgeInsets.all(32.0),
child: new Center(
child: new Column(
children: <Widget>[
new TextField(
)
],
),
),
),
new Divider(
height: 10.0,
),
new Row(
children: <Widget>[
new Container(
padding: new EdgeInsets.fromLTRB(20.0, 20.0, 0.0, 20.0),
child: new Text("License Information", style: new TextStyle(color: Colors.black54)),
)
],
),
new Container(
padding: new EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 20.0) ,
child: new RichText(
text: new TextSpan(
text: "With confirming our terms and conditions you accept full usage of your personal data. Yikes!",
style: new TextStyle(color: Colors.black)
)
)
)
]
);
}
}
EDIT
I tried to solve it with the recommended FutureBuilder from Darek's solution. The initial error is solved now but now I face another inconvenience. The tab builds itself completely everytime a switch is tapped which is clearly noticable. Furthermore the switches don't run smoothly anymore. On startup of the app you can also see the waiting message shortly which isn't that pretty.
Here is the new class in the code:
class ProfileState extends State<Profile> {
bool notifications;
bool trackHistory;
bool instantOrders;
SharedPreferences prefs;
#override
void initState() {
super.initState();
loadSettings();
}
//load settings
Future<String> loadSettings() async {
prefs = await SharedPreferences.getInstance();
notifications= (prefs.getBool('notifications') ?? true);
trackHistory = (prefs.getBool('trackHistory') ?? true);
instantOrders= (prefs.getBool('instantOrders') ?? true);
}
//set settings
setSettings() async {
prefs.setBool('notifications', notifications);
prefs.setBool('trackHistory', trackHistory);
prefs.setBool('instantOrders', instantOrders);
}
#override
Widget build(BuildContext context) {
var profileBuilder = new FutureBuilder(
future: loadSettings(), // a Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text('No preferences');
case ConnectionState.waiting:
return new Text('Loading preferences');
case ConnectionState.done:
if (snapshot.hasError)
return new Text('Error: ');
else
return new Column(
children: <Widget>[
new Row(
children: <Widget>[
new Container(
padding: new EdgeInsets.fromLTRB(20.0, 20.0, 0.0, 8.0),
child: new Text("General", style: new TextStyle(color: Colors.black54)),
)
],
),
new SwitchListTile(
title: const Text('Receive Notifications'),
activeColor: Colors.brown,
value: notifications,
onChanged: (bool value) {
setState(() {
notifications = value;
setSettings();
});
},
secondary: const Icon(Icons.notifications, color: Colors.brown),
),
new SwitchListTile(
title: const Text('Track History of Orders'),
activeColor: Colors.brown,
value: trackHistory,
onChanged: (bool value) {
setState((){
trackHistory = value;
setSettings();
});
},
secondary: const Icon(Icons.history, color: Colors.brown,),
),
new SwitchListTile(
title: const Text('Force instant Orders'),
activeColor: Colors.brown,
value: instantOrders,
onChanged: (bool value) {
setState((){
instantOrders = value;
setSettings();
});
},
secondary: const Icon(Icons.fast_forward, color: Colors.brown),
),
new Divider(
height: 10.0,
),
new Row(
children: <Widget>[
new Container(
padding: new EdgeInsets.fromLTRB(20.0, 20.0, 0.0, 20.0),
child: new Text("License Information", style: new TextStyle(color: Colors.black54)),
)
],
),
new Container(
padding: new EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 20.0) ,
child: new RichText(
text: new TextSpan(
text: "With confirming our terms and conditions you accept full usage of your personal data. Yikes!",
style: new TextStyle(color: Colors.black)
)
)
)
]
);
}
},
);
return new Scaffold(
body: profileBuilder,
);
}
}
The lifecycle of a State object goes createState -> initState -> didChangeDependencies -> build (see the linked doc for more details). So in your case it's not an ordering problem. What's actually happening is that loadSettings is getting called, but as soon as it hits the await a Future is return and execution of the caller continues (see async/await in the Dart docs). So, your widget tree is being built and your initially null values are being used, then the async part gets executed and your variables are initialised and setState is called triggering the rebuild, which works fine.
What you need to use is a FutureBuilder so that you can build the UI accordingly when the Future has finished:
new FutureBuilder(
future: _calculation, // a Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none: return new Text('Press button to start');
case ConnectionState.waiting: return new Text('Awaiting result...');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return new Text('Result: ${snapshot.data}');
}
},
)
In the above example, you'd replace _calculation with loadSettings and return the relevant UIs in the none and waiting states (the latter will be your one with the SwitchListTiles).
To fix the problem of your Edit, store the Future from the loadSettings call in your initState and use this Future for the Future Builder. What you are doing now is calling the function loadSettings everytime your UI rebuilds.
class ProfileState extends State<Profile> {
bool notifications;
bool trackHistory;
bool instantOrders;
SharedPreferences prefs;
Future<String> loadSettingsFuture; // <-Add this
#override
void initState() {
super.initState();
loadSettingsFuture = loadSettings();// <- Change This
}
...
...
...
#override
Widget build(BuildContext context) {
var profileBuilder = new FutureBuilder(
future: loadSettingsFuture, // <-- Change this
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
...
...

Resources