using ChangeNotifier to add items into empty list - flutter

I already have data stored on firestore , but now i'd like to add this data into an empty list using ChangeNotifier , similar to this example -https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple .
so below i'd like to add data stored on firebase into the _cart list , with the method I tried below I got the error The argument type 'List<Menu>' can't be assigned to the parameter type 'Menu'(would like to know why?) , it also contains the ui for where i'd like to map the data from cart list into the dialog sheet:
class AddList extends ConsumerWidget {
const AddList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final menuAsync = ref.watch(menuProvider);
final model = ref.read(cartProvider);
return Scaffold(
appBar: AppBar(
title: const Text("menu"),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.black,
onPressed: () async {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("cart"),
content: Column(
children: const [
...model.cart.map((item) => Text(item.mealName)),
],
),
);
});
},
),
body: menuAsync.when(
data: (menu) => Column(
children: menu
.map(
(e) => Card(
child: ListTile(
title: Text(e.mealName),
subtitle: Text(e.price),
trailing: IconButton(
onPressed: () {
model.addProduct(menu);//where im getting error
},
icon: const Icon(Icons.add)))),
)
.toList(),
),
error: (e, s) => Center(child: Text("$e")),
loading: () => const Center(child: CircularProgressIndicator())),
);
}
}
below im using changenotifier to modify the cart list:
final cartProvider =
ChangeNotifierProvider<CartNotifier>((ref) => CartNotifier());
class CartNotifier extends ChangeNotifier {
final List<Menu> _cart = [];
List<Menu> get cart => _cart;
void addProduct(Menu menu) {
_cart.add(menu);
notifyListeners();
}
void removeProduct(Menu menu) {
_cart.remove(menu);
notifyListeners();
}
}
how im reading data from firestore :
final menuProvider = StreamProvider<List<Menu>>(
(ref) => ref.read(addMealRespositoryProvider).menuSearchStream);
Stream<List<Menu>> get menuSearchStream =>
_firestore.collection("menu").snapshots().map(
(event) => event.docs.map((e) => Menu.fromFirestore(e)).toList(),
);
snippet of my data model:
class Menu {
String mealName;
String price;
Menu({
required this.mealName,
required this.price,
});
Map<String, dynamic> toMap() {
return {
"mealName": mealName,
"price": price, },
factory Menu.fromFirestore(DocumentSnapshot doc) {
final map = doc.data() as Map<String, dynamic>;
return Menu(
mealName: map["mealName"] ?? '',
price: map["price"] ?? '',
);
}
}

Related

NotifyListeners is not rebuilding Consumer Widget

I am trying to build a chat application. but whenever i call a method to update the chat list
everything works, but the widget is not rebuilding.
i have tried with context.read() and context.watch() methods : Not working
i have tried with Provider.of(context,listen:true); : Not working
i have tried with ChangeNotifierProxyProvider : Not working
only restarting rebuilds the ui. any help would be appreciated.
below is my code.
void main() {
runApp(MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => UsersProvider()),
ChangeNotifierProvider(create: (context) => ChatProvider()),
], child: const MyApp()));
}
class UsersProvider extends ChangeNotifier {
UsersProvider(){
getChats();
}
List<Chats> get chats=> _chats;
List<Chats> _chats = [];
Future getChats() async {
final json = await UsersRepo().getChatLists();
if (json == null) {
return;
}
if (json['status']) {
ChatListModel _model = ChatListModel.fromJson(json);
_chats = _model.chats ?? [];
notifyListeners();
}
}
}
and the ui looks like
class ChatList extends StatelessWidget {
const ChatList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.appBg,
appBar: AppBar(
title: Text('Users'),
backgroundColor: AppColors.appBgLight,
),
body: chatListWidget(context),
);
}
Widget chatListWidget(BuildContext context) {
return Consumer<UsersProvider>(
builder: (BuildContext context, provider, Widget? child) {
return ListView.separated(
padding: EdgeInsets.symmetric(vertical: 20),
itemCount: provider.chats.length,
separatorBuilder: (c, i) => sbh(15),
itemBuilder: (c, i) {
Chats chat = provider.chats[i];
return ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (c) {
return ChatScreen(
user: Users(
name: chat.receiver?.name, sId: chat.receiver?.id),
);
},
),
);
context.read<ChatProvider>().connectToUser(chat.receiver?.id);
},
leading:
userImageWidget((chat.receiver?.name ?? "")[0].toUpperCase()),
subtitle: Text(
chat.lastMessage ?? "",
style: TextStyle(
color: Colors.white70,
fontSize: 12,
),
),
trailing: CircleAvatar(
radius: 15,
backgroundColor: Colors.green,
child: Center(
child: Text(
'${chat.unReadMessageCount}',
style: TextStyle(fontSize: 12, color: Colors.white),
)),
),
title: nameWidget(chat.receiver?.name ?? ''),
);
},
);
},
);
}
}
Chats model class
class Chats {
String? lastMessage;
String? messageTime;
int? unReadMessageCount;
Chats(
{this.lastMessage,
this.messageTime,
this.unReadMessageCount});
Chats.fromJson(Map<String, dynamic> json) {
lastMessage = json['lastMessage'];
messageTime = json['messageTime'];
unReadMessageCount = json['unReadMessageCount'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['lastMessage'] = this.lastMessage;
data['messageTime'] = this.messageTime;
data['unReadMessageCount'] = this.unReadMessageCount;
return data;
}
}
I suspect that your getChats() method is failing before it can execute the notifyListeners method.
You see, any "if" statement in dart has to evaluate to a boolean, so unless json['status'] returns true/false then the expression:
if (json['status'])
will throw an error that will end up in a failed future to be returned from your getChats() method.
It would be better to use a more specific condition for your if statement, maybe something like this (if json['status'] is supposed to contain the http response code for the call):
if (json['status'] == 200)
In the case json['status'] does indeed contains a boolean then there could be a few other reasons for your widget not rebuilding:
1- json['status'] always evaluates to false
2- The call to get the chats fails but you get a silent error since you don't have a handler for the failed future that is returned by the getChats method

Updating a page every time I revisit the page in BottomNavigationbar

In my app the user is able to store favorite items on a different page in the bottom navigation bar. My problem is that the page does not refresh properly. If you add a favorite it gets displayed only when restarting the app.
If the favorites page is in the same widget hierarchy of the respective bottomnavitem the function works fine.
https://pastebin.com/nZ2jrLqK
class Favorites extends StatefulWidget {
const Favorites({Key? key}) : super(key: key);
#override
_FavoritesState createState() => _FavoritesState();
}
class _FavoritesState extends State<Favorites> {
// ignore: prefer_typing_uninitialized_variables
var database;
List<Mechanism> people = <Mechanism>[];
Future initDb() async {
database = await openDatabase(
join(await getDatabasesPath(), 'person_database.db'),
onCreate: (db, version) {
return db.execute(
"CREATE TABLE person(id INTEGER PRIMARY KEY, name TEXT, height TEXT, mass TEXT, hair_color TEXT, skin_color TEXT, eye_color TEXT, birth_year TEXT, gender TEXT)",
);
},
version: 1,
);
getPeople().then((value) {
setState(() {
people = value;
});
});
}
Future<List<Mechanism>> getPeople() async {
final Database db = await database;
final List<Map<String, dynamic>> maps = await db.query('person');
return List.generate(maps.length, (i) {
return Mechanism(
id: maps[i]['id'],
name: maps[i]['name'] as String,
);
});
}
#override
void initState() {
super.initState();
initDb();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: backGround,
appBar: AppBar(
backgroundColor: appbarColor,
title: const Text("Favorites"),
),
body: ListView.builder(
itemCount: people.length,
itemBuilder: (context, index) {
var person = people[index];
return ListTile(
title: Text(
person.name,
style: const TextStyle(color: titleColor),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
MechanismDtl(mechanism: person, id: index)),
);
},
);
}),
);
}
}
Edit: page where the user can store the items
class MarkFavs extends StatefulWidget {
const MarkFavs({Key key}) : super(key: key);
#override
_MarkFavsState createState() => _MarkFavsState();
}
class _MarkFavsState extends State<MarkFavs> {
TextEditingController searchController = TextEditingController();
List<People> shownList = <People>[
People(name: 'Test', id: 1),
People(name: 'Test2', id: 2),
People(name: 'Test3', id: 3)
];
List<People> initialData = <People>[
People(name: 'Test', id: 1),
People(name: 'Test2', id: 2),
People(name: 'Test3', id: 3)
];
void queryPeople(String queryString) {
if (kDebugMode) {
print("queryString = $queryString");
}
setState(() {
shownList = initialData.where((string) {
if (string.name.toLowerCase().contains(queryString.toLowerCase())) {
return true;
} else {
return false;
}
}).toList();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: backGround,
appBar: AppBar(
backgroundColor: appbarColor,
title: const Text('Detail'),
),
body: Column(
children: <Widget>[
TextButton.icon(
label: const Text('Favorites'),
icon: const Icon(
Icons.storage,
color: titleColor,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const Favorites()),
);
},
),
Expanded(
child: PeopleList(
people: shownList,
),
),
],
),
);
}
}
class PeopleList extends StatelessWidget {
final List<People> people;
const PeopleList({Key key, this.people}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: people.length,
itemBuilder: (context, index) {
var person = people[index];
var name = person.name;
return ListTile(
title: Text(
name,
style: const TextStyle(color: titleColor),
),
onTap: () {
person.id = index;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
MechanismDtl(mechanism: person, id: index)),
);
},
);
},
);
}
}
Maybe not the most efficient method, but if you provide a passback to the Favourites class from the parent widget you can call a setState in the parent widget (assuming the parent widget reloads the database).
class Favorites extends StatefulWidget {
const Favorites({Key? key, this.passback}) : super(key: key);
final Function passback;
#override
_FavoritesState createState() => _FavoritesState();
}
Then the passback would look like:
passback() {
setState(){
//Reload db
}
}
And pass it into Favourite (does not work with named routes AFAIK)
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Favourites(passback: passback)),
);
Then just call passback when the user adds the item to their favourites.
Solved it quite ugly but it is working. If you know a better way please let me know!
class Favorites extends StatefulWidget {
const Favorites({Key? key}) : super(key: key);
#override
_FavoritesState createState() => _FavoritesState();
}
class _FavoritesState extends State<Favorites> {
// ignore: prefer_typing_uninitialized_variables
var database;
List<TestItems> items = <TestItems>[];
Future initDb() async {
database = await openDatabase(
join(await getDatabasesPath(), 'person_database.db'),
onCreate: (db, version) {
db.execute(
"CREATE TABLE person(id INTEGER PRIMARY KEY, name TEXT)",
);
},
version: 1,
);
getItems().then((value) {
setState(() {
items = value;
});
});
}
Future<List<TestItems>> getItems() async {
final Database db = await database;
final List<Map<String, dynamic>> maps = await db.query('person');
return List.generate(maps.length, (i) {
return TestItems(
id: maps[i]['id'],
name: maps[i]['name'] as String,
);
});
}
Future<void> deleteDB(int id) async {
final db = await database;
await db.delete(
'person',
where: "id = ?",
whereArgs: [id],
);
}
#override
void initState() {
super.initState();
initDb();
}
#override
Widget build(BuildContext context) {
// Updates the page every time the build method gets called
initDb().then((value) {
setState(() {
items = value;
});
});
return Scaffold(
appBar: AppBar(
title: const Text("Favorites"),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
var item = items[index];
return ListTile(
title: Text(
item.name,
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ItemDtl(items: item, id: index)),
);
},
trailing: IconButton(
color: Colors.red,
icon: const Icon(Icons.delete_forever_rounded),
onPressed: () {
deleteDB(item.id).then((value) {
getItems().then((value) {
setState(() {
items = value;
});
});
});
},
),
);
}));
}
}

