You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 15:15:46