Issue
I wrote a Flutter-app and managed my data with the providers package. With an icon button you can mark a product as a favorite. With my product-item which is loaded into a gridview the UI updates if I favorite a product(the icon changes from a border to a filled icon). But on the detail page which shows more details of a product the UI doesn’t update if I press the button.
This is the product class:
class Product with ChangeNotifier {
final String id;
final String title;
final String description;
final double price;
final String imageUrl;
bool isFavorite;
Product({
@required this.id,
@required this.title,
@required this.description,
@required this.price,
@required this.imageUrl,
this.isFavorite = false,
});
void toggleFavoriteStatus() {
isFavorite = !isFavorite;
notifyListeners();
}
}
This is the products class where I manage my data:
import 'package:flutter/material.dart';
import './product.dart';
class Products with ChangeNotifier {
List<Product> _items = [
Product(
id: 'p1',
title: 'Red Shirt',
description: 'A red shirt - it is pretty red!',
price: 29.99,
imageUrl:
'https://cdn.pixabay.com/photo/2016/10/02/22/17/red-t-shirt-1710578_1280.jpg',
),
Product(
id: 'p2',
title: 'Trousers',
description: 'A nice pair of trousers.',
price: 59.99,
imageUrl:
'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Trousers%2C_dress_%28AM_1960.022-8%29.jpg/512px-Trousers%2C_dress_%28AM_1960.022-8%29.jpg',
),
Product(
id: 'p3',
title: 'Yellow Scarf',
description: 'Warm and cozy - exactly what you need for the winter.',
price: 19.99,
imageUrl:
'https://live.staticflickr.com/4043/4438260868_cc79b3369d_z.jpg',
),
Product(
id: 'p4',
title: 'A Pan',
description: 'Prepare any meal you want.',
price: 49.99,
imageUrl:
'https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Cast-Iron-Pan.jpg/1024px-Cast-Iron-Pan.jpg',
),
];
List<Product> get items {
return [..._items];
}
Product findById(String id) {
return _items.firstWhere((prod) => prod.id == id);
}
}
This is the product-item:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../screens/product_detail_screen.dart';
import '../providers/product.dart';
class ProductItem extends StatelessWidget {
// final String id;
// final String title;
// final String imageUrl;
// ProductItem(this.id, this.title, this.imageUrl);
@override
Widget build(BuildContext context) {
final product = Provider.of<Product>(context, listen: false);
return ClipRRect(
borderRadius: BorderRadius.circular(10),
child: GridTile(
child: GestureDetector(
onTap: () {
Navigator.of(context).pushNamed(
ProductDetailScreen.routeName,
arguments: product.id,
);
},
child: Image.network(
product.imageUrl,
fit: BoxFit.cover,
),
),
footer: GridTileBar(
backgroundColor: Colors.black87,
leading: Consumer<Product>(
builder: (ctx, product, child) => IconButton(
icon: Icon(
product.isFavorite ? Icons.favorite : Icons.favorite_border,
),
color: Theme.of(context).accentColor,
onPressed: () {
product.toggleFavoriteStatus();
},
),
),
title: Text(
product.title,
textAlign: TextAlign.center,
),
trailing: IconButton(
icon: Icon(
Icons.shopping_cart,
),
onPressed: () {},
color: Theme.of(context).accentColor,
),
),
),
);
}
}
This is the detail-page:
import 'dart:html';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/products.dart';
class ProductDetailScreen extends StatelessWidget {
// final String title;
// final double price;
// ProductDetailScreen(this.title, this.price);
static const routeName = '/product-detail';
@override
Widget build(BuildContext context) {
final productId =
ModalRoute.of(context).settings.arguments as String; // is the id!
final loadedProduct = Provider.of<Products>(
context,
listen: false,
).findById(productId);
return Scaffold(
appBar: AppBar(
title: Text(loadedProduct.title),
actions: <Widget>[
IconButton(
onTap: () {
loadedProduct.toggleFavoriteStatus();
},
child: Icon(
loadedProduct.isFavorite ? Icons.star : Icons.star_border,
color: Colors.white,
),
),
],
),
);
}
}
Thanks to everybody who tries to find a solution for this.
Solution
It won’t update the UI because there is no Provider or Consumer of type Product
to listen to those changes in the new route. You have Provider<Products>
and extract a Product
from there, but that’s it, the route doesn’t depend on/listen that Product
because there is no Provider<Product>
in that part of the widget tree
I would recommend to expose the value of the provider to the new route instead of passing the id and creating a new Provider.
onTap: () {
Navigator.of(context).pushNamed(
ProductDetailScreen.routeName,
arguments: product, //pass the Product instead of the id
);
},
class ProductDetailScreen extends StatelessWidget {
// final String title;
// final double price;
// ProductDetailScreen(this.title, this.price);
static const routeName = '/product-detail';
@override
Widget build(BuildContext context) {
return ChangeNotifierProvier<Product>.value(
value: ModalRoute.of(context).settings.arguments as Product
child: Consumer<Product>(
builder: (context, loadedProduct, _){
return Scaffold(
appBar: AppBar(
title: Text(loadedProduct.title),
actions: <Widget>[
IconButton(
onTap: () {
loadedProduct.toggleFavoriteStatus();
},
child: Icon(
loadedProduct.isFavorite ? Icons.star : Icons.star_border,
color: Colors.white,
),
),
],
),
);
}
)
);
}
}
Answered By – EdwynZN
Answer Checked By – David Goodson (FlutterFixes Volunteer)