I've recently started programming using dart and flutter and everything has been going smoothly for my app, although recently i wanted to add drop down menu to provide the user with multiple options to pick from. everything worked as planned however when i pick a value from the list it doesn't change the value in the box, it goes back to the hint or an empty box. any help would be appreciated!
here is my code for the dropdownbutton:
Widget buildDropdownButton() {
String newValue;
return new Padding(
padding: const EdgeInsets.all(24.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new ListTile(
title: const Text('Frosting'),
trailing: new DropdownButton<String>(
hint: Text('Choose'),
onChanged: (String changedValue) {
newValue=changedValue;
setState(() {
newValue;
print(newValue);
});
},
value: newValue,
items: <String>['None', 'Chocolate', 'Vanilla', 'ButterCream']
.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList()),
),
],
),
);
}
The error is because you are declaring a method variable newValue you must declare that variable as global inside your StatefulWidget.
String newValue;
Widget buildDropdownButton() {
return new Padding(
padding: const EdgeInsets.all(24.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new ListTile(
title: const Text('Frosting'),
trailing: new DropdownButton<String>(
hint: Text('Choose'),
onChanged: (String changedValue) {
newValue=changedValue;
setState(() {
newValue;
print(newValue);
});
},
value: newValue,
items: <String>['None', 'Chocolate', 'Vanilla', 'ButterCream']
.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList()),
),
],
),
);
}
Faced same issue and none of the answers worked. Then, I found the solution in one of my old projects.
I was using it in a AlertDialog here.
So, Change DropdownButton to DropdownButtonFormField
and add onSaved exactly as onChanged:
onSaved: (value) {
setState(() {
_selectedValue = value;
});
}
That's it. It will start working.
I had this problem although I was already using the solution above.
for anyone who has this problem and the above solution does not work, try separating FutureBuilder from the dropdown. this is how your final code should look like:
class TheFuture extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: myFuture(),
builder: (ctx, snp) {
if (!snp.hasData) return LoadingLine();
return TheBuilder(snp.data);
},
);
}
}
class TheBuilder extends StatefulWidget {
const TheBuilder(this.mp);
final Map<String, dynamic> mp;
#override
_MessageUSScreenFilterBodyState createState() =>
_MessageUSScreenFilterBodyState();
}
class _MessageUSScreenFilterBodyState extends State<MessageUSScreenFilterBody> {
int _selectedId;
#override
Widget build(BuildContext context) {
return DropdownButton<int>(
selectedItemBuilder: (context) =>
widget.mp['myData'].map((e) => Text(e.type)).toList(),
items: widget.mp['myData']
.map(
(e) => DropdownMenuItem(
child: Text(e.type),
value: e.id,
),
)
.toList(),
value: _selectedId,
onChanged: (int _id) {
setState(() {
_selectedId = _id;
});
},
);
}
}
wrap dropdown button with StatefulBuilder and initialise newValue outside build method.
StatefulBuilder(
builder: (context, setState) => AlertDialog(
title: Text("Change Status"),
content: Container(
padding: EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 1),
borderRadius: BorderRadius.circular(5)),
child: DropdownButtonHideUnderline(
child: DropdownButton(
hint: Text('Choose'),
onChanged: (String changedValue) {
setState(() {
newValue = changedValue;
print(newValue);
});
},
value: newValue,
items: <String>[
'None',
'Chocolate',
'Vanilla',
'ButterCream'
].map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList()),
),
),
),
));
Related
I'm having trouble passing the data that's been filled in a textformfields and selected in a dropdown menu.
I'm trying to use the Map function to pass down String values so that I can also pass down all types of values in the future (ex. int, bool, double etc.), however it's not working so I need someone to check it out.
main.dart
import 'package:flutter/material.dart';
import 'package:workoutapp/auth/auth.dart';
import 'package:workoutapp/auth/root_page.dart';
import 'package:workoutapp/inheritedWigets/auth_provider.dart';
void main(List<String> args) {
runApp(
WorkoutManager(),
);
}
class WorkoutManager extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AuthProvider(
auth: Auth(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Workout Manager',
home: RootPage(),
theme: ThemeData(
primaryColor: Colors.indigo,
primarySwatch: Colors.indigo,
accentColor: Colors.indigoAccent,
hintColor: Colors.indigo,
brightness: Brightness.dark,
),
),
);
}
}
HomePage
import 'package:flutter/material.dart';
import 'package:workoutapp/inheritedWigets/auth_provider.dart';
import './profile_account_page.dart';
import './routines_create_page.dart';
import '../objects/Routines/routines_manager.dart';
import '../tools/custom_drawer.dart';
class HomePage extends StatelessWidget {
final VoidCallback onSignedOut;
final List<Map<String, String>> routines;
HomePage({Key key, this.onSignedOut, this.routines}) : super(key: key);
void _signedOut(BuildContext context) async {
try {
var auth = AuthProvider.of(context).auth;
await auth.signOut();
onSignedOut();
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Workout Manager', style: TextStyle(color: Colors.white)),
centerTitle: false,
actions: <Widget>[
FlatButton(
child: Text('Logout'),
onPressed: () {
return _signedOut(context);
},
),
IconButton(
icon: Icon(Icons.account_box),
tooltip: 'Profile Account',
color: Colors.white,
onPressed: () {
return Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return ProfileAccountPage();
}));
},
),
],
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return RoutinesPageCreate();
}));
},
),
body: RoutinesManager(),
drawer: CustomDrawer(),
);
}
}
RoutineManager
import 'package:flutter/material.dart';
import 'package:workoutapp/objects/routines/routines.dart';
class RoutinesManager extends StatefulWidget {
final Map<String, String> startingRoutine;
RoutinesManager({this.startingRoutine});
#override
_RoutinesManagerState createState() => _RoutinesManagerState();
}
class _RoutinesManagerState extends State<RoutinesManager> {
List<Map<String, String>> _routines = [];
#override
void initState() {
if (widget.startingRoutine != null) {
_routines.add(widget.startingRoutine);
}
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: Routines(_routines),
)
],
);
}
}
RoutinesCreatePage
import 'package:flutter/material.dart';
import 'package:workoutapp/pages/home_page.dart';
class RoutinesPageCreate extends StatefulWidget {
#override
_RoutinesPageCreateState createState() => _RoutinesPageCreateState();
}
class _RoutinesPageCreateState extends State<RoutinesPageCreate> {
final formKey = GlobalKey<FormState>();
List<Map<String, String>> _routines = [];
String _routineName, _routineDescription;
var _routineNameController = TextEditingController();
var _routineDescriptionController = TextEditingController();
List<DropdownMenuItem<String>> _dropdownListBodyPartMenuItem = [];
List<String> _dropdownListBodyPart = [
'Chest',
'Back',
'Leg',
'Shoulder',
'Abs',
];
String _selectedBodyPart;
List<DropdownMenuItem<String>> _dropdownListDayOfWeekMenuItem = [];
List<String> _dropdownListDayOfWeek = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
];
String _selectedDayOfWeek;
void loadBodyPartData() {
_dropdownListBodyPartMenuItem = [];
_dropdownListBodyPartMenuItem = _dropdownListBodyPart.map((val) {
return DropdownMenuItem<String>(
child: Text(val),
value: val,
);
}).toList();
}
void loadDayOfWeekData() {
_dropdownListDayOfWeekMenuItem = [];
_dropdownListDayOfWeekMenuItem = _dropdownListDayOfWeek.map((val) {
return DropdownMenuItem<String>(
child: Text(val),
value: val,
);
}).toList();
}
final _scaffoldState = GlobalKey<ScaffoldState>();
void _showSnakBarReset() {
_scaffoldState.currentState.showSnackBar(
SnackBar(
backgroundColor: Theme.of(context).accentColor,
content: Text('Showing SnackBar TEST'),
),
);
}
void _showSnakBarCreateWorkoutRoutine() {
_scaffoldState.currentState.showSnackBar(
SnackBar(
backgroundColor: Theme.of(context).accentColor,
content: Text('Workout Routine has been created'),
),
);
}
void _addRoutine(Map<String, String> routine) {
setState(() {
_routines.add(routine);
});
}
#override
Widget build(BuildContext context) {
loadBodyPartData();
loadDayOfWeekData();
return Scaffold(
key: _scaffoldState,
appBar: AppBar(
title: Text('Create Routines'),
),
body: Container(
padding: EdgeInsets.all(15.0),
child: Form(
key: formKey,
child: ListView(children: buildInputs() + buildCreateButtons()),
),
),
);
}
List<Widget> buildInputs() {
TextStyle textStyle = Theme.of(context).textTheme.title;
return [
TextFormField(
controller: _routineNameController,
validator: (value) {
if (value.length > 20) {
return 'Not a valid Routine Name';
}
},
onSaved: (value) {
return _routineName = value;
},
decoration: InputDecoration(
labelStyle: textStyle,
labelText: 'Routine Name',
hintText: 'Enter the Routine Name for this day',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
))),
Padding(padding: EdgeInsets.all(7.0)),
TextFormField(
controller: _routineDescriptionController,
validator: (value) {
if (value.length > 50) {
return 'Invalid: The Description must be 50 characters or less.';
}
},
onSaved: (value) {
return _routineDescription = value;
},
decoration: InputDecoration(
labelStyle: textStyle,
labelText: 'Description',
hintText: 'Enter the description of the Routine.',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
))),
Padding(padding: const EdgeInsets.all(7.0)),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
DropdownButtonHideUnderline(
child: DropdownButton(
value: _selectedBodyPart,
items: _dropdownListBodyPartMenuItem,
hint: Text('Select Body Part', style: textStyle),
onChanged: (value) {
setState(() {
_selectedBodyPart = value;
});
})),
Padding(
padding: const EdgeInsets.all(1.0),
),
DropdownButtonHideUnderline(
child: DropdownButton(
value: _selectedDayOfWeek,
items: _dropdownListDayOfWeekMenuItem,
hint: Text('Select Day of Week', style: textStyle),
onChanged: (value) {
setState(() {
_selectedDayOfWeek = value;
});
},
),
),
Padding(
padding: const EdgeInsets.all(4.0),
)
],
),
];
}
List<Widget> buildCreateButtons() {
return [
Padding(
padding: const EdgeInsets.all(5.0),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
textColor: Theme.of(context).primaryColorDark,
color: Theme.of(context).accentColor,
child: Text('Create Workout Routine'),
onPressed: () {
if (formKey.currentState.validate()) {
_showSnakBarCreateWorkoutRoutine();
formKey.currentState.save();
_addRoutine({
'routineName': 'Chest Workout',
'description': 'Heavy',
'bodyPart': 'Chest',
'week': 'Monday',
});
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return HomePage();
}));
} else {
return null;
}
}),
),
Expanded(
child: RaisedButton(
textColor: Theme.of(context).primaryColorLight,
color: Theme.of(context).primaryColorDark,
child: Text('Reset'),
onPressed: () {
setState(() {
_showSnakBarReset();
formKey.currentState.reset();
_selectedBodyPart = null;
_selectedDayOfWeek = null;
});
},
),
),
],
),
),
];
}
}
Routines
import 'package:flutter/material.dart';
import 'package:workoutapp/objects/routines/routines_detail.dart';
class Routines extends StatelessWidget {
final List<Map<String, String>> routines;
Routines(this.routines);
Widget _buildRoutinesItem(BuildContext context, int index) {
TextStyle textStyle = Theme.of(context).textTheme.title;
return Expanded(
child: Card(
margin: EdgeInsets.all(5.0),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(routines[index]['routineName'], style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(routines[index]['description'], style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(routines[index]['bodyPart'], style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(routines[index]['week'], style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: ButtonBar(
alignment: MainAxisAlignment.center,
children: <Widget>[
FlatButton(
child: Text('Details'),
onPressed: () {
return Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return RoutinesDetail(
routines[index]['routineName'],
routines[index]['description'],
routines[index]['bodyPart'],
routines[index]['week']);
}));
},
)
],
),
)
],
),
),
);
}
Widget _buildRoutinesList(context) {
TextStyle textStyle = Theme.of(context).textTheme.title;
Widget routinesCards = Container(
child: Container(
child: Center(
child: Text("No routines found, please add some.", style: textStyle),
),
),
);
if (routines.length > 0 || routines.length <= 7) {
ListView.builder(
itemBuilder: _buildRoutinesItem,
itemCount: routines.length,
);
}
return routinesCards;
}
#override
Widget build(BuildContext context) {
return _buildRoutinesList(context);
}
}
RoutineDetailPage
import 'package:flutter/material.dart';
class RoutinesDetail extends StatelessWidget {
final String routineName, description, bodyPart, week;
RoutinesDetail(this.routineName, this.description, this.bodyPart, this.week);
#override
Widget build(BuildContext context) {
TextStyle textStyle = Theme.of(context).textTheme.title;
return Scaffold(
appBar: AppBar(
title: Text(routineName),
centerTitle: true,
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(routineName, style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(description, style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(bodyPart, style: textStyle)),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text(week, style: textStyle)),
Container(
padding: EdgeInsets.all(5.0),
child: RaisedButton(
child: Text('Delete'),
onPressed: () {
Navigator.pop(context);
},
),
),
],
),
),
);
}
}
As you can see, I'm trying to separate the code into multiple files as much as possible, so it's more "readable" and make it easy for myself to make changes to the code whenever I have to in the future.
The problem is, it's quite spit up, I don't understand how to use the data and pass it down or up to the pages or widgets as there are multiple stateful and stateless widgets that are suppose to work together to make this app possible.
You'll notice on the HomePage file (StatelessWidget), I'm trying to show the Scaffold body argument with the RoutinesManager StatefulWidget, which is in a different file. At the same time in the HomePage file, I have a Scaffold floatingActionButton argument that will take you to the RoutinesCreatePage StatefulWidget to create a List of Cards (StatelessWidget) using the ListView.builder(). However, no Card gets created under the HomePage after the "Create Workout Routine" RaisedButton gets pressed in the RoutinesCreatePage and no data gets passed.
Can someone please help me out here as I am totally clueless. Also, I'm fairly a beginner regarding flutter/dart so a solution with a relatively easy to understand explanation would be very helpful.
Note: I do have other files that contribute to this app, however I don't think they're part of the problem so I left them out intentionally.
If more information is needed, please do let me know.
Thanks you!
it looks like you misunderstand what state in Flutter is. To explain in short, state is the internal status/data/... that belongs that that specific widget. StatefulWidget has state to determine if UI should be re-rendered on its own state change. External widgets never know about other widgets' states.
So it means, any state change happening inside RoutinesCreatePage widget, only that RoutinesCreatePage knows and reacts. Unless, you inform other widgets to know something has changed.
Alright, so talking about navigation, it works like a stack structure. HomePage trigger a push to RoutinesCreatePage, then to return, you need to pop, not another push.
Here a quick fix for your code, you can try.
HomePage
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
handleNewRoutine(); <--- this is to handle navigation and retrieve returning data from pop
},
),
Future handleNewRoutine() async {
var newRoutine = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => RoutinesPageCreate());
)
if (newRoutine == null) {
// nothing returns from RoutinesPageCreate widget
// so do nothing then
} else {
// add to routine list
// and trigger list re-rendering
setState(() {
this.routines.add(newRoutine);
});
}
}
RoutinesCreatePage: when clicking submit button, just populate all data from input fields, make object model and pop to return data to where this widget was pushed.
onPressed: () {
var newRoutine = .... // populate from UI to create new Routine model object.
Navigator.pop(context, newRoutine);
}
Also, take time to read the navigation guide from official Flutter documentation. It is very detailed on this part. https://flutter.io/docs/cookbook/navigation/returning-data
Some additional comments to your code:
in RoutinesCreatePage you don't need to know application level state, I mean _routines variable is unnecessary. You only need one object to store new routine to pop back to HomePage.
in Routines, this method Widget _buildRoutinesList(context) having unused ListView creation.
if (routines.length > 0 || routines.length <= 7) {
ListView.builder(
itemBuilder: _buildRoutinesItem,
itemCount: routines.length,
);
}
I'm trying to create a List of data from online server Firebase using StreamBuilder bu the checkbox won't get checked.
I have used StreamBuilder to get the data and used LisTile widget to build the list items but the checkboxtilelist widget won't work after defining setState() function. And buildBody is defined under build Widget class.
Widget buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('hisab').snapshots(),
builder: (context, snapshots) {
if (!snapshots.hasData) {
return LinearProgressIndicator();
}
return _buildList(context, snapshots.data.documents);
}
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return ListView(
padding: EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListitem(context, data)).toList(),
);
}
Widget _buildListitem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot((data));
bool _values = false;
void _onChanged(bool newValue) {
setState(() {
_values = newValue;
});
}
return Padding(
padding: EdgeInsets.symmetric(horizontal: 18.0, vertical: 9.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(5.0),
),
child: new ListTile(
onTap: () {
_onChanged(!_values);
},
leading: CircleAvatar(child: Text(record.name[0])),
title: new Column(
children: <Widget>[
new CheckboxListTile(
title: Text(record.name),
value: _values,
onChanged: _onChanged,
)
],
),
),
),
);
}
It's good idea if you create new stateful widget class:
class CustomListItemWidget extends StatefulWidget {
CustomListItemWidget({Key key, #required this.record}) : super(key: key);
final record;
#override
State createState() => _CustomListItemWidgetState();
}
class _CustomListItemWidgetState extends State<CustomListItemWidget> {
bool _values = false;
void _onChanged(bool newValue) {
setState(() {
_values = newValue;
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 18.0, vertical: 9.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
borderRadius: BorderRadius.circular(5.0),
),
child: new ListTile(
onTap: () {
_onChanged(!_values);
},
leading: CircleAvatar(child: Text(widget.record.name[0])),
title: new Column(
children: <Widget>[
new CheckboxListTile(
title: Text(widget.record.name[0]),
value: _values,
onChanged: _onChanged,
)
],
),
),
),
);
}
}
Next, you can pass value from your method _buildListitem:
Widget _buildListitem(BuildContext context, DocumentSnapshot data) {
return CustomListItemWidget(
record: Record.fromSnapshot((data)),
);
}
When using the following Switch widget, the isOn value always returns true and never changes.
The Switch only moves position on a swipe too, a tap won't move it. How to resolve?
bool isInstructionView = false;
Switch(
value: isInstructionView,
onChanged: (bool isOn) {
setState(() {
isInstructionView = isOn;
print(isInstructionView);
});
},
activeColor: Colors.blue,
inactiveTrackColor: Colors.grey,
inactiveThumbColor: Colors.grey,
)
Update: For extra clarity, onChanged always returns isOn as true. Why would this be?
I added additional code chunks according to #Zulfiqar 's answer. I didn't test this code but I m using similar codes in my project. if you want to save it and use in another class or if you want to show latest state for everytime you load you can save the state in a global variable and call it when you load the class. hope it will help..
class Tab_b extends StatefulWidget {
#override
State<StatefulWidget> createState() => new _TabsPageState();
}
class _TabsPageState extends State<Tab_b>{
bool isInstructionView;
#override
void initState() {
isInstructionView = Global.shared.isInstructionView;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text("add data"),
),
body: new Container(
child: Switch(
value: isInstructionView,
onChanged: (bool isOn) {
print(isOn);
setState(() {
isInstructionView = isOn;
Global.shared.isInstructionView = isOn;
isOn =!isOn;
print(isInstructionView);
});
},
activeColor: Colors.blue,
inactiveTrackColor: Colors.grey,
inactiveThumbColor: Colors.grey,
),
),
);
}
}
class Global{
static final shared =Global();
bool isInstructionView = false;
}
You just need to make sure to declare the bool for the switch toggle outside Widget build to make it global and acessible for SetState method. No need to initstate and etc.
class ExampleClass extends StatefulWidget {
#override
_ExampleClassState createState() => _ExampleClassState();
}
class _ExampleClassState extends State<ExampleClass> {
bool isInstructionView = false;
#override
Widget build(BuildContext context) {
return Container(
child: Switch(
value: isInstructionView,
onChanged: (isOn) {
setState(() {
isInstructionView = isOn
});
print(isInstructionView);
},
...
),
);
}
}
class Tab_b extends StatefulWidget {
bool isInstructionView = false;
#override
State<StatefulWidget> createState() => new _TabsPageState();
}
class _TabsPageState extends State<Tab_b>{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text("add data"),
),
body: new Container(
child: Switch(
value: widget.isInstructionView,
onChanged: (bool isOn) {
print(isOn);
setState(() {
widget.isInstructionView = isOn;
print(widget.isInstructionView);
});
},
activeColor: Colors.blue,
inactiveTrackColor: Colors.grey,
inactiveThumbColor: Colors.grey,
),
),
);
}
Here Is my code for toggle button
class ToggleButtonScreen extends StatefulWidget {
#override
_ToggleButtonScreenState createState() => _ToggleButtonScreenState();
}
class _ToggleButtonScreenState extends State<ToggleButtonScreen> {
bool _value = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: _value ? AssetImage("images/cnw.png") : AssetImage("images/cnw.png"),
fit: BoxFit.cover,
),
),
child: Padding(
padding: EdgeInsets.all(AppDimens.EDGE_REGULAR),
child: Column(
children: [
// _customSwitchButton(),
_normalToggleButton(),
],
),
),
),
),
),
);
}
Widget _normalToggleButton () {
return Container(
child: Transform.scale(
scale: 2.0,
child: Switch(
activeColor : Colors.greenAccent,
inactiveThumbColor: Colors.redAccent,
value: _value,
activeThumbImage: AssetImage("images/cnw.png"),
inactiveThumbImage : AssetImage("images/simple_interest.png"),
onChanged: (bool value){
setState(() {
_value = value;
});
},
),
),
);
}
}
You need to rebuild the widget when the state changes. refer the documentation
https://docs.flutter.io/flutter/material/Switch/onChanged.html
I faced the same issue , the problem is your
bool isInstructionView = false;
is in same build method which will get rebuild due to change of switch to render new UI on setState()
Solution is to move it out of function Scope to Class Scope so that your variable do not change on rendering Widget again
To get Switch to work , move the setState(() {}) outside of Switch in a callback function .
// Switch Widget
Switch( value: _toggleState,
onChanged: _attemptChange,
),
//Callback
void _attemptChange(bool newState) {
setState(() {
_toggleState = newState;
newState ? _switchCase = 'ON' : _switchCase = 'OFF';
});
Change SwitchListTile instead of Switch will work.
bool isSwitched = false;
SwitchListTile(
title: Text("title"),
controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.symmetric(),
value: isSwitched ,
onChanged: (bool value) {
setState(() {
isSwitched = value;
});
}
)
Use Transform when use Switch will work.
bool isSwitched = false;
Transform.scale(
scale: 1.8,
child: Switch(
onChanged: (value) {
setState(() {
isSwitched = value;
});
},
value: isSwitched,
),
)
I have also got the same problem while I was implementing CupertinoSwitch. I was able to turn it ON but wasn't able to turn it OFF.
According to this example, I tried to put my Switch inside the widget called 'Semantics' and magically it started working.
Below is the code:
body: Column(
children: <Widget>[
SizedBox(height: 50.0,),
Semantics(
container: true,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
CupertinoSwitch(
value: _switchValue,
onChanged: (bool value){
setState(() {
_switchValue = value;
_animating = value;
});
},
),
Text(
"${_switchValue ? "On" : "Off"}",
style: TextStyle(fontSize: 20.0,
fontWeight: FontWeight.bold),
),
],
),
),
SizedBox(
height: 50.0,
),
Visibility(
child: CupertinoActivityIndicator(
animating: _animating,
radius: 30.0,
),
visible: _animating,
),
],
),
Hope it helps.
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) {
...
...
i want to achieve this design of dropdown but not sure how to get this type of dropdown i have tried doing this
Container(
padding: EdgeInsets.all(10.0),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
items:_locations.map((String val){
return DropdownMenuItem<String>(
value: val,
child: Container(
margin: EdgeInsets.only(left: 10.0,right: 10.0),
child: new Text(val)
),
);
}).toList(),
hint:Text(_SelectdType),
onChanged:(String val){
_SelectdType = val;
setState(() {});
}
),
),
)
The hint for the DropdownButton and the child for the DropdownMenuItem are just widgets like everything else, so you can put whatever you want in there.
This example uses a Map to supply the text and icons:
final Map<String, IconData> _data = Map.fromIterables(
['First', 'Second', 'Third'],
[Icons.filter_1, Icons.filter_2, Icons.filter_3]);
String _selectedType;
IconData _selectedIcon;
...
Container(
padding: EdgeInsets.all(10.0),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
items: _data.keys.map((String val) {
return DropdownMenuItem<String>(
value: val,
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Icon(_data[val]),
),
Text(val),
],
),
);
}).toList(),
hint: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child:
Icon(_selectedIcon ?? _data.values.toList()[0]),
),
Text(_selectedType ?? _data.keys.toList()[0]),
],
),
onChanged: (String val) {
setState(() {
_selectedType = val;
_selectedIcon = _data[val];
});
}),
),
),
You can use a Row to build your DropdownMenuItem.
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
String dropdownValue = 'One';
#override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: dropdownValue,
icon: Icon(Icons.arrow_downward),
iconSize: 24,
elevation: 16,
style: TextStyle(color: Colors.deepPurple),
underline: Container(
height: 2,
color: Colors.redAccent,
),
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
items: <String>['One', 'Two', 'Free', 'Four']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Row(children: [
IconButton(
icon: Icon(Icons.home),
color: Colors.redAccent,
),
Text(value),
]),
);
}).toList(),
);
}
}
You can use it like:
import 'package:flutter/material.dart';
class SettingsWidget extends StatefulWidget {
SettingsWidget({Key key}) : super(key: key);
#override
_SettingsWidgetState createState() => new _SettingsWidgetState();
}
class _SettingsWidgetState extends State<SettingsWidget> {
List _cities =
["Cluj-Napoca", "Bucuresti", "Timisoara", "Brasov", "Constanta"];
List<DropdownMenuItem<String>> _dropDownMenuItems;
String _currentCity;
#override
void initState() {
_dropDownMenuItems = getDropDownMenuItems();
_currentCity = _dropDownMenuItems[0].value;
super.initState();
}
List<DropdownMenuItem<String>> getDropDownMenuItems() {
List<DropdownMenuItem<String>> items = new List();
for (String city in _cities) {
items.add(new DropdownMenuItem(
value: city,
child: new Text(city)
));
}
return items;
}
#override
Widget build(BuildContext context) {
return new Container(
color: Colors.white,
child: new Center(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text("Please choose your city: "),
new Container(
padding: new EdgeInsets.all(16.0),
),
new DropdownButton(
value: _currentCity,
items: _dropDownMenuItems,
onChanged: changedDropDownItem,
)
],
)
),
);
}
void changedDropDownItem(String selectedCity) {
setState(() {
_currentCity = selectedCity;
});
}
}