I'm trying to build a parent widget that has a button, when clicked, it displays another widget with some text and a drop-down list. When the drop-down selection is changed, the text should change accordingly. I've included below a simplified code of what I'm trying to achieve which doesn't work. The state lifting up concept is something confusing for me as a newcomer to Flutter
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String text = "Empty";
void addWidget() {
setState(() {
widList.clear();
widList.add(MidWidget(
text: text,
setValue: selectValue,
));
});
}
void selectValue(String value) {
setState(() {
text = value;
});
}
List<Widget> widList = [];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(children: [
ElevatedButton(onPressed: addWidget, child: const Text("Add Widget")),
Column(
children: widList,
)
]),
),
);
}
}
class MidWidget extends StatelessWidget {
const MidWidget({super.key, required this.text, required this.setValue});
final String text;
final Function setValue;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(text),
LowestWidget(
dropDownValue: "First",
setValue: setValue,
),
],
);
}
}
////////////////////
///////////////////
///
class LowestWidget extends StatelessWidget {
LowestWidget(
{super.key, required this.dropDownValue, required this.setValue});
final List<String> items = ["First", "Second"];
final String dropDownValue;
final Function setValue;
@override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: dropDownValue,
icon: const Icon(Icons.arrow_downward),
onChanged: (String? value) {
setValue(value);
},
items: items.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}
CodePudding user response:
First of all, both MidWidget
and LowestWidget
need to be converted to StatefulWidget
because we need state changes inside those widgets too.
Secondly, selectValue
function should be in the MidWidget
, not in the parent widget, because it attempts to change the state of text
that has already been passed onto the MidWidget
with its original value at the time of its instantiation. Any change in text
via setState
is not going to affect its value in MidWidget
anymore.
Thirdly, I've introduced _value
variable in both MidWidget
and LowestWidget
that takes its initial value from the respective parent widgets in initState
and then gets value changes via setState
that are then used to be displayed in Text
widget in MidWidget
and DropdownButton
widget in LowestWidget
.
Following is the revised code that is working as per your requirements. I've commented out the deletions so that you could relate it with the original code.
Hope it helps!
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String text = "Empty";
void addWidget() {
setState(() {
widList.clear();
widList.add(MidWidget(
text: text,
// setValue: selectValue,
));
});
}
// void selectValue(String value) {
// setState(() {
// text = value;
// });
// }
List<Widget> widList = [];
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(children: [
ElevatedButton(onPressed: addWidget, child: const Text("Add Widget")),
Column(
children: widList,
)
]),
),
);
}
}
class MidWidget extends StatefulWidget {
const MidWidget({super.key, required this.text, /*required this.setValue*/});
final String text;
// final Function setValue;
@override
State<MidWidget> createState() => _MidWidgetState();
}
class _MidWidgetState extends State<MidWidget> {
String? _value;
void selectValue(String value) {
setState(() => _value = value);
}
@override
void initState() {
_value = widget.text;
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(_value!),
LowestWidget(
dropDownValue: "First",
setValue: selectValue,
),
],
);
}
}
////////////////////
///////////////////
///
class LowestWidget extends StatefulWidget {
LowestWidget(
{super.key, required this.dropDownValue, required this.setValue});
final String dropDownValue;
final Function setValue;
@override
State<LowestWidget> createState() => _LowestWidgetState();
}
class _LowestWidgetState extends State<LowestWidget> {
final List<String> items = ["First", "Second"];
String? _value;
@override
void initState() {
_value = widget.dropDownValue;
super.initState();
}
@override
Widget build(BuildContext context) {
return DropdownButton<String>(
value: _value,
icon: const Icon(Icons.arrow_downward),
onChanged: (String? value) {
setState(() => _value = value);
widget.setValue(value);
},
items: items.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}