I have trouble with data model to add an item into a List inside MyList model with notifylistener() to notify the UI about changes because im using add button in another page. Is it okay to pass data to another screens when using ChangeNotifierProvider?
mylist.dart
import 'package:grocery_app/models/item.dart'; // import Item model
import 'dart:convert'; // import Json decoder and encoder
MyList myListFromJson(String str) => MyList.fromJson(json.decode(str));
String myListToJson(MyList data) => json.encode(data.toJson());
class MyList{
MyList({
this.listName,
this.items,
}); // Constructor
String listName; // List name
List<Item> items; // List of items in Array
factory MyList.fromJson(Map<String, dynamic> json) => MyList( // Constructing a new MyList instance from a Map structure
listName: json["listName"],
items: List<Item>.from(json["Items"].map((x) => Item.fromJson(x))),
);
Map<String, dynamic> toJson() => { // Convert into Map structure
"listName": listName,
"Items": List<dynamic>.from(items.map((x) => x.toJson())),
};
}
item.dart
import 'dart:convert';
Item itemFromJson(String str) => Item.fromJson(json.decode(str));
String itemToJson(Item data) => json.encode(data.toJson());
class Item {
Item({
this.itemName,
this.itemDesc,
this.itemCategory,
this.itemPrice,
this.itemQty,
this.checked,
this.itemTotPrice,
}); // Constructor
String itemName; // Item name
String itemDesc; // Item description
String itemCategory; // Item category
double itemPrice; // Item price
int itemQty; // Item quantity
bool checked; // Item checked true or false
double itemTotPrice; // Item total price
factory Item.fromJson(Map<String, dynamic> json) => Item(
// Constructing a new Item instance from a Map structure
itemName: json["itemName"],
itemDesc: json["itemDesc"],
itemCategory: json["itemCategory"],
itemPrice: json["itemPrice"].toDouble(),
itemQty: json["itemQty"],
checked: json["checked"],
itemTotPrice: json["itemTotPrice"].toDouble(),
);
Map<String, dynamic> toJson() => {
// Convert into Map structure
"itemName": itemName,
"itemDesc": itemDesc,
"itemCategory": itemCategory,
"itemPrice": itemPrice,
"itemQty": itemQty,
"checked": checked,
"itemTotPrice": itemTotPrice,
};
}
MyListProvider.dart
import 'package:grocery_app/models/mylist.dart'; // import MyList model
import 'package:flutter/foundation.dart'; // import ChangeNotifier
class MyListProvider extends ChangeNotifier {
// Notify the UI when any changes happen to the list
List<MyList> _myList = [];
void addList(MyList myList) {
// Add new list method
_myList.add(myList);
notifyListeners(); // Call notify listener to notify UI about changes in the list
}
void removeList(int index) {
// Remove list method
_myList.removeAt(index);
notifyListeners(); // Call notify listener to notify UI about changes in the list
}
List<MyList> get myList => _myList; // Getter method
}
Mainpage.dart
import 'package:flutter/material.dart';
import 'package:grocery_app/screens/AddNewList.dart';
// import 'dart:convert'
// show json, base64, ascii; // Convert Json string into Json object
// import 'package:flutter_secure_storage/flutter_secure_storage.dart'; // Persistent storage for JWToken
import 'package:grocery_app/screens/Login.dart'; // import Login page
import 'package:grocery_app/screens/Signup.dart'; // import Signup page
import 'package:grocery_app/screens/Home.dart'; // import Home page
import 'package:grocery_app/screens/ListDetails.dart'; // import List details page
import 'package:grocery_app/models/mylist.dart'; // import MyList model
// import 'package:grocery_app/models/item.dart'; // import Item model
import 'package:grocery_app/providers/MyListProvider.dart'; // import MyListProvider
import 'package:flutter_svg/flutter_svg.dart'; // import svg module
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // import Font Awesome package
import 'package:provider/provider.dart'; // import Provider package
class Mainpage extends StatefulWidget {
// Mainpage(
// this.jwt, this.payload); // Receive jwt from FutureBuilder snapshot.data
// factory Mainpage.fromBase64(String jwt) => Mainpage(
// jwt,
// json.decode(ascii.decode(base64.decode(base64.normalize(jwt.split(".")[
// 1]))))); // Split 3 dots from response body into different string and normalize
// // Normalize will do the following:
// // Unescape any %-escapes.
// // Only allow valid characters (A-Z, a-z, 0-9, / and +).
// // Normalize a _ or - character to / or +.
// // Validate that existing padding (trailing = characters) is correct.
// // If no padding exists, add correct padding if necessary and possible.
// // Validate that the length is correct (a multiple of four).
// final String jwt; // Jwt access token
// final Map<String, dynamic>
// payload; // JWT payload from response body into Map structure
// final storage =
// FlutterSecureStorage(); // Flutter secure storage instance for persistent storage
@override
_MainpageState createState() => _MainpageState();
}
class _MainpageState extends State<Mainpage> {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => MyListProvider()),
],
child: MaterialApp(
routes: <String, WidgetBuilder>{
// MaterialApp routes
'/home': (BuildContext context) => Home(), // Home page route
'/login': (BuildContext context) => Login(), // Login page route
'/signup': (BuildContext context) => Signup(), // Sign up page route
},
home: SecondMainpage(),
),
);
}
}
class SecondMainpage extends StatefulWidget {
// Create a stateful widget for Mainpage
@override
_SecondMainpageState createState() => _SecondMainpageState();
}
class _SecondMainpageState extends State<SecondMainpage> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
var myListLength = 0; // default MyList length
@override
Widget build(BuildContext context) {
final myListProvider = Provider.of<MyListProvider>(context);
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AddNewList())); // Go to add new list page
},
child: FaIcon(
FontAwesomeIcons.plus,
size: 30,
),
backgroundColor: Colors.deepPurple[900],
),
drawer: Drawer(
child: ListView(padding: EdgeInsets.zero, children: [
DrawerHeader(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/drawer-header.jpg"),
fit: BoxFit.cover)),
child: Align(
alignment: Alignment.bottomLeft,
child: Container(
margin: EdgeInsets.only(left: 5, bottom: 5),
child: Text(
'Crew Zul',
style: TextStyle(
color: Colors.white,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w700,
fontSize: 18),
),
),
),
),
InkWell(
child: ListTile(
leading: Icon(
FontAwesomeIcons.donate,
color: Colors.amber[600],
),
title: Text(
'Donate',
style: TextStyle(
fontSize: 15,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w600,
color: Colors.black),
),
onTap: () {},
),
),
InkWell(
child: ListTile(
leading: Icon(
FontAwesomeIcons.powerOff,
color: Colors.redAccent[700],
),
title: Text(
'Log out',
style: TextStyle(
fontSize: 15,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w600,
color: Colors.black),
),
onTap: () {},
),
),
]),
),
backgroundColor: Colors.purple,
body: CustomScrollView(slivers: <Widget>[
SliverAppBar(
backgroundColor: Colors.deepPurple[900],
title: Text(
'My List',
style: TextStyle(
color: Colors.white,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w600),
),
expandedHeight: 200.0,
flexibleSpace: Container(
margin: EdgeInsets.only(bottom: 25),
child: Align(
alignment: Alignment.bottomCenter,
child: SvgPicture.asset('assets/groceries.svg',
width: 110, height: 110),
),
),
),
SliverToBoxAdapter(
child: Container(
color: Colors.deepPurple[900],
height: 20,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(70),
topRight: Radius.circular(70)),
child: Container(
height: 20,
width: double.infinity,
color: Colors.purple,
),
),
],
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
MyList mList = myListProvider.myList[index]; // Display myList
return Dismissible(
background: Container(
padding: EdgeInsets.only(left: 15),
color: Colors.amber[600],
child: Align(
alignment: Alignment.centerLeft,
child: FaIcon(
FontAwesomeIcons.trashAlt,
size: 27,
color: Colors.white,
),
)),
// Each Dismissible must contain a Key. Keys allow Flutter to
// uniquely identify widgets.
key: UniqueKey(), // Object key to specify which element
onDismissed: (DismissDirection direction) {
myListProvider
.removeList(index); // Remove object from specific index
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
"List deleted"))); // Show snackbar when list deleted
print(myListProvider.myList.length);
print(mList.listName);
},
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListDetails(
myList: mList,
))); // Go to list details page with my list send as parameters to the next screen
},
child: Align(
alignment: Alignment.center,
child: Container(
width: MediaQuery.of(context).size.width * 0.85,
height: 135,
child: Card(
elevation: 4,
shadowColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(13),
),
margin: EdgeInsets.only(bottom: 10, top: 20),
child: Stack(children: [
Align(
alignment: Alignment.centerLeft,
child: Container(
margin: EdgeInsets.only(left: 10),
child: SvgPicture.asset('assets/chart-list.svg',
width: 65, height: 65),
),
),
Container(
margin: EdgeInsets.only(
left: MediaQuery.of(context).size.width * 0.25,
top: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 139,
child: Text(
'${mList.listName}',
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: TextStyle(
color: Colors.black,
fontSize: 17,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w600),
),
),
Container(
margin: EdgeInsets.only(top: 5),
alignment: Alignment.center,
width: MediaQuery.of(context).size.width *
0.15,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(11),
bottomRight:
Radius.circular(11))),
child: Text(
'0/99',
style: TextStyle(
color: Colors.white,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w600),
)),
]),
),
Align(
alignment: Alignment.centerRight,
child: IconButton(
icon: FaIcon(
FontAwesomeIcons.ellipsisV,
size: 20,
color: Colors.black,
),
onPressed: () {}),
),
]),
)),
),
),
);
},
childCount: myListProvider.myList == null
? myListLength
: myListProvider.myList.length),
)
]),
);
}
}
ListDetails.dart
import 'package:flutter/material.dart';
import 'package:grocery_app/models/mylist.dart'; // import MyList model
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // import Font Awesome package
import 'package:flutter_svg/flutter_svg.dart'; // import svg module
import 'package:grocery_app/models/item.dart'; // import Item model
import 'package:grocery_app/screens/AddItem.dart'; // import add new item page
class ListDetails extends StatefulWidget {
final MyList myList;
ListDetails({Key key, @required this.myList}) : super(key: key);
@override
_ListDetailsState createState() => _ListDetailsState(myList);
}
class _ListDetailsState extends State<ListDetails> {
final MyList myList;
_ListDetailsState(this.myList);
var myListItemLength =
0; // Default list item length when the list just created
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddItem(myList: myList))); // Go to add new item page
},
child: FaIcon(
FontAwesomeIcons.plus,
size: 30,
),
backgroundColor: Colors.deepPurple[900],
),
backgroundColor: Colors.white,
body: CustomScrollView(
slivers: [
SliverAppBar(
backgroundColor: Colors.deepPurple[900],
leading: IconButton(
icon: Icon(
Icons.keyboard_arrow_left,
size: 30,
color: Colors.white,
),
onPressed: () {
Navigator.of(context).popUntil(
(route) => route.isFirst); // Return to My List page
}),
expandedHeight: 200.0,
flexibleSpace: Stack(
children: [
Container(
alignment: Alignment.center,
margin: EdgeInsets.only(bottom: 10),
child: SvgPicture.asset('assets/chart-list.svg',
width: 110, height: 110)),
Container(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(bottom: 25),
child: Text(
myList.listName,
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w600),
),
)
],
),
),
SliverToBoxAdapter(
child: Container(
color: Colors.deepPurple[900],
height: 20,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(70),
topRight: Radius.circular(70)),
child: Container(
height: 20,
width: double.infinity,
color: Colors.white,
),
),
],
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
Item itm = myList.items[index];
return Dismissible(
background: Container(
padding: EdgeInsets.only(left: 15),
color: Colors.green,
child: Align(
alignment: Alignment.centerLeft,
child: FaIcon(
FontAwesomeIcons.check,
size: 23,
color: Colors.white,
),
)),
secondaryBackground: Container(
padding: EdgeInsets.only(right: 15),
color: Colors.red,
child: Align(
alignment: Alignment.centerRight,
child: FaIcon(
FontAwesomeIcons.times,
size: 23,
color: Colors.white,
),
)),
// Each Dismissible must contain a Key. Keys allow Flutter to
// uniquely identify widgets.
key: UniqueKey(), // Object key to specify which element
onDismissed: (DismissDirection direction) {
if (direction == DismissDirection.startToEnd) {
// if swipe item to right, then item checked is set to true
setState(() {
itm.checked = true;
myList.items.removeAt(index); // Remove item from list
myList.items.add(
itm); // Add item to bottom with checked status is true
});
Scaffold.of(context).showSnackBar(SnackBar(
content: Text("Item checked"))); // Show snackbar
print(myList.items.length);
print(index);
print(itm.itemName);
} else if (direction == DismissDirection.endToStart) {
// if swipe item to left, then item checked is set to false
setState(() {
itm.checked = false;
myList.items.removeAt(index); // Remove item from list
myList.items.add(itm);
});
Scaffold.of(context).showSnackBar(SnackBar(
content: Text("Item unchecked"))); // Show snackbar
print(myList.items.length);
print(index);
print(itm.itemName);
}
},
child: Align(
alignment: Alignment.center,
child: Container(
margin: EdgeInsets.only(bottom: 10),
width: MediaQuery.of(context).size.width * 0.85,
height: 99,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
color: itm.checked ? Colors.green : Colors.purple[900],
child: Row(
children: [
Container(
margin: EdgeInsets.only(left: 15, right: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10)),
width: 50,
height: 50,
child: Icon(
Icons.shopping_cart,
color: itm.checked
? Colors.green
: Colors.purple[900],
size: 20,
),
),
Container(
margin: EdgeInsets.only(top: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
itm.itemName,
style: TextStyle(
color: Colors.white,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w600,
fontSize: 14),
),
Text(
'Qty: ${itm.itemQty}',
style: TextStyle(
color: Colors.white,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w600,
fontSize: 14),
),
Text(
'Price: ${itm.itemPrice.toStringAsFixed(2)}',
style: TextStyle(
color: Colors.white,
fontFamily: 'Open Sans',
fontWeight: FontWeight.w600,
fontSize: 14),
)
]),
),
],
),
),
),
));
},
childCount: myList.items == null
? myListItemLength
: myList.items.length))
],
),
);
}
}