How to solve: Flutter ChangeNotifier was used after being disposed

Question

Asked by bluends on January 04, 2022 (source).

I have checked mukltiple posts on this matter, but none has helped, while some stopped the error (e.g. adding a variable that stops the function to notifylistener after its disposed), it certainly didnt fix it.

My Goal is to make a shopping list that just records the state of each checkbox in a tab and displays it using navigator.push and back using pop

Source Code:

home.dart

// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:loginpage/main.dart';
import 'package:badges/badges.dart';
import 'package:provider/provider.dart';
import 'tabPage.dart';
import 'mobileList.dart';
import 'itemList.dart';

class HomePage extends StatefulWidget {
  HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => HomePageState();
}

class HomePageState extends State<HomePage> {
  LoginPage loginPage = LoginPage();
  TabPage? tabPage;

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

    tabPage = TabPage(
      key: GlobalKey<TabPageState>(),
      tabNum: 10,
      controlNum: 3,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home Page"),
        leading: IconButton(
          onPressed: () {
            //Back to Login Page
            Navigator.push(
                context, MaterialPageRoute(builder: (context) => LoginPage()));
          },
          icon: Image.asset('assets/images/backbtn.png'),
        ),
        actions: [
          ChangeNotifierProvider(
            create: (context) => tabPage!.checkBoxInfo,
            child: Consumer<CheckBoxInfo>(
              builder: (context, checkBoxInfo, child) {
                return Badge(
                  position: BadgePosition.topEnd(top: 0, end: 0),
                  shape: BadgeShape.square,
                  borderRadius: BorderRadius.circular(20),
                  badgeContent: Text(checkBoxInfo.checked.toString()),
                  animationType: BadgeAnimationType.scale,
                  animationDuration: Duration(milliseconds: 50),
                  child: IconButton(
                      onPressed: () {
                        Navigator.push(
                            context,
                            MaterialPageRoute(
                                builder: (context) => ItemList(
                                    checkBoxInfo: tabPage!.checkBoxInfo)));
                      },
                      icon: Icon(Icons.shopping_cart)),
                );
              },
            ),
          ),
        ],
      ),
      body: SafeArea(
        child: Scaffold(
          body: Center(
            child: Column(
              children: <Widget>[
                SizedBox(height: 30),
                Text('This is the Home Page'),
                SizedBox(height: 20),
                Text(loginPage.username),
                //Tab Page
                Container(
                  child: tabPage,
                ),
                TextButton(
                    onPressed: () {
                      Navigator.push(
                          context,
                          MaterialPageRoute(
                              builder: (context) => MobileList(data: data)));
                    },
                    child: Text("Mobile List")),
              ],
            ),
          ),
        ),
      ),
      floatingActionButton: Container(
        decoration:
            BoxDecoration(color: Colors.grey[800], shape: BoxShape.circle),
        child: IconButton(
            color: Colors.lightBlue,
            onPressed: () {
              // tabPage!.tabindex == tabPage!.tabNum - 1
              //     ? tabPage!.tabindex = 0
              //     : tabPage!.tabindex += 1;
              // //callback method
              // //GlobalKey method
              // //Set Global Key
              // (tabPage!.key as GlobalKey<TabPageState>) //Casting
              //     .currentState
              //     ?.tabSetState();
              // // tabPageKey.currentState?.tabSetState();

              (tabPage!.key as GlobalKey<TabPageState>)
                  .currentState!
                  .scrollToOffset(100, Duration(milliseconds: 200));
            },
            icon: const Icon(Icons.ac_unit_outlined)),
      ),
    );
  }
}

tabPage.dart

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

import 'package:loginpage/pages/home.dart';

class TabPage extends StatefulWidget {
  TabPage({Key? key, required this.tabNum, required this.controlNum})
      : super(key: key);

  final int controlNum;
  final int tabNum;
  final String _example = "";
  CheckBoxInfo checkBoxInfo = CheckBoxInfo();
  int tabindex = 0;

