runable
This commit is contained in:
598
docs/QUICK_START_PROVIDERS.md
Normal file
598
docs/QUICK_START_PROVIDERS.md
Normal file
@@ -0,0 +1,598 @@
|
||||
# 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
|
||||
```dart
|
||||
import 'package:retail/features/home/presentation/providers/providers.dart';
|
||||
```
|
||||
|
||||
### Import All Product Providers
|
||||
```dart
|
||||
import 'package:retail/features/products/presentation/providers/providers.dart';
|
||||
```
|
||||
|
||||
### Import All Category Providers
|
||||
```dart
|
||||
import 'package:retail/features/categories/presentation/providers/providers.dart';
|
||||
```
|
||||
|
||||
### Import All Settings Providers
|
||||
```dart
|
||||
import 'package:retail/features/settings/presentation/providers/providers.dart';
|
||||
```
|
||||
|
||||
### Import Core Providers (Sync, Network)
|
||||
```dart
|
||||
import 'package:retail/core/providers/providers.dart';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### 1. Display Products
|
||||
|
||||
```dart
|
||||
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
|
||||
|
||||
```dart
|
||||
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
|
||||
|
||||
```dart
|
||||
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
|
||||
|
||||
```dart
|
||||
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
|
||||
|
||||
```dart
|
||||
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
|
||||
|
||||
```dart
|
||||
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)
|
||||
```dart
|
||||
// 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
|
||||
```dart
|
||||
// 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
|
||||
```dart
|
||||
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
|
||||
```dart
|
||||
// Invalidate - resets provider
|
||||
ref.invalidate(productsProvider);
|
||||
|
||||
// Refresh - invalidate + read immediately
|
||||
final products = ref.refresh(productsProvider);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Providers
|
||||
|
||||
```dart
|
||||
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
|
||||
|
||||
1. ✅ Providers are implemented and generated
|
||||
2. ✅ All dependencies are installed
|
||||
3. ✅ Code generation is complete
|
||||
4. 🔄 Replace mock data sources with Hive implementations
|
||||
5. 🔄 Build UI pages using the providers
|
||||
6. 🔄 Add error handling and loading states
|
||||
7. 🔄 Write tests for providers
|
||||
8. 🔄 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!
|
||||
Reference in New Issue
Block a user