Icon value not updating with provider and sqflite in flutter

I was making a simple cart app, it did well but cart count not showing when app is closed and reopened again.
I am using provider and calls fetchCartProducts() method when the app is opened. It calls fine. but cart badge widget itemcount is not changing at first time. only shows 0 at first time.
Future<void> fetchCartProducts() async {
final dataList = await DBHelper.getData('cart_food');
//convert dataList to _cartItems
final entries = dataList
.map((item) => CartModel(
item['id'],
item['price'].toDouble(),
item['productName'],
item['quantity'],
))
.map((cart) => MapEntry(cart.id, cart));
_cartItems = Map<String, CartModel>.fromEntries(entries);
print('inside fetchcart');
}
class HomeScreen extends StatefulWidget
{
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
{
Future<List<FoodItem>> _foodItems;
var _isInit = true;
#override
void initState() {
super.initState();
_foodItems = ApiService.getFoodItems();
Provider.of<CartProvider>(context, listen: false).fetchCartProducts();
setState(() {});
}
#override
void didChangeDependencies()
{
if (_isInit) {
Provider.of<CartProvider>(context).fetchCartProducts();
_isInit = false;
setState(() {});
}
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
final cart = Provider.of<CartProvider>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: const Text('Food Cart'),
actions: [
//this is not updating when the app is closed and opened again.
Consumer<CartProvider>(
builder: (_, cartprovider, ch) => Badge(
child: ch,
value: cartprovider.itemCount.toString(),
),
child: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) {
return CartScreen();
}),
);
},
),
),
],
),
body: FutureBuilder<List<FoodItem>>(
future: _foodItems,
builder: (conext, snapshot) => !snapshot.hasData
? const Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
FoodItem foodItem = snapshot.data[index];
return ListTile(
title: Text(foodItem.productName),
subtitle: Text(foodItem.variant),
trailing: IconButton(
onPressed: () {
cart.addToCart(
foodItem.storeid.toString(),
foodItem.productName,
1,
foodItem.price,
);
setState(() {});
},
icon: const Icon(Icons.shopping_cart),
),
);
},
),
),
);
}
}
otherwise when item added to cart, it working fine. the data loss when reopened. how to get total count when the app starts?
In order to rebuild Consumer you need to call notifyListeners() inside your CartProvider
Add notifyListeners() to your fetchCartProducts() after assigning the value to _cartItems = Map<String, CartModel>.fromEntries(entries);
Future<void> fetchCartProducts() async {
final dataList = await DBHelper.getData('cart_food');
//convert dataList to _cartItems
final entries = dataList
.map((item) => CartModel(
item['id'],
item['price'].toDouble(),
item['productName'],
item['quantity'],
))
.map((cart) => MapEntry(cart.id, cart));
_cartItems = Map<String, CartModel>.fromEntries(entries);
notifyListeners(); // <------- this line
print('inside fetchcart');
}

