如何在Flutter父Products页面与子Product页面中使用Provider
基于Provider + Firestore的实时产品列表与详情方案
核心思路
用StreamProvider全局托管产品集合的实时流,通过ChangeNotifierProvider维护选中产品的ID状态,子页面基于选中ID单独监听对应文档的实时流,既避免重复订阅,又保证UI精准响应数据变更。
1. 定义Product数据模型
class Product { final String id; final String name; final double price; final String description; Product({ required this.id, required this.name, required this.price, required this.description, }); // 从Firestore文档快照转换为模型 factory Product.fromFirestore(DocumentSnapshot doc) { final data = doc.data() as Map<String, dynamic>; return Product( id: doc.id, name: data['name'] ?? '', price: (data['price'] ?? 0).toDouble(), description: data['description'] ?? '', ); } }
2. 实现选中产品状态管理
class SelectedProductNotifier extends ChangeNotifier { String? _selectedProductId; String? get selectedProductId => _selectedProductId; void setSelectedProductId(String id) { _selectedProductId = id; notifyListeners(); } }
3. 全局Provider配置(main.dart)
将集合流和状态管理全局注入,确保所有页面可访问:
void main() { runApp( MultiProvider( providers: [ // 提供产品集合的实时流 StreamProvider<List<Product>>( create: (context) => FirebaseFirestore.instance .collection('products') .snapshots() .map((snapshot) => snapshot.docs.map(Product.fromFirestore).toList()), initialData: const [], ), // 提供选中产品的状态管理 ChangeNotifierProvider(create: (context) => SelectedProductNotifier()), ], child: const MyApp(), ), ); }
4. 父页面:产品列表页
从全局StreamProvider获取实时列表,点击时更新选中产品ID并跳转:
class ProductListPage extends StatelessWidget { const ProductListPage({super.key}); @override Widget build(BuildContext context) { final products = Provider.of<List<Product>>(context); final selectedProductNotifier = Provider.of<SelectedProductNotifier>(context, listen: false); return Scaffold( appBar: AppBar(title: const Text('产品列表')), body: products.isEmpty ? const Center(child: CircularProgressIndicator()) : ListView.builder( itemCount: products.length, itemBuilder: (context, index) { final product = products[index]; return ListTile( title: Text(product.name), subtitle: Text('¥${product.price.toStringAsFixed(2)}'), onTap: () { selectedProductNotifier.setSelectedProductId(product.id); Navigator.push( context, MaterialPageRoute(builder: (context) => const ProductDetailPage()), ); }, ); }, ), ); } }
5. 子页面:产品详情页
基于选中ID单独监听对应文档的实时流,仅在当前产品数据变更时刷新UI:
class ProductDetailPage extends StatelessWidget { const ProductDetailPage({super.key}); @override Widget build(BuildContext context) { final selectedProductId = Provider.of<SelectedProductNotifier>(context).selectedProductId; if (selectedProductId == null) { return const Scaffold(body: Center(child: Text('未选择产品'))); } // 单独监听选中产品的文档流 return StreamProvider<Product?>( create: (context) => FirebaseFirestore.instance .collection('products') .doc(selectedProductId) .snapshots() .map((doc) => doc.exists ? Product.fromFirestore(doc) : null), initialData: null, child: const _ProductDetailContent(), ); } } class _ProductDetailContent extends StatelessWidget { const _ProductDetailContent(); @override Widget build(BuildContext context) { final product = Provider.of<Product?>(context); if (product == null) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } return Scaffold( appBar: AppBar(title: Text(product.name)), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('产品ID: ${product.id}'), const SizedBox(height: 16), Text('价格: ¥${product.price.toStringAsFixed(2)}'), const SizedBox(height: 16), Text('描述: ${product.description}'), ], ), ), ); } }
可选优化方案
如果产品数量较少,也可以直接从全局集合流中筛选选中产品,无需单独监听文档流:
// 子页面中替换StreamProvider的写法 final products = Provider.of<List<Product>>(context); final selectedProductId = Provider.of<SelectedProductNotifier>(context).selectedProductId; final product = products.firstWhere((p) => p.id == selectedProductId, orElse: () => null);
缺点是集合中任意产品变更时,子页面都会刷新,适合小数据量场景。
内容的提问来源于stack exchange,提问作者user6097845
相关产品推荐
相关产品推荐

