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

Flutter实现附近商家展示功能出现多类报错如何解决

Flutter 近场商家列表报错修复方案

三个报错的根因和修复方法如下:

  • A RenderShrinkWrappingViewport expected a child of type RenderSliver:这是核心错误,原因是PaginateFirestore的header、footer传入了普通非Sliver类型的组件,同时你把可滚动的PaginateFirestore嵌套在Column内、禁用了自身滚动,和外层RefreshIndicator的滚动逻辑冲突。
  • Duplicate GlobalKey detected in widget tree:PaginateFirestore放在StreamBuilder的builder方法中,每次Stream触发更新都会创建新的实例,内部自带的GlobalKey重复。
  • Failed assertion: line 6075 pos 12: 'child == _child': is not true:前两个错误导致的组件状态不匹配的连带报错,核心问题解决后会同步消失。

具体修复步骤

  1. 将PaginateFirestore的header、footer用SliverToBoxAdapter包裹,转换成Sliver类型组件,适配内部滚动视图的要求。
  2. 移除PaginateFirestore外层多余的Column,直接将RefreshIndicator作为StreamBuilder返回的根组件,删掉PaginateFirestore的shrinkWrap: true和physics: NeverScrollableScrollPhysics配置,让组件自身处理滚动逻辑,同时保证RefreshIndicator可正常触发刷新。
  3. 给PaginateFirestore手动指定固定的PageStorageKey,避免Stream重建时重复生成GlobalKey。
  4. 将_storeData.getUserLocationData(context)的调用移到initState生命周期中,不要放在build方法内重复执行,减少不必要的组件重建。
  5. 空安全版本的Flutter需要给snapShot.data加上非空断言!,避免空检测报错。

修复后完整代码

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:paginate_firestore/bloc/pagination_listeners.dart';
import 'package:paginate_firestore/paginate_firestore.dart';
import 'package:phatafut/providers/store_provider.dart';
import 'package:phatafut/services/store_services.dart';
import 'package:provider/provider.dart';
import '../constants.dart';

class NearestStore extends StatefulWidget {
  static const id = 'nearest-store-screen';
  const NearestStore({key}) : super(key: key);

  @override
  _NearestStoreState createState() => _NearestStoreState();
}

class _NearestStoreState extends State<NearestStore> {
  StoreServices _storeServices = StoreServices();
  PaginateRefreshedChangeListener refreshedChangeListener = PaginateRefreshedChangeListener();

  @override
  void initState() {
    super.initState();
    // 将位置数据获取移到初始化阶段,避免重复执行
    WidgetsBinding.instance.addPostFrameCallback((_) {
      Provider.of<StoreProvider>(context, listen: false).getUserLocationData(context);
    });
  }

  // 距离计算方法移到build外部
  String getDistance(dynamic location, StoreProvider storeData){
    var distance = Geolocator.distanceBetween(
      storeData.userLatitude, storeData.userLongitude, location.latitude, location.longitude,
    );
    var distanceInKM = distance/1000;
    return distanceInKM.toStringAsFixed(2);
  }

