Flutter | Pass value from child to parent - flutter

I need a DropdownButton with items depending on another DropdownButton. Sounds a bit confusing but it isnt. Here is my code with comments at the important parts in order to understand my intention.
Parent
class Parent extends StatefulWidget {
const Parent({ Key? key }) : super(key: key);
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: SizedBox(
width: 500,
height: 500,
child: Column(
children: const [
// Main
DropDownWidget(collection: "MainCollection",),
// Depending
DropDownWidget(collection: ""), // Collection should equals value from Main DropDownWidget
],
),
),
);
}
}
Child
class DropDownWidget extends StatefulWidget {
final String collection;
const DropDownWidget({Key? key, required this.collection}) : super(key: key);
#override
State<DropDownWidget> createState() => _DropDownWidgetState();
}
class _DropDownWidgetState extends State<DropDownWidget> {
var selectedItem;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection(widget.collection)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.hasError) {
return const CircularProgressIndicator();
} else {
var length = snapshot.data?.docs.length;
List<DropdownMenuItem<String>> items = [];
for (int i = 0; i < length!; i++) {
DocumentSnapshot snap = snapshot.data!.docs[i];
items.add(DropdownMenuItem(
child: Text(snap.id),
value: snap.id,
));
}
return DropdownButtonFormField<String>(
onChanged: (value) {
setState(() {
selectedItem = value;
// ********************
// PASS value TO PARENT
// ********************
});
},
value: selectedItem,
items: items);
}
});
}
}
When the Main DropdownButton changes its value, it should pass that to my parent in order to change the focused collection of my depending DropdownButton. I already solved that problem by throwing all the code in one class buts that not the way I want to go.
So maybe you can help me out :)
Thanks

Create an argument ValueChanged<String> onSelectItem in your child. Call the method when the value changes.
Then in your parent, you provide a function that needs to be called when the value changes in your child.

Related

After I pick pictures or take a picture, when selected pictures go out side of screen, pictures keep disappearing