  @override
  State<StatefulWidget> createState() => TabPageState();
}

class TabPageState extends State<TabPage> {
  List<String> tabTitles = [];
  List<List<Widget>> tabs = [];
  ScrollController? _scrollController;

  void scrollToOffset(increment, duration) {
    _scrollController!.animateTo(_scrollController!.offset + increment,
        duration: duration, curve: Curves.ease);
  }

  void tabSetState() {
    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    tabTitles = createTabTitles(widget.tabNum);
    _scrollController = ScrollController(initialScrollOffset: 20);
    _scrollController!.addListener(() {
      print(_scrollController!.offset);
    });

    widget.checkBoxInfo
        .initCheckBox(widget.controlNum, widget.tabNum, tabTitles);
  }

  @override
  Widget build(BuildContext context) {
    tabs = createTabs(widget.tabNum);

    return SafeArea(
      child: Column(
        children: [
          SizedBox(
            height: 10,
          ),
          Container(
            margin: EdgeInsets.only(left: 20.0, right: 20.0),
            decoration: BoxDecoration(
                border: Border.all(width: 3, color: Colors.black)),
            child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  SingleChildScrollView(
                    scrollDirection: Axis.horizontal,
                    controller: _scrollController,
                    child: Row(
                      // children: tabTitles
                      //     .asMap()
                      //     .map((i, title) => MapEntry(
                      //         i,
                      //         TextButton(
                      //           onPressed: () {
                      //             setState(() {
                      //               widget.tabindex = i;
                      //             });
                      //           },
                      //           child: Text(tabTitles[i]),
                      //         )))
                      //     .values
                      //     .toList()

                      // children: tabTitles.map((title) =>  TextButton(
                      //           onPressed: () {
                      //             setState(() {
                      //               widget.tabindex = i;
                      //             });
                      //           },
                      //           child: Text(tabTitles[i]),
                      //         )).toList(),

                      children: createTabBtns(),
                    ),
                  ),
                  Padding(
                    padding: EdgeInsets.all(5),
                    child: Column(
                      children: tabs[widget.tabindex],
                    ),
                  )
                ]),
          ),
        ],
      ),
    );
  }

  List<Widget> createTabBtns() {
    List<Widget> btnList = [];
    for (int i = 0; i < tabTitles.length; i++) {
      btnList.add(Container(
        decoration: BoxDecoration(
            border: Border(
                right: BorderSide(width: 1, color: Colors.grey),
                bottom: widget.tabindex == i
                    ? BorderSide(width: 4, color: Colors.blue[400]!)
                    : BorderSide(width: 4, color: Colors.grey[400]!))),
        child: TextButton(
          onPressed: () {
            setState(() {
              widget.tabindex = i;
            });
          },
          child: Text(tabTitles[i]),
        ),
      ));
    }
    return btnList;
  }

  List<List<Widget>> createTabs(tabNum) {
    List<List<Widget>> tabList = [];
    for (int i = 0; i < tabNum; i++) {
      List<Widget> tabContent = [
        Align(
          alignment: Alignment.centerLeft,
          child: Text(
            tabTitles[i] + " Items",
          ),
        ),
        ...createCheckBoxList(widget.controlNum, tabTitles[i])
      ];

      tabList.add(tabContent);
    }

    return tabList;
  }

  List<Widget> createCheckBoxList(controlNum, tabTitle) {
    List<Widget> CBList = [];

    for (int i = 0; i < controlNum; i++) {
      String checkBoxName = '${tabTitle} Item $i';

      CBList.add(CheckboxListTile(
          title: Text('${tabTitle} Item $i'),
          value: widget.checkBoxInfo.isChecked(checkBoxName),
          onChanged: (newValue) {
            setState(() {
              widget.checkBoxInfo.setCheckBox(checkBoxName, newValue!);
            });
          }));
    }

    return CBList;
  }

  List<String> createTabTitles(tabNum) {
    List<String> tabTitles = [];

    for (int i = 0; i < tabNum; i++) {
      tabTitles.add("A" + i.toString());
    }

    return tabTitles;
  }
}