  @override
  Widget build(BuildContext context) {
    final _storeData = Provider.of<StoreProvider>(context);

    return Container(
      child: StreamBuilder<QuerySnapshot>(
        stream: _storeServices.getnearestStore(),
        builder: (BuildContext context, AsyncSnapshot<QuerySnapshot>snapShot){
          if(!snapShot.hasData)
            return const Center(child: CircularProgressIndicator());
          List shopDistance = [];
          for(int i=0 ; i<=snapShot.data!.docs.length-1; i++){
            var distance = Geolocator.distanceBetween(
                _storeData.userLatitude, _storeData.userLongitude,
                snapShot.data!.docs[i]['location'].latitude,
                snapShot.data!.docs[i]['location'].longitude
            );
            var distanceInKM = distance/1000;
            shopDistance.add(distanceInKM);
          }
          shopDistance.sort();
          if(shopDistance[0]>1){
            return Container();
          }
          // 移除外层Column,直接返回RefreshIndicator
          return RefreshIndicator(
            child: PaginateFirestore(
              // 手动指定固定Key,避免GlobalKey重复
              key: const PageStorageKey('nearest_store_pagination'),
              bottomLoader: CircularProgressIndicator(
                valueColor: AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
              ),
              // header包裹SliverToBoxAdapter
              header: SliverToBoxAdapter(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Padding(
                      padding: const EdgeInsets.only(
                        left: 8,right: 8,top: 20,),
                      child: Text('All Nearby Stores',
                        style: TextStyle(fontWeight: FontWeight.w700,
                          fontSize: 21,
                        ),
                      ),
                    ),
                    Padding(
                      padding: const EdgeInsets.only(
                        left: 8,right: 8,top: 20,),
                      child: Text('Find out quality products Nearby You',
                        style: TextStyle(
                            fontSize: 21,
                            color: Colors.blueGrey
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              itemBuilderType: PaginateBuilderType.listView,
              itemBuilder: (index, context, document)=>
              Padding(
                padding: const EdgeInsets.all(4),
                child: Container(
                  width: MediaQuery.of(context).size.width,
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      SizedBox(
                        width: 100,
                        height: 110,
                        child: Card(
                          child: ClipRRect(
                            borderRadius: BorderRadius.circular(4),
                            child: Image.network(document['imageUrl'],fit:BoxFit.cover),
                          ),
                        ),
                      ),
                      const SizedBox(width: 10,),
                      Column(
                        mainAxisSize: MainAxisSize.min,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Container(
                            child: Text(document['shopName'],
                              style: const TextStyle(
                                fontSize: 14,
                                fontWeight: FontWeight.bold,
                              ),
                              maxLines: 2,
                              overflow: TextOverflow.ellipsis,
                            ),
                          ),
                          const SizedBox(height: 3,),
                          Text(document['dialog'],
                            style: KShopCardStyle,
                          ),
                          const SizedBox(height: 3,),
                          Container(
                            width: MediaQuery.of(context).size.width-250,
                            child: Text(document['address'],
                              overflow: TextOverflow.ellipsis,
                              style: KShopCardStyle,
                            ),
                          ),
                          const SizedBox(height: 3,),
                          Text('${getDistance(document['location'], _storeData)}KM',
                            overflow: TextOverflow.ellipsis,
                            style: KShopCardStyle,
                          ),
                          const SizedBox(height: 3,),
                          Row(
                            children: const [
                              Icon(Icons.star,
                                size: 12,
                                color: Colors.blueGrey,
                              ),
                              SizedBox(width: 4,),
                              Text('4.0',
                                style: KShopCardStyle,
                              ),
                            ],
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
              query: FirebaseFirestore.instance
                  .collection('vendors')
                  .where('accVerified', isEqualTo: true)
                  .where('isTopPicked',isEqualTo: true)
                  .orderBy('shopName'),
              listeners: [
                refreshedChangeListener,
              ],
              // footer包裹SliverToBoxAdapter
              footer: SliverToBoxAdapter(
                child: Padding(
                  padding: const EdgeInsets.only(top: 30,),
                  child: Container(
                    child: Stack(
                      children: [
                        Center(
                          child: Text('**That\s all folks**',
                            style: const TextStyle(
                              color: Colors.blueGrey,
                            ),
                          ),
                        ),
                        Image.asset('images/city.png'),
                        Positioned(
                          right: 10.0,
                          top: 80,
                          child: Container(
                            width: 100,
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                const Text('Made by : ',
                                  style: TextStyle(
                                    color: Colors.black12,
                                  ),
                                ),
                                const Text('Adeel',
                                  style: TextStyle(
                                    fontWeight: FontWeight.bold,
                                    color: Colors.black,
                                    letterSpacing: 2,
                                    fontFamily: 'Poppins-Bold',
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
            onRefresh: ()async{
              refreshedChangeListener.refreshed = true;
            },
          );
        },
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者Adeel Nazim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 12:42:03