Related DropdownButtons and raising onChange event - flutter

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

Related

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 to add an item to a list on a separate page in Flutter

I am new to Flutter and currently building an app to log spasms that happens due to spasticity. This is somewhat like a ToDo style app in structure. So I have a list in my home.dart file that a ListViewBuilder to display my Spasm objects. What I want to do is to create a Spasm object in recordSpasm.dart and add it to the list in home.dart. How do I do that? I´ll post my code here:
home.dart
import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:spasmlogger/classses/spasm.dart';
class Home extends StatefulWidget {
const Home({ Key? key }) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
List<Spasm> Spasms = [
Spasm("Extremely strong", "Upper body", "Cannot control left arm"),
Spasm("Extremely strong", "Upper body", "Cannot control left arm")
];
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SpasmLogger"),
actions: <Widget>[
IconButton(
icon: Icon(MdiIcons.help),
onPressed: () {
Navigator.pushNamed(context, '/about');
},
)
],
),
floatingActionButton: FloatingActionButton(
child: Icon(MdiIcons.plus),
onPressed: () {
Navigator.pushNamed(context, "/recordSpasm");
}
),
body: Padding(
padding: EdgeInsets.fromLTRB(16, 16, 16, 16),
child: ListView.builder(
itemCount: Spasms.length,
itemBuilder: (BuildContext context, int index){
return Card(
child: ListTile(
title: Text(Spasms[index].strength + " spasm detected in " + Spasms[index].bodyPart),
subtitle: Text(Spasms[index].comment)
)
);
},
)
),
);
}
}
recordSpasm.dart
import 'package:flutter/material.dart';
import 'package:spasmlogger/classses/spasm.dart';
class RecordSpasm extends StatefulWidget {
const RecordSpasm({ Key? key }) : super(key: key);
#override
_RecordSpasmState createState() => _RecordSpasmState();
}
class _RecordSpasmState extends State<RecordSpasm> {
#override
String Strength = "Regular strength";
List<String> Strengths = ["Regular strength", "Mildly stronger", "Severely Strong", "Extremely strong"];
String BodyPart = "Lower body";
List<String> BodyParts = ["Lower body", "Upper body", "Head"];
TextEditingController comment = new TextEditingController();
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Record spasm")
),
body:
Padding(
padding: EdgeInsets.all(16),
child: Form(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget> [
Text("Spasm strength"),
DropdownButton(
value: Strength,
items: Strengths.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
setState(() {
Strength = value!;
});
},
),
Text("Part of body"),
DropdownButton(
value: BodyPart,
items: BodyParts.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? value) {
setState(() {
BodyPart = value!;
});
},
),
Text("Comments"),
TextFormField(
maxLines: 5,
controller: comment,
),
ElevatedButton(
onPressed: () {
// Add object to the list in home.dart
print(Strength);
print(BodyPart);
print(comment.text);
},
child: Text("Record spasm")
)
]
)
)
)
);
}
}
Navigator.push returns a Future when the pushed page is popped. So you just need to add the Spasm object in the recordSpasm.dart:
ElevatedButton(
onPressed: () {
Navigator.pop(context,Spasm(Strength, BodyPart, comment.text));
},
child: Text("Record spasm")
)
and retrieve the object and "refresh" the page in the home.dart
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.pushNamed(context, "/recordSpasm").then((value) {
if (value != null && value is Spasm) {
setState(() {
// if this doesn't work add the value to the list then call setState
Spasms.add(value);
});
}
});
}),
Just a tip, in dart variable names should be lowercase (eg: String strength) :)
first you need to access to you list. you have 2 ways to do that
make the list static like this static List yourListName = [];
for the other way you don't need to do anything right now
so if you use way 1 then you can add something to your list like this:
import 'thePathFromTheFileThatHaveTheListIn';
...
// here we add something to your list without building the whole class again
TheClassWhereTheListWasIn.yourListName.add(...);
and if you use way 2 then you can add something to your list like this:
import 'thePathFromTheFielThatHaveTheListIn';
...
// here we add something to your list but here we build the whole class again
// and then add something to your list
TheClassWhereTheListIsIn().yourListName.add(...);

How do I change it such that the change is instant?