class CheckBoxInfo extends ChangeNotifier {
  Map<String, bool> checkBoxState = {};
  int checked = 0;
  bool disposed = false;

  void setCheckBox(String name, bool state) {
    checkBoxState[name] = state;
    checkChecked();
    notifyListeners();
  }

  bool isChecked(String name) {
    return checkBoxState[name]!;
  }

  void initCheckBox(int controlNum, int tabNum, List<String> tabTitles) {
    for (int i = 0; i < tabNum; i++) {
      String checkBoxName = '${tabTitles[i]} ';
      for (int i = 0; i < controlNum; i++) {
        setCheckBox(checkBoxName + 'Item $i', false);
      }
    }
  }

  void checkChecked() {
    int _checked = 0;
    checkBoxState.forEach((key, value) {
      _checked += value == true ? 1 : 0;
    });
    checked = _checked;
  }
}

itemList.dart

import 'dart:collection';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:loginpage/pages/tabPage.dart';
import 'dart:developer';

import 'package:provider/provider.dart';

import 'home.dart';

class ItemList extends StatefulWidget {
  ItemList({Key? key, required this.checkBoxInfo}) : super(key: key);

  final CheckBoxInfo checkBoxInfo;

  @override
  State<StatefulWidget> createState() => ItemListState();
}

class ItemListState extends State<ItemList> {
  List<Widget> itemList = [];

  @override
  void initState() {
    super.initState();
    itemList = setItemList(widget.checkBoxInfo);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Item List"),
        leading: IconButton(
          onPressed: () {
            //Back to Home Page
            Navigator.pop(context);
          },
          icon: Image.asset('assets/images/backbtn.png'),
        ),
      ),
      body: SafeArea(
          child: Container(
              child: ChangeNotifierProvider(
        create: (context) => widget.checkBoxInfo,
        child: Consumer<CheckBoxInfo>(
          builder: (context, checkBoxInfo, child) {
            return Column(
              children: itemList,
            );
          },
        ),
      ))),
    );
  }

  List<Widget> setItemList(CheckBoxInfo checkBoxInfo) {
    List<Widget> tempItemList = [];
    checkBoxInfo.checkBoxState.forEach((key, value) {
      if (checkBoxInfo.checkBoxState[key]!) {
        tempItemList.add(Container(
          width: double.infinity,
          decoration: BoxDecoration(
            border: Border.all(color: Colors.grey, width: 2),
          ),
          child: Padding(
            padding: EdgeInsets.all(8),
            child: Row(
              children: [
                Text(
                  key,
                  overflow: TextOverflow.ellipsis,
                ),
                SizedBox(
                  width: 20,
                ),
                Icon(value ? Icons.check : Icons.cancel_outlined)
              ],
            ),
          ),
        ));
      }
    });
    return tempItemList;
  }
}

THe error is the following: A CheckBoxInfo was used after being disposed. I dont know why it doesnt work, and according to my senior, the provider should work sort of like the local storage in web.

Answer

Question answered by Andrija (source).

I think there is all sorts of things going wrong here.

You create your TabPage widget on the fly, and you put your state - checkBoxInfo - in it. And then you expose it through ChangeNotifierProvider. After that you add the TabPage to the Widget tree - which will eventually create it's state object...

As soon as you navigate from the page that shows your TabPage, it's state object and the widget itself will be disposed. And your ChangeNotifierProvider is gone, too. The thing is: it's not your ChangeNotifierProvider that is holding your state, it is the widget underneath it.

Edit: here's the code that should work. I commented out some navigation (Login Page etc.) to make it work.

