Search code examples
androidflutterdartstatefuldropdownbutton

Why stateful widget unable to maintain it's state in flutter


In my program I put the add button to create stateful box with stateful drop down button inside of it, each time I add the box I add it to Map<int, Widget> and pass it to the column. When I click on the cross button it delete the widget from the map in parent. But when I click on cross button on the widget, it show wrong colour of the box and wrong drop down value.Watch the GIF I posted to get the overview of the problem

enter image description here

Link to dart pad to run the example : dart pad code link here

import 'package:flutter/material.dart';
import 'dart:math';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  _StateMyWidget createState() => _StateMyWidget();
}

class _StateMyWidget extends State<MyWidget> {
  Map<int, Widget> widgetList = {};
  int boxCount = 0;

  @override
  initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ListView(children: [
      Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Column(
              mainAxisSize: MainAxisSize.min,
              children: widgetList.values.toList()),
          TextButton(
              onPressed: () {
                widgetList[boxCount] =
                    new MyBox(boxIndex: boxCount, deleteFunction: deleteBox);
                setState(() {});
                boxCount += 1;
              },
              child: Text("Add"))
        ],
      )
    ]);
  }

  deleteBox(boxIndex) {
    widgetList.remove(boxIndex);
    setState(() {});
  }
}

class MyBox extends StatefulWidget {
  final int boxIndex;
  final Function deleteFunction;
  MyBox({required this.boxIndex, required this.deleteFunction});
  _StateMyBox createState() => _StateMyBox();
}

class _StateMyBox extends State<MyBox> {
  var containerColor;
  @override
  initState() {
    super.initState();
    containerColor =
        Colors.primaries[Random().nextInt(Colors.primaries.length)];
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        width: 200,
        height: 200,
        margin: EdgeInsets.all(17),
        padding: EdgeInsets.all(10),
        color: containerColor,
        child: Column(children: [
          Row(children: [
            Text("Box Number: ${widget.boxIndex}"),
            Spacer(),
            IconButton(
              icon: const Icon(Icons.clear),
              onPressed: () {
                widget.deleteFunction(widget.boxIndex);
              },
            ),
          ]),
          RegistrationDropdown(listData: ['One', 'Two', 'Three', 'Four']),
        ]));
  }
}

class RegistrationDropdown extends StatefulWidget {
  final List<String> listData;
  RegistrationDropdown({
    required this.listData,
  });
  @override
  _StateRegistrationDropdown createState() {
    return _StateRegistrationDropdown();
  }
}

class _StateRegistrationDropdown extends State<RegistrationDropdown> {
  String dropdownValue = 'One';
  @override
  Widget build(BuildContext context) {
    return Container(
        color: Colors.white,
        padding: EdgeInsets.only(left: 10, right: 10),
        child: DropdownButton<String>(
          isExpanded: true,
          underline: SizedBox(),
          value: dropdownValue,
          icon: const Icon(Icons.arrow_downward),
          iconSize: 24,
          elevation: 16,
          style: const TextStyle(color: Colors.deepPurple),
          onChanged: (String? newValue) {
            print("Previous dropdown value $dropdownValue");
            print("New value $newValue");
            setState(() {
              dropdownValue = newValue!;
            });
          },
          items: widget.listData.map<DropdownMenuItem<String>>((String value) {
            return DropdownMenuItem<String>(
              value: value,
              child: Text(value),
            );
          }).toList(),
        ));
  }
}

Solution

  • The solution is a Key of the widget. The When to Use Keys: Flutter Youtube will be helpful.

    import 'package:flutter/material.dart';
    import 'dart:math';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          home: Scaffold(
            body: Center(
              child: MyWidget(),
            ),
          ),
        );
      }
    }
    
    class MyWidget extends StatefulWidget {
      @override
      _StateMyWidget createState() => _StateMyWidget();
    }
    
    class _StateMyWidget extends State<MyWidget> {
      Map<int, Widget> widgetList = {};
      int boxCount = 0;
    
      @override
      initState() {
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return ListView(
          children: [
            Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Column(
                  mainAxisSize: MainAxisSize.min,
                  children: widgetList.values.toList(),
                ),
                TextButton(
                  onPressed: () {
                    widgetList[boxCount] = new MyBox(
                      key: UniqueKey(), // <---------------------
                      boxIndex: boxCount,
                      deleteFunction: deleteBox,
                    );
                    setState(() {});
                    boxCount += 1;
                  },
                  child: Text("Add"),
                )
              ],
            )
          ],
        );
      }
    
      deleteBox(boxIndex) {
        widgetList.remove(boxIndex);
        setState(() {});
      }
    }
    
    class MyBox extends StatefulWidget {
      final int boxIndex;
      final Function deleteFunction;
    
      MyBox({
        Key? key, // <---------------------
        required this.boxIndex,
        required this.deleteFunction,
      }) : super(key: key); // <---------------------
    
      _StateMyBox createState() => _StateMyBox();
    }
    
    class _StateMyBox extends State<MyBox> {
      var containerColor;
      @override
      initState() {
        super.initState();
        containerColor =
            Colors.primaries[Random().nextInt(Colors.primaries.length)];
      }
    
      @override
      Widget build(BuildContext context) {
        return Container(
            width: 200,
            height: 200,
            margin: EdgeInsets.all(17),
            padding: EdgeInsets.all(10),
            color: containerColor,
            child: Column(children: [
              Row(children: [
                Text("Box Number: ${widget.boxIndex}"),
                Spacer(),
                IconButton(
                  icon: const Icon(Icons.clear),
                  onPressed: () {
                    widget.deleteFunction(widget.boxIndex);
                  },
                ),
              ]),
              RegistrationDropdown(listData: ['One', 'Two', 'Three', 'Four']),
            ]));
      }
    }
    
    class RegistrationDropdown extends StatefulWidget {
      final List<String> listData;
      RegistrationDropdown({
        required this.listData,
      });
      @override
      _StateRegistrationDropdown createState() {
        return _StateRegistrationDropdown();
      }
    }
    
    class _StateRegistrationDropdown extends State<RegistrationDropdown> {
      String dropdownValue = 'One';
      @override
      Widget build(BuildContext context) {
        return Container(
          color: Colors.white,
          padding: EdgeInsets.only(left: 10, right: 10),
          child: DropdownButton<String>(
            isExpanded: true,
            underline: SizedBox(),
            value: dropdownValue,
            icon: const Icon(Icons.arrow_downward),
            iconSize: 24,
            elevation: 16,
            style: const TextStyle(color: Colors.deepPurple),
            onChanged: (String? newValue) {
              print("Previous dropdown value $dropdownValue");
              print("New value $newValue");
              setState(() {
                dropdownValue = newValue!;
              });
            },
            items: widget.listData.map<DropdownMenuItem<String>>((String value) {
              return DropdownMenuItem<String>(
                value: value,
                child: Text(value),
              );
            }).toList(),
          ),
        );
      }
    }