import 'package:flutter/material.dart';
class RandomPage extends StatefulWidget {
#override
_RandomScreenState createState() => _RandomScreenState();
}
class _RandomScreenState extends State {
String _selectedValue = 'Select';
bool appear = false;
Widget FirstDropDownButton() {
return Container(
child: DropdownButton <String> (
value: _selectedValue,
icon: Icon(Icons.arrow_drop_down),
underline: Container(
height: 1.5,
color: Colors.blueGrey,
),
onChanged: (String newValue) {
setState(() {
_selectedValue = newValue;
});},
items: <String> ['Select','One'].map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);}).toList(),
)
);
}
Widget FirstFlatButton() {
return FlatButton(
child: Text("Next"),
onPressed: () {
if (_selectedValue == 'Select') {
print("Cannot be NULL");
appear = false;
}
else if (_selectedValue == 'One')
appear = true;
});
}
Widget getWidget() {
if (appear == true) {
return Column(
children: <Widget>[
Text("Hello")
],
);
}
else
return Column(
children: <Widget>[
Text("Bye")
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget> [
FirstDropDownButton(),
FirstFlatButton(),
getWidget(),
],
)
);
}
}
Hi this is a simpler version of the code of mine.
If I choose 'one' from the dropdownbutton the hello text should appear but it doesnt, only when I go ahead and change the value of the dropdownbutton again then will it reflect the change.
How do I make it such that when I select 'one' such that text hello appears immediately and when i select 'select' the text bye appears and hello disappears at the click of the flatbutton without toggling the options again for the change to be reflected?
i have taken the liberty to modify your code a bit, and made it more general.
For live demo check: codepen
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: RandomPage(),
),
);
}
class RandomPage extends StatefulWidget {
#override
_RandomScreenState createState() => _RandomScreenState();
}
class _RandomScreenState extends State {
int _selectedValue = 0;
bool appear = false;
List<String> values = ['Select', 'One', 'Two', 'Three'];
Widget FirstDropDownButton() {
return Container(
child: DropdownButton<String>(
value: values[_selectedValue],
icon: Icon(Icons.arrow_drop_down),
underline: Container(
height: 1.5,
color: Colors.blueGrey,
),
onChanged: (String newValue) {
setState(() {
_selectedValue = values.indexWhere((element) => element == newValue);
});
changeFlag();
},
items: values.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
));
}
void changeFlag() {
if (values[_selectedValue] == 'One') {
setState(() {
appear = true;
});
} else {
setState(() {
appear = false;
});
}
}
Widget FirstFlatButton() {
return FlatButton(
child: Text("Next"),
onPressed: () {
setState(() {
_selectedValue = (_selectedValue + 1) % (values.length);
});
changeFlag();
});
}
Widget getWidget() {
if (appear == true) {
return Column(
children: <Widget>[Text("Hello")],
);
} else
return Column(
children: <Widget>[Text("Bye")],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("StackOverflow")),
body: Column(
children: <Widget>[
FirstDropDownButton(),
FirstFlatButton(),
getWidget(),
],
));
}
}
Update: Now that I re-read the question,
I figured that you wanted to show either the value Hello or Bye depending upon the value on dropdown, when the user clicked Next button,
but i am sure you got the idea whatsoever,
just remove the setState on the flatbutton and changeFlag in the dropdown and you'll be fine.

Is it possible in a dropdown button the displayed value to not be in the menu?

I tried to do it but I get an error. I was wondering if there is some kind of loophole.
Also, is there a way for the menu to open below the displayed value?
Here is the code:
import 'package:flutter/material.dart';
import './grile_view.dart';
class Grile extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return GrileState();
}
}
class GrileState extends State<Grile> {
var _bGrile = ['bgrila 1', 'bgrila 2'];
var _bio = 'bgrila 1';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.only(top: 15.0, left: 10.0, right: 10.0),
child: ListView(
children: <Widget>[
// DropdownButton
DropdownButton<String>(
items: _bGrile.map((String dropDownStringItem) {
return DropdownMenuItem<String>(
value: dropDownStringItem,
child: Text(dropDownStringItem)
);
}).toList(),
onChanged: (value){
Navigator.push(context, MaterialPageRoute(builder: (context) =>
GView()));
},
value: _bio
),
// DropdownButton End
]
)
)
);
} // build
} // GrileState
I am new at programming so excuse anything dumb I wrote in the code.
The DropdownButton class contains a very handy hint property. According to the DropdownButton documentation:
If [value] is null and [hint] is non-null, the [hint] widget is
displayed as a placeholder for the dropdown button's value.
What does this mean for you?
All you have to do is add a hint widget to your DropdownButton and then remove the assignment from your dynamic value.
Here's a brief example of a DropdownButton with a hint property:
class DropDownPage extends StatefulWidget {
#override
_DropDownPageState createState() => _DropDownPageState();
}
class _DropDownPageState extends State<DropDownPage> {
int dropdownValue; // Notice how this isn't assigned a value.
#override
Widget build(BuildContext context) {
return Center(
child: DropdownButton(
// Here's the hint property. I used a Text widget,
// but you can use any widget you like
hint: Text("Pick a Widget"),
value: dropdownValue,
items: [
DropdownMenuItem(child: Text("FlatButton"), value: 0),
DropdownMenuItem(child: Text("Container"), value: 1),
DropdownMenuItem(child: Text("Scaffold"), value: 2),
DropdownMenuItem(child: Text("GestureDetector"), value: 3),
],
onChanged: (value) {
setState(() {
dropdownValue = value;
});
},
));
}
}
This is the result:
So in your code, here's what you have to do in your own code.
Edit this: var _bio = 'bgrila 1';
And change it to this: var _bio;
Then add a hint property to your DropdownButton:
DropdownButton<String>(
hint: Text("Pick a *****"), // Replace with your hint widget
items: _bGrile.map((String dropDownStringItem) {
return DropdownMenuItem<String>(
value: dropDownStringItem,
child: Text(dropDownStringItem)
);
}).toList(),
onChanged: (value){
Navigator.push(context, MaterialPageRoute(builder: (context) =>
GView()));
},
value: _bio
),
So that your entire code looks like this:
class Grile extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return GrileState();
}
}
class GrileState extends State<Grile> {
var _bGrile = ['bgrila 1', 'bgrila 2'];
var _bio;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.only(top: 15.0, left: 10.0, right: 10.0),
child: ListView(
children: <Widget>[
// DropdownButton
DropdownButton<String>(
hint: Text("Pick a *******"),
items: _bGrile.map((String dropDownStringItem) {
return DropdownMenuItem<String>(
value: dropDownStringItem,
child: Text(dropDownStringItem)
);
}).toList(),
onChanged: (value){
Navigator.push(context, MaterialPageRoute(builder: (context) =>
GView()));
},
value: _bio
),
// DropdownButton End
]
)
)
);
} // build
} // GrileState

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