In the code, ChangeNotifierProvider is above the MaterialApp - making sure it is visible in all pages you try to navigate to.

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

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<CheckBoxInfo>(
        create: (context) => CheckBoxInfo(3, 10), //tabPage!.checkBoxInfo,
        child: MaterialApp(
          theme: ThemeData.dark().copyWith(
            scaffoldBackgroundColor: darkBlue,
          ),
          debugShowCheckedModeBanner: false,
          home: Scaffold(
            body: Center(
              child: HomePage(),
            ),
          ),
        ));
  }
}

class HomePage extends StatelessWidget {
  HomePage({Key? key}) : super(key: key);
  final _tabKey = GlobalKey<TabPageState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: const Text("Home Page"),
          leading: IconButton(
            onPressed: () {
              //Back to Login Page
//             Navigator.push(
//                 context, MaterialPageRoute(builder: (context) => LoginPage()));
            },
            icon: const Icon(Icons.arrow_back), //.asset('assets/images/backbtn.png'),
          ),
          actions: [
            Consumer<CheckBoxInfo>(
              builder: (context, checkBoxInfo, child) {
                return IconButton(
                  onPressed: () {
                    Navigator.push(context, MaterialPageRoute(builder: (context) => const ItemList()));
                  },
                  icon: const Icon(Icons.shopping_cart),
                );
              },
            ),
          ]),
      body: SafeArea(
        child: Scaffold(
          body: Center(
            child: Column(
              children: <Widget>[
                const SizedBox(height: 30),
                const Text('This is the Home Page'),
                const SizedBox(height: 20),
                const Text("Test"),
                //Tab Page
                Consumer<CheckBoxInfo>(builder: (context, checkBoxInfo, child) {
                  return TabPage(key: _tabKey, tabNum: checkBoxInfo.tabNum, controlNum: checkBoxInfo.controlNum);
                }),
                TextButton(
                    onPressed: () {
//                       Navigator.push(
//                           context,
//                           MaterialPageRoute(
//                               builder: (context) => MobileList(data: data)));
                    },
                    child: const Text("Mobile List")),
              ],
            ),
          ),
        ),
      ),
      floatingActionButton: Container(
        decoration: BoxDecoration(color: Colors.grey[800], shape: BoxShape.circle),
        child: IconButton(
            color: Colors.lightBlue,
            onPressed: () {
              _tabKey.currentState!.scrollToOffset(100, const Duration(milliseconds: 200));
            },
            icon: const Icon(Icons.ac_unit_outlined)),
      ),
    );
  }
}

class TabPage extends StatefulWidget {
  const TabPage({Key? key, required this.tabNum, required this.controlNum}) : super(key: key);

  final int controlNum;
  final int tabNum;
  @override
  State<StatefulWidget> createState() => TabPageState();
}

class TabPageState extends State<TabPage> {
  List<List<Widget>> tabs = [];
  ScrollController? _scrollController;

  int tabindex = 0;

  void scrollToOffset(increment, duration) {
    _scrollController!.animateTo(_scrollController!.offset + increment, duration: duration, curve: Curves.ease);
  }

  void tabSetState() {
    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    _scrollController = ScrollController(initialScrollOffset: 20);
    _scrollController!.addListener(() {
      print(_scrollController!.offset);
    });
  }

