ValueListenableBuilder not rebuilding a CheckboxListTile Flutter - flutter

I am trying to run a method that updates the value of a CheckboxListTile as I pass the new values at the end at Globals.data.updateFilterSelection(newFilters); the method runs fine and it do updates (tested it with prints), but the ValueListenableBuilder is not rebuilding the CheckboxListTile when I change its value.
I have three CheckboxListTile with the same code but different logic all of them listening to Globals.data.filterSelection,
What I am missing?
Hi here is the code:
ValueListenableBuilder<Map>(
valueListenable: Globals.data.filterSelection,
builder: (context, value, _) {
return CheckboxListTile(
activeColor: Theme.of(context).indicatorColor,
value: value['all_neighbors'],
secondary: Icon(
Icons.people,
color: Theme.of(context).indicatorColor,
),
title: Text(
'All Neighbors',
style: Theme.of(context).textTheme.bodyText1,
),
onChanged: (newValue) {
if (newValue) {
newFilters
..update('inactive', (value) => false)
..update('active', (value) => false)
..update('all_neighbors', (value) => true);
} else {
newFilters
..update('inactive', (value) => true)
..update('all_neighbors', (value) => false);
}
Globals.data.updateFilterSelection(newFilters);
},
);
}),
Here is also my ValueNotifier and the method called:
ValueNotifier<Map> filterSelection = ValueNotifier<Map>({
'inactive': true,
'active': false,
'all_neighbors': false,
});
/// Changes the filter selection
void updateFilterSelection(Map newFilter) {
filterSelection.value = newFilter;
print(filterSelection.value);
}
Thanks in advance

I found out that the ValueListenableBuilder does not rebuild on <Map> types it needs to be a single value that can be compared with the == operator, as described.
When value is replaced with something that is not equal to the old value as evaluated by the equality operator ==, this class notifies its listeners.

Related

Flutter: disable DropdownButtonFormField option