I made vertical scroll list of question cards with selected pictures at the end of list using image_picker with scroll_to_index. I can see pictures right after I picked the pictures but when I scroll up the list, the selected pictures keep disappearing. It seems like the imageList in child widget does not maintain changed values of list. here is my short code.
parent.dart
class Parent extends StatefulWidget {
const Parent({Key? key}) : super(key: key);
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
#override
Widget build(BuildContext context) {
return Scaffold(body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
padding: EdgeInsets.all(8),
itemCount: _list.length + 1,
itemBuilder: ((context, index) {
if (index != _list.length) {
return AutoScrollTag( //scroll_to_index
key: ValueKey(index), //scroll_to_index
controller: scrollController, //scroll_to_index
index: index,
highlightColor: Colors.blue.withOpacity(0.3),
child: Container(
...
}else(){
return Child();
}
));
}
}
child.dart
class Child extends StatefulWidget {
const Child({Key? key}) : super(key: key);
#override
State<Child> createState() => _ChildState();
}
class _ChildState extends State<Child> {
List<String> imageList = [];
pickMultiImages() async {
final List<PickedFile>? selectedImages =
await ImagePicker.platform.pickMultiImage();
if (selectedImages != null) {
imageList.addAll(selectedImages);
}
setState(() {});
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Wrap(
children: [
if (imageList.length > 0)
for (int i = 0; i < imageList.length; i++)
Stack(children: [
Column(
...
,
IconButton(onPressed: (() => {
pickMultiImages()
}), icon: Icon(Icons.add))
],
);
this code does not work.
but When I added List<PickedFile> imageList = [] and pass to child widget as parameter in Parent Widget and in Child Widget,
class Child extends StatefulWidget {
List<PickedFile> imageList = []; // added
Child({
Key? key,
required this.imageList, // added
}) : super(key: key);
...
pickMultiImages() async {
...
widget.imageList.addAll(selectedImages); // imageList.addAll -> widget.imageList.addAll
...
setState(() {});
}
this one worked. Should I always declare the changing child state in parent widget? or this is just special case that i have to do like this?

How to make this dropdownbutton widget reusable?

I have the dropdownbutton widget below, with a futurebuilder that reads a list from a google spreadsheet. The widget works good as intended.
I want to reuse this widget within the app, and simply pass a different Future
As example, I want to call the same widget, but pass different lists
mydropdownbutton ( futureList1 ),
mydropdownbutton ( futureList2 ),
mydropdownbutton ( futureList3 ),
//========================================================
// WIDGET: FUTURE DROPDOWN MENU + FUTURE LIST
//--------------------------------------------------------
class dropDownButtonWidget extends StatefulWidget {
const dropDownButtonWidget({ Key? key,}) : super(key: key);
#override
State<dropDownButtonWidget> createState() => _dropDownButtonWidgetState();
}
// --------------------------------------------------------
class _dropDownButtonWidgetState extends State<dropDownButtonWidget> {
#override
Widget build(BuildContext context) {
return Center(
child: FutureBuilder(future: futureList(),
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if(snapshot.hasData){ List? futureDataList = snapshot.data;
futureDataList ??= ['Loading']; //if snapshot is null
return buildDropdownButton(dropdownList: futureDataList );
}else if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}else {
return Center(child: CircularProgressIndicator());
}
}
)
);
}
//----------------------------------
// DROPDOWNBUTTON EXTRACTED METHOD
DropdownButton<Object> buildDropdownButton({required List dropdownList}) {
String defaultValue = dropdownList.first; //DEFAULT SELECTED ITEM
return DropdownButton(
value: defaultValue,
onChanged: (value) => setState(() => defaultValue = value.toString()),
items: dropdownList.map((items) {
return DropdownMenuItem(value: items, child: Text(items));
}).toList(),
);
}
//----------------------------------
}
//=============================================
//=============================================
//FUTURE LIST FOR THE DROPDOWN MENU
//=============================================
Future<List> futureList() async {
var items = await ScheduleInfo.mx_schedule_WEEKDAYS_as_List(debugONOFF: 1);
return items;}
//=============================================
How can I make this widget modular and reusable?
you can send the future to this widget's constructor. first you declare a variable and set it to constructor. then you can reference that variable in the state of that class by widget.variable keyword. something like this
class dropDownButtonWidget extends StatefulWidget {
final variable;
dropDownButtonWidget(this.variable);
const dropDownButtonWidget({ Key? key,}) : super(key: key);
#override
State<dropDownButtonWidget> createState() => _dropDownButtonWidgetState();
}
class _dropDownButtonWidgetState extends State<dropDownButtonWidget> {
widget.variable //whatever you want to do with it
}
Community... after few hours of reading and testing, I found the solution to my own question. I am posting the solution for anyone else needing it.
Probably, my code can be improved, I welcome suggestions.
My question above has a working code for a dropdown button widget (fully working as today), using a Future
Below, my own answer, the same widget transformed into a reusable modular widget.
( This works only with future lists (async) )
Simple SCREEN WIDGET (with nested dropdownbutton widgets):
class Screen01 extends StatefulWidget {
const Screen01({
Key? key,
}) : super(key: key);
#override
State<Screen01> createState() => _Screen01State();
}
class _Screen01State extends State<Screen01> {
#override
Widget build(BuildContext context) {
return Center(child:
Flex(
direction: Axis.horizontal,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children:
[ // each time you call the widget, you can provide a different future list
dropDownButtonWidgetModular(futureList: dropdownFutureList()),
SizedBox(width: 50,),
dropDownButtonWidgetModular(futureList: anotherFutureList())
]
),
);
}
}
DROPDOWNBUTTON WIDGET:
//========================================================
// WIDGET: FUTURE DROPDOWN MENU + FUTURE LIST
//--------------------------------------------------------
class dropDownButtonWidgetModular extends StatefulWidget {
final Future<List> futureList; // ===> ADDED FUTURE LIST
const dropDownButtonWidgetModular({ Key? key, required this.futureList}) : super(key: key); // ===> ADDED REQUIRED FUTURE LIST
#override
State<dropDownButtonWidgetModular> createState() => _dropDownButtonWidgetModularState(chosenFutureList: futureList);
}
// --------------------------------------------------------
class _dropDownButtonWidgetModularState extends State<dropDownButtonWidgetModular> {
Future<List> chosenFutureList; // ===> ADDED FUTURE LIST
String? defaultValue;
_dropDownButtonWidgetModularState({required this.chosenFutureList}); // ===> ADDED REQUIRED FUTURE LIST
#override
Widget build(BuildContext context) {
return Center(child:
FutureBuilder(future: chosenFutureList, // ===> ADDED FUTURE LIST
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if(snapshot.hasData){ List? futureDataList = snapshot.data;
futureDataList ??= ['Loading']; //if snapshot is null
return buildDropdownButton(dropdownList: futureDataList );
}else if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}else {
return Center(child: CircularProgressIndicator());
}
}
)
);
}
//----------------------------------
// DROPDOWNBUTTON EXTRACTED METHOD
DropdownButton<Object> buildDropdownButton({required List dropdownList}) {
defaultValue ??= dropdownList.first; //DEFAULT SELECTED ITEM
return DropdownButton(
value: defaultValue,
onChanged: (value) => setState(() => defaultValue = value.toString()),
items: dropdownList.map((items) {
return DropdownMenuItem(value: items, child: Text(items));
}).toList(),
);
}
//----------------------------------
}
//=============================================
I commented some of the changes made from my original question

Best practice to update value from another class

I am new to flutter, so please excuse my experience.
I have 2 classes, both stateful widgets.
One class contains the tiles for a listview.
Each tile class has a checkbox with a state bool for alternating true or false.
The other class (main) contains the body for creating the listview.
What I'd like to do is retrieve the value for the checkbox in the main class, and then update a counter for how many checkbboxes from the listview tiles have been checked, once a checkbox value is updated. I am wondering what the best practices are for doing this.
Tile class
class ListTile extends StatefulWidget {
#override
_ListTileState createState() => _ListTileState();
}
class _ListTileState extends State<ListTile> {
#override
Widget build(BuildContext context) {
bool selected = false;
return Container(
child: Row(
children: [Checkbox(value: selected, onChanged: (v) {
// Do something here
})],
),
);
}
}
Main Class
class OtherClass extends StatefulWidget {
#override
_OtherClassState createState() => _OtherClassState();
}
class _OtherClassState extends State<OtherClass> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text("Checkbox selected count <count here>"),
ListView.builder(itemBuilder: (context, index) {
// Do something to get the selected checkbox count from the listview
return ListTile();
}),
],
),
);
}
}
Hope this is you are waiting for
class OtherClass extends StatefulWidget {
#override
_OtherClassState createState() => _OtherClassState();
}
class _OtherClassState extends State<OtherClass> {
bool selected = false;
#override
void initState() {
super.initState();
}
var items = [
Animal("1", "Buffalo", false),
Animal("2", "Cow", false),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("title")),
body: Container(
child: ListView.builder(
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (ctx, i) {
return Row(
children: [
Text(items[i].name),
ListTile(
id: items[i].id,
index: i,
)
],
);
}),
));
}
}
ListTileClass
class ListTile extends StatefulWidget {
final String? id;
final int? index;
final bool? isSelected;
const ListTile ({Key? key, this.id, this.index, this.isSelected})
: super(key: key);
#override
_ListTileState createState() => _ListTileState();
}
class _ListTileState extends State<ListTile> {
bool? selected = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
width: 20,
child: Checkbox(
value: selected,
onChanged: (bool? value) {
setState(() {
selected = value;
});
}));
}
}
I'd recommend using a design pattern such as BLoC or using the Provider package. I personally use the Provider Package. There are plenty of tutorials on youtube which can help get you started.

