我在数据模型上遇到麻烦,因为我使用另一个页面中的添加按钮,因此无法通过notifylistener()将项目添加到MyList模型内的列表中,以通知UI有关更改。使用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))
],
),
);
}
}