I have this widget:
DropdownButtonFormField<String>(
hint: Text(translate('payments.select_frequency')),
value: frequency,
items: frequencies.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(
translate("expense.$value"),
style: TextStyle(
color: disabledFrequencies.contains(value) ? Colors.grey : null,
),
),
);
}).toList(),
onChanged: (value) async {
if (!disabledFrequencies.contains(value)) {
setState(() {
frequency = value;
});
}
},
validator: (value) {
if (value == null) {
return translate('fill_field');
}
return null;
},
);
This generates something like this:
Here I should be able to just click the first option but I can select any of them. I opened this issue a while ago in Flutter repo and they mentioned it's not an issue.
What's the option then?
There is enable property on DropdownMenuItem control the tap accessibility.
return DropdownMenuItem<String>(
value: value,
enabled: !disabledFrequencies.contains(value), //this
onTap: () {
Whether or not a user can select this menu item.
Defaults to true.

DropdownButtonFormField reset value to initial

How do I reset or select the first value from DropdownButtonFormField?
The answer from here How to reset value in Flutter DropdownButtonFormField is outdated and not suitable for the newer flutter version.
DropdownButtonFormField:
final etSkillScore1Key = GlobalKey<FormState>();
...
DropdownButtonFormField(
key: etSkillScore1Key,
decoration: const InputDecoration(labelText: 'Select value'),
onChanged: (val) async {
setState(() {
etSkillScore1 = val as int;
});
FocusScope.of(context).requestFocus(FocusNode());
},
value: etSkillScore1,
items: priorities2.map((db.Priority priorities) {
return DropdownMenuItem(
child: Text(priorities.name),
value: priorities.value,
);
}).toList(),
),
Button for resetting the value:
IconButton(
onPressed: () {
//ERORR: Null check operator used on a null value
etSkillScore1Key.currentState!.reset();
},
icon: Icon(
Icons.close,
))
Error:
======== Exception caught by gesture
The following _CastError was thrown while handling a gesture:
Null check operator used on a null value
If I use
etSkillScore1Key.currentState?.reset();
then nothing happens
First of all you are not using the correct key it should be a GlobalKey<FormFieldState>(), but even then the reset() would not work.
The reason for this is because of the implementation of DropdownButtonFormField:
DropdownButtonFormField({
// ...
T? value,
// ...
}) : super(
// ...
initialValue: value,
// ...
);
(source: Flutter Documentation)
As you can see the value property of DropdownButtonFormField is what defines the initialValue of the FormField so when you are rebuilding your form field and changing the value of etSkillScore1 it is also changing the value of your DropdownButtonFormField.initialValue.
Solution 1
If you want your reset() to work then you can remove the property value of DropdownButtonFormField so the initialValue won't change with etSkillScore1.
DropdownButtonFormField<int>(
key: etSkillScore1Key,
decoration: const InputDecoration(labelText: 'Select value'),
onChanged: (val) {
etSkillScore1 = val;
FocusScope.of(context).requestFocus(FocusNode());
},
// value: etSkillScore1,
items: priorities2.map((db.Priority priorities) {
return DropdownMenuItem<int>(
child: Text(priorities.name),
value: priorities.value,
);
}).toList(),
)
Try the full example on DartPad
Solution 2
Do not set the value property with etSkillScore1, if you want to initialize your widget with an initial value then do it like this:
DropdownButtonFormField<int>(
key: etSkillScore1Key,
decoration: const InputDecoration(labelText: 'Select value'),
onChanged: (val) {
etSkillScore1 = val;
FocusScope.of(context).requestFocus(FocusNode());
},
value: 1, // Use a fixed value that won't change
items: priorities2.map((db.Priority priorities) {
return DropdownMenuItem<int>(
child: Text(priorities.name),
value: priorities.value,
);
}).toList(),
)
As your value will be fixed then when DropdownButtonFormField is rebuild it will keep 1 as its initial value.

DropdownButtonFormField not getting rebuilt

I'm trying to update the selected value programmatically.
I've used various method, including Consumer etc., and have made sure that the value is updated and the widget is called when the value changes, however, DropdownButtonFormField never got rebuilt with the latest value.
Currently I'm wrapping the DropdownButtonFormField in a StreamBuilder, which supposedly, should get rebuild whenever there's a new event sent through the stream. This is my code:
Declaration
final StreamController<String> _raceStreamController = new StreamController<String>();
DropdownButtonFormField
return
StreamBuilder<String>(
stream: _raceStreamController.stream,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return new DropdownButtonFormField<String>(
value: snapshot.data,
hint: new Text(hint,
textAlign: TextAlign.center),
isExpanded: true,
items: items.map((String value) {
return new DropdownMenuItem<String>(
child: new Text(value),
value: value
);
}).toList(),
validator: (value) => value == null ? 'field required' : null,
onChanged: (value) {} // no use for now,
);
});
Push data
onFocusChange: (focus) async {
if (!focus) {
try{
await userBloc.searchUser(controller.text.toUpperCase());
_raceStreamController.sink.add(userBloc.user.race);
} catch(e) {
if (e.toString() == ERROR_UNAUTHORISED)
navigateToRoot(context);
}
}
}
I've tried to remove as much redundant code as possible.
Thank you.
In Flutter version 1.17.2, that DropdownButtonFormField bug was fixed, so be sure to upgrade.
Github issue: https://github.com/flutter/flutter/issues/56898
Fixed in version 1.17.2: https://github.com/flutter/flutter/wiki/Hotfixes-to-the-Stable-Channel#1172-may-28-2020

What is the correct way to use PopUpMenuItem onTap/onPessed in Flutter?

What is the correct way to call a function in Flutter when PopupMenuItem is tapped? Because inherently the widget does not have a onTapped property. The only thing close to it is setting the value of the PopupMenuItem widget. So the way I approached it is to set the state in the onSelected parameter in the PopupMenuButton. I did not find anyone talking about this situation on the internet so I thought it is worth to get opinions from other. Is this the proper way to do this?
I have tried to use a FlatButton as the child of the PopupMenuButton, but it did not work. It seemed that the application did not record the onTapped function of the FlatButton.
PopupMenuButton<Choice>(
onSelected: (Choice result) {
setState(() {
_selection = result;
if (_selection == Choice.SIGN_OUT) {
_signOut();
print('[home.dart] _signOut()');
}
});
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<Choice>>[
PopupMenuItem(
child: Text(
'Sign Out',
style: TextStyle(color: Colors.black),
),
value: Choice.SIGN_OUT,
),
],
),
The expected result is to call a function when a PopupMenuItem is tapped.
PopUpMenuItem invocation can be done in two ways now.
We can do it with the onTap method now.
PopupMenuItem(
value: 'share',
onTap: () {
print("hello world");
},
child: Text('Share'),
),
Or it can be done old school like the OP has mentioned.
onSelected: (value) {
switch (value) {
case ('subscribe'):
print('Subscribing');
break;
case ('edit'):
print('editing');
break;
case ('share'):
print('sharing');
break;
default:
return;
}
},

control & disable a dropdown button in flutter?

I wanted to control a drop-down button and make it unclickable using a button.
Is there any way to make it disable. Basically not allowing it able to change.
new DropdownButton(
value: animalName,
items: animals.map(
(String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text('$value'),
);
},
).toList(),
onChanged: (value) {
setState(() {
animalName = value;
});
},
),
So this is the code I currently use on the drop-down button, but i cant disabled it.
Found this in the DropdownButton docs:
If items or onChanged is null, the button will be disabled, the down arrow will be grayed out, and the disabledHint will be shown (if provided)
DropdownButton(
onChanged: null,
items: [...],
)
This isn't what you want to hear, but I don't think there's currently an easy way. I experimented with simply removing all the items and that causes a nice little crash. Maybe worth raising an issue with the flutter people on github...
There is an alternative that may be good enough for you for now. If you wrap your DropdownButton in an IgnorePointer, when you want it to be disabled you can change IgnorePointer's ignoring property to true.
That way if the user taps on it, it won't do anything.
But you'll probably want to indicate to the user somehow that it's disabled as well, something like setting the hint text (as it's grey).
child: new IgnorePointer(
ignoring: true,
child: new DropdownButton(
hint: new Text("disabled"),
items: ["asdf", "wehee", "asdf2", "qwer"].map(
(String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text('$value'),
);
},
).toList(),
onChanged: (value) {},
),
You can make DropdownButtonFormField or DropdownButton disabled if set onChanged to null, and if you want that dropdown still shows selected value you must set disabledHint. For example:
DropdownButtonFormField<String>(
disabledHint: Text(_selectedItem),
value: _selectedItem,
onChanged: enabled ? (value) => setState(() => _selectedItem = value) : null,
items: items.map<DropdownMenuItem<String>>((item) {
return DropdownMenuItem(
value: item,
child: Text(item),
);
}).toList(),
)
Just wrap it with IgnorePointer widget to make DropdownButton disable
IgnorePointer(
ignoring: enabled,
child: new DropdownButton(
value: animalName,
items: animals.map(
(String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text('$value'),
);
},
).toList(),
onChanged: (value) {
setState(() {
animalName = value;
});
},
),
);
If items or onChanged is null, the button will be disabled, the down
arrow will be grayed out, and the disabledHint will be shown (if
provided)
So something like this should work:
DropdownButton<String>(
...
onChanged: this.enabled ? (id) => setState(() => this.id = id) : null,
)
okay, i found a trick that satisfied me
i wanted it hide/show the DropdownButton depending on CheckboxListTile
in StatefulWidget Class
first create a function ex:
_buildDropDown(bool enable) {
if (enable) {
return DropdownButton<String>(
hint: Text("Hint"),
items: <String>[
'item 1',
'item 2',
'item 3',
].map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
onChanged: (value) {},
);
} else { // Just Divider with zero Height xD
return Divider(color: Colors.white, height: 0.0);
}
}
and now in build
bool enable = true;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
CheckboxListTile(
title: const Text('Switcher'),
selected: true,
value: enable,
onChanged: (bool value) {
setState(() {
enable = value;
});
},
),
_buildDropDown(enable),
],
);
}
now every time you change enable it will display and hide the DropdownButton
DropdownButtonFormField(
onChange: isDisable ? null : (str){
},
disabledHint: isDisable ? null : Text('Your hint text'),
...
)
For disable
onChange: null
For disable Caption
disabledHint: Text('Your hint text')
//add widget'AbsorbPointer' true-disable,false-enable
// isEditable = ture
AbsorbPointer(
absorbing: isEditable
DropdownButton(
onChanged: null,
items: [...],
)
)
Simple:
decoration:InputDecoration(enabled: false),