Flutter: Unable to update listview builder with correct values

I have created a filter to filter UI between two dates. My problem is that the UI does not accurately reflect this, instead I notice that the former filteredList is used BUT the build .length method calculates accurately... resulting in displaying the wrong items from the list.
See code below, simplified for convenience:
homepage.dart
class _MyHomePageState extends State<HomePage> {
void _refreshFilter() {
setState(() => filteredList = myList.where((item) {
DateTime date = DateTime(item.date.year, item.date.month, item.date.day);
return date != null && (dateFilterFrom != null ? date.difference(dateFilterFrom).inDays >= 0: true) &&
(dateFilterTo != null ? dateFilterTo.difference(date).inDays >= 0: true);})
.toList());
showSnackBar(context, 'Filter refreshed!');
#override
Widget build(BuildContext context) {
return Display(filteredList);
}
display.dart
class Display extends StatefulWidget {
const Display(this.filteredList, {Key key})
: super(key: key);
final List filteredList;
#override
_DisplayState createState() => _DisplayState();
}
class _DisplayState extends State<Display> {
#override
Widget build(BuildContext context) {
return Flexible(
child: Padding(
padding: EdgeInsets.all(20.0),
child: ListView.builder(
shrinkWrap: true,
itemCount: filteredList.length,
itemBuilder: (context, index) {
return MyItem(index),
));
}
});
}
myitem.dart
class MyItem extends StatefulWidget {
MyItem(this.index, {Key key}) : super(key: key);
final int index;
#override
_MyItemState createState() => _MyItemState();
}
class _MyItemState extends State<MyItem> {
DateTime dateTime;
#override
Widget build(BuildContext context) {
return OutlineButton(
borderSide: BorderSide(color: Colors.blue),
onPressed: () {
DatePicker.showDateTimePicker(context, showTitleActions: true,
onChanged: (value) {
print('change $value in time zone ' +
value.timeZoneOffset.inHours.toString());
}, onConfirm: (value) {
print('confirm $value');
setState(() {
dateTime = value;
filteredList[widget.index].date = value;
});
}, currentTime: dateTime != null ? dateTime : DateTime.now());
},
child: Text(
dateTime != null
? dateTime.toString().substring(0, 16)
: "select date",
style: TextStyle(color: Colors.blue),
));
}
}
filteredList is in globals.dart
Any tips strongly appreciated, or simply a method to have a parent widget with function that will rebuild the builder from the child widget...
NB: When I debug filteredList print the accurate items, so this is definitely a UI/state issue
In your Display widget class, add required List parameter, something like this:
class Display extends Stateless {
final List filteredList;
const Display({required this.filteredList});
Now, in your homepage where you callDisplay(), it'll ask you for the list,
So, it will look like this now:
Widget build(BuildContext context) {
return Display(filteredList: filteredList);
This way, you can convert your listviewBuilder to be in a stateless widget, where you get better performance. And from your homepage, whenever you press the filter function, it'll rebuild your Display widget, with actual filtered list.

Passing data from one widget to another

I have a list of choice widget and want to pass the selected choice to another widget.
Here is the list of choice widget
class ChoiceChipWidget extends StatefulWidget {
final List<String> reportList;
final Function(String item) onChoiceSelected;
ChoiceChipWidget(this.reportList, this.onChoiceSelected);
#override
_ChoiceChipWidgetState createState() => new _ChoiceChipWidgetState();
}
class _ChoiceChipWidgetState extends State<ChoiceChipWidget> {
String selectedChoice = "";
_buildChoiceList() {
List<Widget> choices = List();
widget.reportList.forEach((item) {
choices.add(Container(
child: ChoiceChip(
label: Text(item),
selected: selectedChoice == item,
onSelected: (selected) {
setState(() {
selectedChoice = item;
widget.onChoiceSelected(item);
print(selectedChoice); //DATA THAT NEEDS TO BE PASSED
});
},
),
));
});
return choices;
}
#override
Widget build(BuildContext context) {
return Wrap(
children: _buildChoiceList(),
);
}
}
I need to pass it to this widget
class AddCashPage extends StatefulWidget {
#override
_AddCashPageState createState() => _AddCashPageState();
}
class _AddCashPageState extends State<AddCashPage> {
void createTodo() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
String repetition = //DATA NEEDS TO GO HERE;
final addCash = AddCash(repetition);
setState(() {
id = addCash.id;
});
}
}
#override
Widget build(BuildContext context) {
return Container(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
ChoiceChipWidget(chipList, (item) {
selectedItem = item;
}),
],
),
RaisedButton(
child: Text("Update Cash Flow"),
onPressed: createTodo,
),
],
),
),
);
}
}
I tried making a constructor inside AddCashPage
like this
class AddCashPage extends StatefulWidget {
final ChoiceChipWidget choiceChipWidget;
AddCashPage({Key key, #required this.choiceChipWidget}) : super(key: key);
#override
_AddCashPageState createState() => _AddCashPageState();
}
I think you just missed to call setState() in here:
ChoiceChipWidget(chipList, (item) {
selectedItem = item;
}),
Like this:
ChoiceChipWidget(chipList, (item) {
setState(() => selectedItem = item);
}),
Then you could do this:
AddCash(selectedItem)
Make sure to declare the selectedItem variable inside _AddCashPageState, I don't see it on your code.
Your choice widget passes the data to the AddCashPage via the constructor you created, but you're missing something. You need to pass the data that AddCashPage has to its state (_AddCashState) so that you can use it there. Basically, you need to create one more constructor.
class AddCashPage extends StatefulWidget {
final ChoiceChipWidget choiceChipWidget;
AddCashPage({Key key, #required this.choiceChipWidget}) : super(key: key);
#override
_AddCashPageState createState() => _AddCashPageState(choiceChipWidget: choiceChipWidget);
}
And in _AddCashPageState:
class _AddCashPageState extends State<AddCashPage> {
final choiceChipWidget;
_AddCashPageState({Key key, #required this.choiceChipWidget});
}
To use your passed data inside _AddCashPageState class you can use widget property of the corresponding state of the related Stateful class.
For Ex : To use choice chip widget in your class you can use it like widget.ChoiceChipWidget
Any properties/methods provided in AddCashPage class can be accessed in its State class _AddCashPageState() using widget.ChoiceChipWidget property;
You can use this widget property inside methods only like, initState(), build(), dispose() etc.