I'm trying to use MultiProvider with FutureBuilder.
Is there source code that uses Firestore with Riverpod or any package to create a cart?
Also, how can we use Riverpod and Cart along with Firestore?
class MyApp extends StatelessWidget {
MyApp({super.key});
// List<CartModel> cartModels = new List<CartModel>.empty(growable: true);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
return MaterialApp.router(
routerConfig: _router,
);
}
}
class MainPage extends StatelessWidget {
const MainPage({super.key});
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
final user = FirebaseAuth.instance.currentUser;
devtools.log(user.toString());
if (user != null) {
if (user.emailVerified) {
devtools.log("verified");
return const HomePage();
} else {
devtools.log("not verified!");
return const Login();
}
} else {
return const Login();
}
default:
return const CircularProgressIndicator();
}
},
);
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
// Using MultiProvider is convenient when providing multiple objects.
return MultiProvider(
providers: [
// In this sample app, CatalogModel never changes, so a simple Provider
// is sufficient.
Provider(create: (context) => CatalogModel()),
// CartModel is implemented as a ChangeNotifier, which calls for the use
// of ChangeNotifierProvider. Moreover, CartModel depends
// on CatalogModel, so a ProxyProvider is needed.
ChangeNotifierProxyProvider<CatalogModel, CartModel>(
create: (context) => CartModel(),
update: (context, catalog, cart) {
if (cart == null) throw ArgumentError.notNull('cart');
cart.catalog = catalog;
return cart;
},
),
],
child: const MaterialApp(
home: MyCatalog(),
),
);
}
}
class MyCatalog extends StatelessWidget {
const MyCatalog({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
_MyAppBar(),
const SliverToBoxAdapter(child: SizedBox(height: 12)),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => _MyListItem(index)),
),
],
),
);
}
}
class _AddButton extends StatelessWidget {
final Item item;
const _AddButton({required this.item});
@override
Widget build(BuildContext context) {
// The context.select() method will let you listen to changes to
// a *part* of a model. You define a function that "selects" (i.e. returns)
// the part you're interested in, and the provider package will not rebuild
// this widget unless that particular part of the model changes.
//
// This can lead to significant performance improvements.
var isInCart = context.select<CartModel, bool>(
// Here, we are only interested whether [item] is inside the cart.
(cart) => cart.items.contains(item),
);
return TextButton(
onPressed: isInCart
? null
: () {
// If the item is not in cart, we let the user add it.
// We are using context.read() here because the callback
// is executed whenever the user taps the button. In other
// words, it is executed outside the build method.
var cart = context.read<CartModel>();
cart.add(item);
},
style: ButtonStyle(
overlayColor: MaterialStateProperty.resolveWith<Color?>((states) {
if (states.contains(MaterialState.pressed)) {
return Theme.of(context).primaryColor;
}
return null; // Defer to the widget's default.
}),
),
child: isInCart
? const Icon(Icons.check, semanticLabel: 'ADDED')
: const Text('ADD'),
);
}
}
class _MyAppBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SliverAppBar(
title: Text('Catalog', style: Theme.of(context).textTheme.displayLarge),
floating: true,
actions: [
IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const MyCart()),
);
},
),
],
);
}
}
class _MyListItem extends StatelessWidget {
final int index;
const _MyListItem(this.index);
@override
Widget build(BuildContext context) {
var item = context.select<CatalogModel, Item>(
// Here, we are only interested in the item at [index]. We don't care
// about any other change.
(catalog) => catalog.getByPosition(index),
);
var textTheme = Theme.of(context).textTheme.titleLarge;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: LimitedBox(
maxHeight: 48,
child: Row(
children: [
AspectRatio(
aspectRatio: 1,
child: Container(
color: item.color,
),
),
const SizedBox(width: 24),
Expanded(
child: Text(item.name, style: textTheme),
),
const SizedBox(width: 24),
_AddButton(item: item),
],
),
),
);
}
}
class MyCart extends StatelessWidget {
const MyCart({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Cart', style: Theme.of(context).textTheme.displayLarge),
backgroundColor: Colors.blue,
),
body: Container(
color: Colors.yellow,
child: Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(32),
child: _CartList(),
),
),
const Divider(height: 4, color: Colors.black),
_CartTotal()
],
),
),
);
}
}
class _CartList extends StatelessWidget {
@override
Widget build(BuildContext context) {
var itemNameStyle = Theme.of(context).textTheme.titleLarge;
// This gets the current state of CartModel and also tells Flutter
// to rebuild this widget when CartModel notifies listeners (in other words,
// when it changes).
var cart = context.watch<CartModel>();
return ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) => ListTile(
leading: const Icon(Icons.done),
trailing: IconButton(
icon: const Icon(Icons.remove_circle_outline),
onPressed: () {
cart.remove(cart.items[index]);
},
),
title: Text(
cart.items[index].name,
style: itemNameStyle,
),
),
);
}
}
class _CartTotal extends StatelessWidget {
@override
Widget build(BuildContext context) {
var hugeStyle =
Theme.of(context).textTheme.displayLarge!.copyWith(fontSize: 48);
return SizedBox(
height: 200,
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Another way to listen to a model's change is to include
// the Consumer widget. This widget will automatically listen
// to CartModel and rerun its builder on every change.
//
// The important thing is that it will not rebuild
// the rest of the widgets in this build method.
Consumer<CartModel>(
builder: (context, cart, child) =>
Text('\$${cart.totalPrice}', style: hugeStyle)),
const SizedBox(width: 24),
TextButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Buying not supported yet.')));
},
style: TextButton.styleFrom(foregroundColor: Colors.white),
child: const Text('BUY'),
),
],
),
),
);
}
}
class CartModel extends ChangeNotifier {
/// The private field backing [catalog].
late CatalogModel _catalog;
/// Internal, private state of the cart. Stores the ids of each item.
final List<int> _itemIds = [];
/// The current catalog. Used to construct items from numeric ids.
CatalogModel get catalog => _catalog;
set catalog(CatalogModel newCatalog) {
_catalog = newCatalog;
// Notify listeners, in case the new catalog provides information
// different from the previous one. For example, availability of an item
// might have changed.
notifyListeners();
}
/// List of items in the cart.
List<Item> get items => _itemIds.map((id) => _catalog.getById(id)).toList();
/// The current total price of all items.
int get totalPrice =>
items.fold(0, (total, current) => total + current.price);
/// Adds [item] to cart. This is the only way to modify the cart from outside.
void add(Item item) {
_itemIds.add(item.id);
// This line tells [Model] that it should rebuild the widgets that
// depend on it.
notifyListeners();
}
void remove(Item item) {
_itemIds.remove(item.id);
// Don't forget to tell dependent widgets to rebuild _every time_
// you change the model.
notifyListeners();
}
}
class CatalogModel {
static List<String> itemNames = [
'Code Smell',
'Control Flow',
'Interpreter',
'Recursion',
'Sprint',
'Heisenbug',
'Spaghetti',
'Hydra Code',
'Off-By-One',
'Scope',
'Callback',
'Closure',
'Automata',
'Bit Shift',
'Currying',
];
/// Get item by [id].
///
/// In this sample, the catalog is infinite, looping over [itemNames].
Item getById(int id) => Item(id, itemNames[id % itemNames.length]);
/// Get item by its position in the catalog.
Item getByPosition(int position) {
// In this simplified case, an item's position in the catalog
// is also its id.
return getById(position);
}
}
@immutable
class Item {
final int id;
final String name;
final Color color;
final int price = 42;
Item(this.id, this.name)
// To make the sample app look nicer, each item is given one of the
// Material Design primary colors.
: color = Colors.primaries[id % Colors.primaries.length];
@override
int get hashCode => id;
@override
bool operator ==(Object other) => other is Item && other.id == id;
}
Here is a complete example with Flutter that you can test.