State not chaning for switch tiles inside popmenu item flutter - 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(() {});
},
);
},
),
),
]
)

Related

How can I only check one checkbox at time?

How can I select/check only one checkbox to be checked at time?
And below is my code
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Checkbox(
checkColor: Colors.white,
value: isChecked,
onChanged: (bool value) {
setState(() {
isChecked = value;
// ignore: unnecessary_statements
passData(certId);
});
},
),
],
)),
Option1 - Using a map to maintain the state
Create a map:
final Map<int, bool> _state = {};
then, check if the value for that index is true/false:
return ListView.builder(itemBuilder: (context, index) {
return CheckboxListTile(
value: _state[index] ?? false,
onChanged: (value) {
setState(() {
_state[index] = value!;
});
},
title: Text(_data[index].text),
);
});
Option 2 - using a model:
class CheckBoxModel {
bool isChecked = false;
String text = "";
CheckBoxModel({required this.isChecked, required this.text});
}
and then, generate a List of 30 widgets:
final _data = List.generate(
30, (index) => CheckBoxModel(isChecked: false, text: "Item $index"));
Now, use a ListView.builder and based on the index, to update the corresponding value:
class Testing extends StatefulWidget {
const Testing({Key? key}) : super(key: key);
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
#override
Widget build(BuildContext context) {
return ListView.builder(itemBuilder: (context, index) {
return CheckboxListTile(
value: _data[index].isChecked,
onChanged: (value) {
setState(() {
_data[index].isChecked = value!;
});
},
title: Text(_data[index].text),
);
});
}
}
See also
Expansion tile trailing icon updates all in list on interaction with one tile. How can I only change the icon for the expanded tile?

Avoid Updating Parent Variable when Updating Child

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

how can i create drowdownbutton with Riverpod Notifier?

I want to create DropDownButton with changenotifierProvider in Riverpod, but i can not write nicely my code. Please help me. I suppose, i just wrote 'watch' method, but i do not know how to read it. So, it should show the item which is chosen, and also it should be update with provider.Category.
My DropDownButton code is here:
Widget dropdownButton(BuildContext context,watch) {
String constantValue = "League Of Legends";
final postProvider = ChangeNotifierProvider<PostProvider>((ref) => PostProvider());
final provider = watch(postProvider);
return Consumer(
builder: (context, watch, _) {
return DropdownButton(
value: provider.postCategory ?? constantValue,
onChanged: (newValue) {
provider.postCategory = newValue;
},
items: <String>["League Of Legends", "Steam", "Csgo"]
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
onTap: () => value,
value: value ?? constantValue,
child: Text(value ?? constantValue),
);
}).toList());
},
);
}
Here my DropDownButton Image: ( when i choose any item of the list, it can not work properly. It always pick the first one (It picks "League of Legends").
I select steam but card categoty shows League of legends
final postProvider =
ChangeNotifierProvider<PostProvider>((ref) => PostProvider());
class PostProvider extends ChangeNotifier {
String _postCategory;
String get postCategory => _postCategory;
categoryOnChanged(String value) {
if (value.isEmpty) {
return _postCategory;
}
_postCategory = value;
print(_postCategory);
return notifyListeners();
}
}
class DropDownPage extends StatefulWidget {
#override
_DropDownPageState createState() => _DropDownPageState();
}
class _DropDownPageState extends State<DropDownPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Dropdown Riverpod issue"),
),
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Center(
child: _DropDownWidgetConsumer(),
),
),
);
}
}
class _DropDownWidgetConsumer extends ConsumerWidget {
#override
Widget build(BuildContext context, ScopedReader watch) {
final category = watch(postProvider).postCategory;
return DropdownButton(
hint: Text("Choose category"),
value: category,
items: <String>["League Of Legends", "Steam", "Csgo"]
.map((e) => DropdownMenuItem<String>(
onTap: () => e,
value: e ?? category,
child: Text(e ?? "$category"),
))
.toList(),
onChanged: (value) {
context.read(postProvider).categoryOnChanged(value);
},
);
}
}

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

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