Avoid Updating Parent Variable when Updating Child - flutter

I have problem with updating variable since it also affect its parent. I am trying to remove List item from child but it also removing the parent data.
Here is my code
Future<void> ChangeSubCategory({required String value}) async {
if (this.mounted) {
setState(() {
if (!_subCategory.contains(value)) {
if (value == 'all') {
_subCategory = _categoryOutput[_category]; => set _subCategory from parent List
} else {
_subCategory.add(value);
}
} else if (_subCategory.contains(value)) {
_subCategory.remove(value); => When doing this one, the parent _categoryOutput also affected
}
UpdateFilter();
});
}
}
What is the safest way if we want to replicate variable from parent? since in flutter it also update the parent when we update child variable. Thank you.

Your problem is updating the whole state of parent which updates all the sub children widgets, to avoid that, you can use StatefulBuilder to only update the child you want!
As the below example, even all the three Checkboxes change the variable isChecked, but only the first Checkbox can refreshes the whole state which refreshes all the three Checkboxes childs, but the the second and the third Checkboxes can only refresh its state as they are using StatefulBuilder:
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool isChecked = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Refresh all the checkboxes'),
Checkbox(
value: isChecked,
onChanged: (value) {
setState(() {
isChecked = !isChecked;
});
},
),
Divider(),
Text('Refresh only this checkbox'),
StatefulBuilder(
builder: (context, setState) => Checkbox(
value: isChecked,
onChanged: (value) {
setState(() {
isChecked = !isChecked;
});
},
),
),
Divider(),
Text('Refresh only this checkbox'),
StatefulBuilder(
builder: (context, setState) => Checkbox(
value: isChecked,
onChanged: (value) {
setState(() {
isChecked = !isChecked;
});
},
),
),
],
),
)),
);
}
}

Related

Related DropdownButtons and raising onChange event

I have two DropdownButton widget. Content of second one depends on first one selection. Second DropdownButton will initiate refresh of third widget. How can I initiate refresh of second DropdownButton when first one is populated? And then how can I refresh third widget when second DropdownButton populated also?
class ParentBloc {
Stream<List<Parent>> get items => _controller.asyncMap(...);
Future<List<Parent>> _callApi() {
// call endpoint /parents
}
}
class ChildBloc {
ChildBloc(this.parentId);
int parentId;
Stream<List<Child>> get items => _controller.asyncMap(...);
Future<List<Child>> _callApi() {
// call endpoint /parents/$parentId/children
}
}
// This bloc created at init state
ParentBloc parentBloc;
// This bloc will be created only after value will
// be selected in the Parent dropdownbutton because
// I need to know `parentId`.
ChildBloc childBloc;
#override
void initState() {
super.initState();
parentBloc = ParentBloc();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
StreamBuilder<List<Parent>>(
stream: parentBloc.items,
builder: (context,snapshot) {
return DropdownButton(
items: snapshot.data.map((item) {
return DropdownButtonItem();
}),
);
}
),
// Content of this widget depends on above one
StreamBuilder<List<Child>>(
stream: childBloc.items,
builder: (context,snapshot) {
return DropdownButton(
items: snapshot.data.map((item) {
return DropdownButtonItem();
}),
);
}
),
// Content of this widget depends on above one
StreamBuilder<List<Grandchild>>(
stream: grandchildBloc.items,
builder: (context,snapshot) {
return ListView(),
);
}
),
]
);
}
Provided you're doing this inside a StatefulWidget, you can use setState inside one of your functions where you update the variables that in turn have to be used to control what is currently displayed in each of your widgets.
It should look something like this (inside your Dropdown):
onChanged: (newValue) {
setState(() {
_currentSelection = newValue;
});
},
Update: after discussion in the comments, here's a working example that I made of how something can be updated based on a value selected inside a dropbox, hope it helps:
import 'package:flutter/material.dart';
class ExampleWidget extends StatefulWidget {
#override
_ExampleWidgetState createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
List<String> someStringsToSelectFrom = [
'value1',
'value2',
'value3',
];
String selectedValue;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Select something:'),
DropdownButton<String>(
value: selectedValue,
icon: Icon(
Icons.arrow_downward,
color: Colors.deepPurpleAccent,
),
iconSize: 24,
elevation: 16,
style: TextStyle(
fontSize: 30.0,
color: Colors.green,
),
underline: Container(
height: 2,
color: Colors.deepPurpleAccent,
),
onChanged: (String newValue) {
setState(() {
selectedValue = newValue;
});
},
items: someStringsToSelectFrom.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
Text('This will update after you selected a value:'),
someStringsToSelectFrom.contains(selectedValue) ? Text(selectedValue + ' was selected') :
Text('Still waiting for user action.'),
],
),
),
);
}
}

