k dhanji
k dhanji

Reputation: 55

Flutter Stateful Widget used across Multiple Screens getting Rebuilt

ive created the below Multiselect Chip Widget its using Provider and Listening for changes to the list

the widget creates a List of Choice Chips that allows multiple choice chips to be chosen

class MultiSelectChip extends StatefulWidget {
  final Function(List<String>) onSelectionChanged;

  MultiSelectChip({this.onSelectionChanged});

  @override
  _MultiSelectChipState createState() => _MultiSelectChipState();
}

class _MultiSelectChipState extends State<MultiSelectChip> {
  List<String> selected = List();
  List<Clinic> clinicList = List();

  @override
  void didChangeDependencies() {
    final list = Provider.of<ClinicProvider>(context).clinics;

    final clinic = Clinic(
        id: null,
        name: "All Clinics",
        city: null,
        suburb: null,
        postcode: null,
        prate: null,
        udarate: null,
        goal: null,
        uid: null);

    clinicList.add(clinic);
    selected.add(clinicList[0].name);
    list.forEach((clinic) => clinicList.add(clinic));
    super.didChangeDependencies();
  }

  _buildList() {
    List<Widget> choices = List();

    clinicList.forEach((item) {
      choices.add(Padding(
        padding: const EdgeInsets.only(left: 5.0, right: 5.0),
        child: ChoiceChip(
          key: Key("${item.name}"),
          shape: selected.contains(item.name)
              ? RoundedRectangleBorder(
                  borderRadius: BorderRadius.all(
                    Radius.circular(0),
                  ),
                )
              : RoundedRectangleBorder(
                  side: BorderSide(
                      color: Color.fromRGBO(46, 54, 143, 1), width: 1.0),
                  borderRadius: BorderRadius.circular(0.0),
                ),
          label: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text(item.name),
          ),
          onSelected: (value) {
            setState(() {
              selected.contains(item.name)
                  ? selected.remove(item.name)
                  : selected.add(item.name);
              widget.onSelectionChanged(selected);
            });
          },
          selected: selected.contains(item.name),
          selectedColor: Color.fromRGBO(46, 54, 143, 1),
          labelStyle:
              selected.contains(item.name) ? kChipActive : kChipInActive,
          backgroundColor: Colors.transparent,
        ),
      ));
    });

    return choices;
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(left: 8.0, top: 5.0, bottom: 5.0),
      child: SizedBox(
        height: 50,
        width: double.infinity,
        child: ListView(
          scrollDirection: Axis.horizontal,
          children: _buildList(),
        ),
      ),
    );
  }
}

when i click from this Log screen and goto the NewLog screen and Pop Back to the Log Screen

class LogScreen extends StatefulWidget {
  static const String id = 'logscreen';
  @override
  _LogScreenState createState() => _LogScreenState();
}

class _LogScreenState extends State<LogScreen> {
  MonthSelector selectedMonth;
  List<String> selectedItems = List();

  static DateTime now = DateTime.now();
  static DateTime end = DateTime(now.year, now.month + 1, 0);
  static DateTime start = DateTime(now.year, now.month, 1);

  MonthSelector currentMonth = MonthSelector(
      monthName: DateFormat("MMMM").format(now),
      monthStart: start,
      monthEnd: end);

  void refreshData(MonthSelector selector) async {
    await Provider.of<LogProvider>(context, listen: false)
        .getLogs(selector.monthStart, selector.monthEnd);
    await Provider.of<LogProvider>(context, listen: false)
        .loadTreatments(selector.monthStart, selector.monthEnd);
  }

  @override
  Widget build(BuildContext context) {
    final List<LogSummary> list = Provider.of<LogProvider>(context).summary;
    final List<FlSpot> chartData = Provider.of<LogProvider>(context).spots;

    return Container(
      color: Color.fromRGBO(246, 246, 246, 1),
      child: Column(
        children: <Widget>[
          Row(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Expanded(
                  flex: 1,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      SizedBox(
                        height: 15,
                      ),
                      RawMaterialButton(
                        onPressed: () {
                          Navigator.pushNamed(context, NewLogScreen.id);
                        },
                        constraints: BoxConstraints.tight(Size(60, 60)),
                        child: Icon(
                          Icons.add,
                          color: Color.fromRGBO(255, 255, 255, 1),
                          size: 30,
                        ),
                        shape: CircleBorder(),
                        fillColor: Color.fromRGBO(46, 54, 143, 1),
                        padding: EdgeInsets.all(15.0),
                        elevation: 1,
                      ),
                      SizedBox(
                        height: 10,
                      ),
                      Text(
                        'Add log',
                        style: kAddLogLabel,
                      )
                    ],
                  ),
                ),
              ]),
          list.isEmpty || chartData.isEmpty
              ? Expanded(
                  child: Center(
                    child: Text("No Log Data.."),
                  ),
                )
              : Expanded(
                  child: Column(
                    mainAxisSize: MainAxisSize.max,
                    children: <Widget>[
                      Container(
                        height: 150,
                        alignment: Alignment.center,
                        child: LineChartWidget(
                          list: chartData,
                          isDollar: true,
                        ),
                      ),
                      SizedBox(
                        height: 10,
                      ),
                      MultiSelectChip(
                        onSelectionChanged: (selectedList) async {
                          setState(() {
                            selectedItems = selectedList;
                          });
                          await Provider.of<LogProvider>(context, listen: false)
                              .filterLogList(selectedItems);
                        },
                      ),
                      MonthSelect(Color.fromRGBO(246, 246, 246, 1),
                          onMonthSelectionChanged: (selected) {
                        setState(() {
                          selectedMonth = selected;
                        });
                        selectedMonth == null
                            ? refreshData(currentMonth)
                            : refreshData(selectedMonth);
                      }),
                      Padding(
                        padding:
                            const EdgeInsets.only(top: 10, left: 0, right: 0),
                        child: Container(
                          width: double.infinity,
                          height: 1.0,
                          color: kDividerColor,
                        ),
                      ),

what i am seeing is the Multiselect Chip has the Same List of Items redrawn/added to the list view 3 times, each time i go into the NewLog screen the list keeps growing

im currently using the same Widget across 4 diffrent screens, but for some reason when i navigate to one of the other screen the list resets and displays the orignal items and the duplicate items dissapear

what can i do to prevent this from redrawing, all the time when navigating off the screen

thanks

Upvotes: 0

Views: 1246

Answers (1)

kaboc
kaboc

Reputation: 844

Have you tried specifying listen: false in Provider.of() used in didChangeDependencies()? It may solve the issue.

However, there can still be a risk. I doubt initialising something there is safe because didChangeDependencies() is called when/whenever a dependency of the State object changes as written in its document. It'd be safer to do it in initState(), or have it done outside only once and its result passed in to MultiSelectChip.

Upvotes: 1

Related Questions