18 KiB
18 KiB
Quick Start Guide - Riverpod 3.0 Providers
Setup Complete! ✅
All Riverpod 3.0 providers have been successfully implemented and code has been generated.
Quick Import Reference
Import All Cart Providers
import 'package:retail/features/home/presentation/providers/providers.dart';
Import All Product Providers
import 'package:retail/features/products/presentation/providers/providers.dart';
Import All Category Providers
import 'package:retail/features/categories/presentation/providers/providers.dart';
Import All Settings Providers
import 'package:retail/features/settings/presentation/providers/providers.dart';
Import Core Providers (Sync, Network)
import 'package:retail/core/providers/providers.dart';
Usage Examples
1. Display Products
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:retail/features/products/presentation/providers/providers.dart';
class ProductsPage extends ConsumerWidget {
const ProductsPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final productsAsync = ref.watch(productsProvider);
return Scaffold(
appBar: AppBar(title: const Text('Products')),
body: productsAsync.when(
data: (products) => GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.75,
),
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return Card(
child: Column(
children: [
Text(product.name),
Text('\$${product.price.toStringAsFixed(2)}'),
ElevatedButton(
onPressed: () {
ref.read(cartProvider.notifier).addItem(product, 1);
},
child: const Text('Add to Cart'),
),
],
),
);
},
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
),
);
}
}
2. Search and Filter Products
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:retail/features/products/presentation/providers/providers.dart';
import 'package:retail/features/categories/presentation/providers/providers.dart';
class FilteredProductsPage extends ConsumerWidget {
const FilteredProductsPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final filteredProducts = ref.watch(filteredProductsProvider);
final searchQuery = ref.watch(searchQueryProvider);
final categoriesAsync = ref.watch(categoriesProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(60),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: const InputDecoration(
hintText: 'Search products...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
onChanged: (value) {
ref.read(searchQueryProvider.notifier).setQuery(value);
},
),
),
),
),
body: Column(
children: [
// Category filter chips
categoriesAsync.when(
data: (categories) => SizedBox(
height: 50,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categories.length + 1,
itemBuilder: (context, index) {
if (index == 0) {
return Padding(
padding: const EdgeInsets.all(4.0),
child: FilterChip(
label: const Text('All'),
selected: ref.watch(selectedCategoryProvider) == null,
onSelected: (_) {
ref.read(selectedCategoryProvider.notifier).clearSelection();
},
),
);
}
final category = categories[index - 1];
return Padding(
padding: const EdgeInsets.all(4.0),
child: FilterChip(
label: Text(category.name),
selected: ref.watch(selectedCategoryProvider) == category.id,
onSelected: (_) {
ref.read(selectedCategoryProvider.notifier).selectCategory(category.id);
},
),
);
},
),
),
loading: () => const SizedBox.shrink(),
error: (_, __) => const SizedBox.shrink(),
),
// Products grid
Expanded(
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: filteredProducts.length,
itemBuilder: (context, index) {
final product = filteredProducts[index];
return Card(
child: Column(
children: [
Text(product.name),
Text('\$${product.price}'),
],
),
);
},
),
),
],
),
);
}
}
3. Shopping Cart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:retail/features/home/presentation/providers/providers.dart';
class CartPage extends ConsumerWidget {
const CartPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final cartItems = ref.watch(cartProvider);
final cartTotal = ref.watch(cartTotalProvider);
return Scaffold(
appBar: AppBar(
title: Text('Cart (${cartTotal.itemCount})'),
actions: [
IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () {
ref.read(cartProvider.notifier).clearCart();
},
),
],
),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: cartItems.length,
itemBuilder: (context, index) {
final item = cartItems[index];
return ListTile(
title: Text(item.productName),
subtitle: Text('\$${item.price.toStringAsFixed(2)}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () {
ref.read(cartProvider.notifier).decrementQuantity(item.productId);
},
),
Text('${item.quantity}'),
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
ref.read(cartProvider.notifier).incrementQuantity(item.productId);
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
ref.read(cartProvider.notifier).removeItem(item.productId);
},
),
],
),
);
},
),
),
// Cart summary
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Subtotal:'),
Text('\$${cartTotal.subtotal.toStringAsFixed(2)}'),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Tax (${(cartTotal.taxRate * 100).toStringAsFixed(0)}%):'),
Text('\$${cartTotal.tax.toStringAsFixed(2)}'),
],
),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Total:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
Text('\$${cartTotal.total.toStringAsFixed(2)}', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
],
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: cartItems.isEmpty ? null : () {
// Handle checkout
},
child: const Text('Checkout'),
),
],
),
),
),
],
),
);
}
}
4. Settings Page
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:retail/features/settings/presentation/providers/providers.dart';
class SettingsPage extends ConsumerWidget {
const SettingsPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final settingsAsync = ref.watch(settingsProvider);
final themeMode = ref.watch(themeModeProvider);
return Scaffold(
appBar: AppBar(title: const Text('Settings')),
body: settingsAsync.when(
data: (settings) => ListView(
children: [
// Theme settings
ListTile(
title: const Text('Theme'),
subtitle: Text(themeMode.toString().split('.').last),
trailing: SegmentedButton<ThemeMode>(
segments: const [
ButtonSegment(value: ThemeMode.light, label: Text('Light')),
ButtonSegment(value: ThemeMode.dark, label: Text('Dark')),
ButtonSegment(value: ThemeMode.system, label: Text('System')),
],
selected: {themeMode},
onSelectionChanged: (Set<ThemeMode> newSelection) {
ref.read(settingsProvider.notifier).updateThemeMode(newSelection.first);
},
),
),
// Language
ListTile(
title: const Text('Language'),
subtitle: Text(settings.language),
trailing: DropdownButton<String>(
value: settings.language,
items: ref.watch(supportedLanguagesProvider).map((lang) {
return DropdownMenuItem(
value: lang.code,
child: Text(lang.nativeName),
);
}).toList(),
onChanged: (value) {
if (value != null) {
ref.read(settingsProvider.notifier).updateLanguage(value);
}
},
),
),
// Tax rate
ListTile(
title: const Text('Tax Rate'),
subtitle: Text('${(settings.taxRate * 100).toStringAsFixed(1)}%'),
trailing: SizedBox(
width: 100,
child: TextField(
keyboardType: TextInputType.number,
decoration: const InputDecoration(suffix: Text('%')),
onSubmitted: (value) {
final rate = double.tryParse(value);
if (rate != null) {
ref.read(settingsProvider.notifier).updateTaxRate(rate / 100);
}
},
),
),
),
// Store name
ListTile(
title: const Text('Store Name'),
subtitle: Text(settings.storeName),
trailing: IconButton(
icon: const Icon(Icons.edit),
onPressed: () {
// Show dialog to edit
},
),
),
],
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
),
);
}
}
5. Sync Data
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:retail/core/providers/providers.dart';
class SyncButton extends ConsumerWidget {
const SyncButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final syncAsync = ref.watch(syncStatusProvider);
final lastSync = ref.watch(lastSyncTimeProvider);
return syncAsync.when(
data: (syncResult) {
if (syncResult.isSyncing) {
return const CircularProgressIndicator();
}
return Column(
children: [
ElevatedButton.icon(
icon: const Icon(Icons.sync),
label: const Text('Sync Data'),
onPressed: () {
ref.read(syncStatusProvider.notifier).syncAll();
},
),
if (lastSync != null)
Text(
'Last synced: ${lastSync.toString()}',
style: Theme.of(context).textTheme.bodySmall,
),
if (syncResult.isOffline)
const Text(
'Offline - No internet connection',
style: TextStyle(color: Colors.orange),
),
if (syncResult.isFailed)
Text(
'Sync failed: ${syncResult.message}',
style: const TextStyle(color: Colors.red),
),
if (syncResult.isSuccess)
const Text(
'Sync successful',
style: TextStyle(color: Colors.green),
),
],
);
},
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Error: $error'),
);
}
}
6. Main App Setup
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:retail/features/settings/presentation/providers/providers.dart';
void main() {
runApp(
// Wrap entire app with ProviderScope
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends ConsumerWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeModeProvider);
return MaterialApp(
title: 'Retail POS',
themeMode: themeMode,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: const HomePage(),
);
}
}
Common Patterns
Pattern 1: Optimized Watching (Selective Rebuilds)
// Bad - rebuilds on any cart change
final cart = ref.watch(cartProvider);
// Good - rebuilds only when length changes
final itemCount = ref.watch(cartProvider.select((items) => items.length));
Pattern 2: Async Operations
// Always use AsyncValue.guard for error handling
Future<void> syncData() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
return await dataSource.fetchData();
});
}
Pattern 3: Listening to Changes
ref.listen(cartProvider, (previous, next) {
if (next.isNotEmpty && previous?.isEmpty == true) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Item added to cart')),
);
}
});
Pattern 4: Invalidate and Refresh
// Invalidate - resets provider
ref.invalidate(productsProvider);
// Refresh - invalidate + read immediately
final products = ref.refresh(productsProvider);
Testing Providers
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:retail/features/home/presentation/providers/providers.dart';
void main() {
test('Cart adds items correctly', () {
final container = ProviderContainer();
addTearDown(container.dispose);
// Initial state
expect(container.read(cartProvider), isEmpty);
// Add item
final product = Product(/*...*/);
container.read(cartProvider.notifier).addItem(product, 1);
// Verify
expect(container.read(cartProvider).length, 1);
expect(container.read(cartItemCountProvider), 1);
});
}
Next Steps
- ✅ Providers are implemented and generated
- ✅ All dependencies are installed
- ✅ Code generation is complete
- 🔄 Replace mock data sources with Hive implementations
- 🔄 Build UI pages using the providers
- 🔄 Add error handling and loading states
- 🔄 Write tests for providers
- 🔄 Implement actual API sync
Need Help?
- Full Documentation: See
PROVIDERS_DOCUMENTATION.md - Provider List: See
PROVIDERS_SUMMARY.md - Riverpod Docs: https://riverpod.dev
All Providers Ready to Use! 🚀
Start building your UI with confidence - all state management is in place!