Widget don't rebuild with riverpod when changing state from child

I am using riverpod to build my app and i am struggling to build a simple add to favorite feature. I have a list of products and a child Consumer widget that is a card with add to favorite button.
When I change the state of the products from the child cards the ui don't rebuild.
here is the git repository https://github.com/nisa10880/riverpod_difficulties
Consumer(
builder: (context, watch, child) =>
watch(getProductsFutureProvider).when(
data: (p) {
final products = watch(productListStateProvider).state;
return ListView.builder(
itemCount: products.length,
itemBuilder: (BuildContext context, int index) =>
ProductCard(product: products[index]));
},
loading: () => CircularProgressIndicator(),
error: (e, s) => Text(e)))
final productServiceProvider = Provider<ProductService>((ref) {
return ProductService();
});
class ProductService {
ProductService();
Future<List<ProductModel>> getAllProducts() async {
await Future.delayed(Duration(seconds: 1));
return [
ProductModel(id: '1', title: 'product 1', isFavorite: false),
ProductModel(id: '2', title: 'product 2', isFavorite: false),
ProductModel(id: '3', title: 'product 3', isFavorite: false),
ProductModel(id: '4', title: 'product 4', isFavorite: false)
];
}
}
final productListStateProvider = StateProvider<List<ProductModel>>((ref) {
return [];
});
final getProductsFutureProvider =
FutureProvider.autoDispose<List<ProductModel>>((ref) async {
ref.maintainState = true;
final evtentService = ref.watch(productServiceProvider);
final products = await evtentService.getAllProducts();
ref.read(productListStateProvider).state = products;
return products;
});
class ProductCard extends ConsumerWidget {
final ProductModel product;
const ProductCard({
#required this.product,
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context, ScopedReader watch) {
return Card(
child: Column(
children: <Widget>[
Text(product.title),
product.isFavorite
? IconButton(
icon: Icon(Icons.favorite),
color: Colors.red,
onPressed: () {
product.isFavorite = !product.isFavorite;
},
)
: IconButton(
icon: Icon(Icons.favorite_border),
color: Colors.black54,
onPressed: () {
product.isFavorite = !product.isFavorite;
},
)
],
));
}
}
The problem is you're updating an inner value of ProductModel instead of the state (the state is the List<ProductModel>) so it won't trigger a rebuild, you either try something like this:
onPressed: () {
List<ProductModel> productList = context.read(productListStateProvider).state;
int index = productList.indexWhere((e) => e.id == product.id);
productList[index].isFavorite = !product.isFavorite;
context.read(productListStateProvider).state = productList;
},
or you try to use a ChangeNotifierProvider so you can call notifyListeners() when you feel you made a change to a value

Flutter Passing Data:: Getter not found

I'm trying to make a splash screen where the user chooses a city, with each city having its own API via url_items variable to access its data to populate the ListViews in the second screen.
When I call the data in the second screen, via http.Response response = await http.get(url_items); I get an error Getter not found: url_items
How do I do the Getter properly?
class Splash extends StatefulWidget {
_SplashState createState() => _SplashState();
}
class _SplashState extends State<Splash> {
String dropdownValue = 'NY';
String city = 'NY';
String url_items = 'https://ny.com/items';
String url_stores = 'https://ny.com/stores';
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
ListTile(
title: DropdownButton<String>(
value: dropdownValue,
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
city = newValue;
if (city == 'NY'){url_items = 'https://ny.com/items';} else {url_items = 'https://chicago.com/items';}
});
},
items: <String>['NY', 'Chicago'].map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}
).toList(),
),
),
RaisedButton(
child: Text('View Items'),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => Items(url_items: url_items, url_stores: url_stores, city: city)
),
);
},
),
],
),
);
}
}
class Items extends StatelessWidget {
var url_items="";
var url_stores="";
var city="";
Items({Key key, this.url_items, this.url_stores, this.city}) : super(key: key);
static Future<List<Item>> getItems() async {
http.Response response = await http.get(url_items);
String data = response.body;
List collection = json.decode(data);
Iterable<Item> _items = collection.map((_) => Item.fromJson(_));
return _items.toList();
}
Stream<List<Item>> get itemListView => Stream.fromFuture(getItems());
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: itemListView,
builder: (BuildContext context, AsyncSnapshot<List<Item>> snapshot) {
List<Item> items = snapshot.data;
return ListView.separated(
itemBuilder: (BuildContext context, int index) {
Item item = items[index];
return ListTile(
title: Html(data: item.name),
subtitle: Html(data: item.userName),
onTap: () {
Navigator.push(context,
MaterialPageRoute(
builder: (context) => ItemDetail(item.name, item.userName..),
),
);
},
);
},
separatorBuilder: (context, index) => Divider(),
);
}
}
),
);
}
}
Instance variables/members cannot be accessed from a static method. so try changing
static Future<List<Item>> getItems() async {...}
to
Future<List<Item>> getItems() async {...}