How to create a CheckBoxTile ListView?

So I'm making an app in and I need a listview of checkboxes. And afterwards it needs to display all the checked items on a new screen. So far what I tried gives me the listview but when I tap on one checkbox, all of them get checked. Any help would be appreciated. Thanks!
bool yes = false
Widget pro() =>
Expanded(
child: ListView(
children: ListTile.divideTiles(
context: context,
tiles: [
options("Apple"),
options("Banana"),
],
).toList(),
),
);
Widget options(String head) =>
CheckboxListTile(
title: Text(head),
value: yes,
onChanged: (bool value) {
setState(() {
yes = value;
});
},
);
try this,
List<bool> isCheckedList = List.filled(2, false, growable: false);
List<String> selectedItemsList = [];
Widget pro() =>
Expanded(
child: ListView(
children: ListTile.divideTiles(
context: context,
tiles: [
options("Apple",0),
options("Banana",1),
],
).toList(),
),
);
Widget options(String head,int index) =>
CheckboxListTile(
title: Text(head),
value: isCheckedList[index],
onChanged: (bool value) {
setState(() {
isCheckedList[index] = value;
value ? selectedItemsList.add(head): selectedItemsList.removeWhere((item) => item == head);
});
},
);
then pass the list while navigating to another page, in this way
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewPage(selectedItemsList: selectedItemsList),
);
then in the new page you can retrieve it this way
class NewPage extends StatelessWidget {
final List<String> selectedItemsList;
NewPage({this.selectedItemsList});
#override
Widget build(BuildContext context) {
return Container();
}
}

State not chaning for switch tiles inside popmenu item flutter

This is my widget
Widget sortingWidget() {
return new PopupMenuButton<int>(
itemBuilder: (context) => [
PopupMenuItem(
value : this._popMenuBtn,
child: SwitchListTile(
title: const Text('Sort by experience'),
value: this._sortByExperienceSwitch,
onChanged: handleExperienceSortingToggle,
),
)
],
);
}
And this is my toggle handle function
handleExperienceSortingToggle(value) {
this.setState(() {
this._sortByExperienceSwitch = value;
}
);
}
But when I change state it is not changing and only changes when the popup-menu button is closed and open
The child of PopIpMenuItem is stateless, so it does not redraw when the value changes.
Either 1) you create a stateful widget for the child,
PopupMenuItem(
child: PopupMenuSwitchItem(
value: _value,
onValueChanged: _handleValueChanged,
),
),
...
class PopupMenuSwitchItem extends StatefulWidget {
PopupMenuSwitchItem({
Key key,
this.value,
this.onValueChanged,
}) : super(key: key);
final bool value;
final ValueChanged<bool> onValueChanged;
#override
_PopupMenuSwitchItemState createState() =>
_PopupMenuSwitchItemState(this.value);
}
class _PopupMenuSwitchItemState extends State<PopupMenuSwitchItem> {
_PopupMenuSwitchItemState(bool value) {
this._state = value;
}
bool _state;
void _handleValueChanged(String value) {
widget.onValueChanged(value);
}
or 2) if you are using a provider to manage the state, just wrap your switch in a Consumer to force the widget to redraw when the value changes.
PopupMenuItem(
child: Consumer<YourProvider>(
builder: (context, state, child) {
return Switch(
value: state.value,
onChanged: (bool value) {
state.handleValueChanged(value);
},
);
...
you need state management,
parent widget is not refreshed on that action, that is why state doesn't change,
have a look at this
https://stackoverflow.com/a/51778268/5180337
it will fix your issue
I had a similar problem, i used a StatefulBuilder to solve it. Here is how i used it for my switch
PopupMenuButton(
itemBuilder: (BuildContext context) => [
PopupMenuItem(
child:
StatefulBuilder(
builder: (context, setState) {
return Switch(
value: darkMode,
onChanged: (bool value) {
setState(() {
darkMode = value;
});
this.setState(() {});
},
);
},
),
),
]
)

Flutter Checkbox not changing/updating/working

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,
),
),
);
}
),
),
),
);
}

Dynamic list of check box tile in alert dialog not working

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();
}
}