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.
CodePudding user response:
@override
void dispose() {
_disposed = true;
super.dispose();
}
@override
void notifyListeners() {
if (!_disposed) {
super.notifyListeners();
}
}
CodePudding user response:
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;
}
}
