Home > database >  Flutter lifting the state up through multiple dynamically added widgets
Flutter lifting the state up through multiple dynamically added widgets

Time:12-04

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

screen

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