  @override
  Widget build(BuildContext context) {
    var checkBoxInfo = Provider.of<CheckBoxInfo>(context, listen: false);
    tabs = createTabs(checkBoxInfo);

    return SafeArea(
      child: Column(
        children: [
          const SizedBox(
            height: 10,
          ),
          Container(
            margin: const EdgeInsets.only(left: 20.0, right: 20.0),
            decoration: BoxDecoration(border: Border.all(width: 3, color: Colors.black)),
            child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
              SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                controller: _scrollController,
                child: Row(
                  children: createTabBtns(checkBoxInfo.tabTitles),
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(5),
                child: Column(
                  children: tabs[tabindex],
                ),
              )
            ]),
          ),
        ],
      ),
    );
  }

  List<Widget> createTabBtns(List<String> tabTitles) {
    List<Widget> btnList = [];
    for (int i = 0; i < widget.tabNum; i++) {
      btnList.add(Container(
        decoration: BoxDecoration(
            border: Border(
                right: const BorderSide(width: 1, color: Colors.grey),
                bottom: tabindex == i
                    ? BorderSide(width: 4, color: Colors.blue[400]!)
                    : BorderSide(width: 4, color: Colors.grey[400]!))),
        child: TextButton(
          onPressed: () {
            setState(() {
              tabindex = i;
            });
          },
          child: Text(tabTitles[i]),
        ),
      ));
    }
    return btnList;
  }

  List<List<Widget>> createTabs(CheckBoxInfo checkBoxInfo) {
    List<List<Widget>> tabList = [];
    for (int i = 0; i < widget.tabNum; i++) {
      List<Widget> tabContent = [
        Align(
          alignment: Alignment.centerLeft,
          child: Text(
            checkBoxInfo.tabTitles[i] + " Items",
          ),
        ),
        ...createCheckBoxList(checkBoxInfo, checkBoxInfo.tabTitles[i])
      ];

      tabList.add(tabContent);
    }

    return tabList;
  }

  List<Widget> createCheckBoxList(checkBoxInfo, tabTitle) {
    List<Widget> cBList = [];

    for (int i = 0; i < widget.controlNum; i++) {
      String checkBoxName = '$tabTitle Item $i';

      cBList.add(CheckboxListTile(
          title: Text('$tabTitle Item $i'),
          value: checkBoxInfo.isChecked(checkBoxName),
          onChanged: (newValue) {
            setState(() {
              checkBoxInfo.setCheckBox(checkBoxName, newValue!);
            });
          }));
    }

    return cBList;
  }
}

class CheckBoxInfo extends ChangeNotifier {
  Map<String, bool> checkBoxState = {};
  int checked = 0;
//   bool disposed = false;
  List<String> tabTitles = [];
  int controlNum;
  int tabNum;

  CheckBoxInfo(this.controlNum, this.tabNum) {
    for (int i = 0; i < tabNum; i++) {
      String tabTitle = "A" + i.toString();
      tabTitles.add(tabTitle);
      for (int i = 0; i < controlNum; i++) {
        setCheckBox(tabTitle + ' Item $i', false);
      }
    }
  }

  void setCheckBox(String name, bool state) {
    checkBoxState[name] = state;
    checkChecked();
    notifyListeners();
  }

  bool isChecked(String name) {
    return checkBoxState[name]!;
  }

  void checkChecked() {
    int _checked = 0;
    checkBoxState.forEach((key, value) {
      _checked += value == true ? 1 : 0;
    });
    checked = _checked;
  }
}

class ItemList extends StatelessWidget {
  const ItemList({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    var checkBoxInfo = Provider.of<CheckBoxInfo>(context, listen: false);
    var itemList = setItemList(checkBoxInfo);

    return Scaffold(
        appBar: AppBar(
          title: const Text("Item List"),
          leading: IconButton(
            onPressed: () {
              //Back to Home Page
              Navigator.pop(context);
            },
            icon: const Icon(Icons.arrow_back),
          ),
        ),
        body: SafeArea(
          child: Consumer<CheckBoxInfo>(
            builder: (context, checkBoxInfo, child) {
              return Column(
                children: itemList,
              );
            },
          ),
        ));
  }

  List<Widget> setItemList(CheckBoxInfo checkBoxInfo) {
    List<Widget> tempItemList = [];

    checkBoxInfo.checkBoxState.forEach((key, value) {
      if (value) {
        tempItemList.add(Container(
          width: double.infinity,
          decoration: BoxDecoration(
            border: Border.all(color: Colors.grey, width: 2),
          ),
          child: Padding(
            padding: const EdgeInsets.all(8),
            child: Row(
              children: [
                Text(
                  key,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(
                  width: 20,
                ),
                Icon(value ? Icons.check : Icons.cancel_outlined)
              ],
            ),
          ),
        ));
      }
    });
    return tempItemList;
  }
}

DART FLUTTER
SHARE: