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:前两个错误导致的组件状态不匹配的连带报错,核心问题解决后会同步消失。
具体修复步骤
- 将
PaginateFirestore的header、footer用SliverToBoxAdapter包裹,转换成Sliver类型组件,适配内部滚动视图的要求。 - 移除
PaginateFirestore外层多余的Column,直接将RefreshIndicator作为StreamBuilder返回的根组件,删掉PaginateFirestore的shrinkWrap: true和physics: NeverScrollableScrollPhysics配置,让组件自身处理滚动逻辑,同时保证RefreshIndicator可正常触发刷新。 - 给
PaginateFirestore手动指定固定的PageStorageKey,避免Stream重建时重复生成GlobalKey。 - 将
_storeData.getUserLocationData(context)的调用移到initState生命周期中,不要放在build方法内重复执行,减少不必要的组件重建。 - 空安全版本的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
相关产品推荐
相关产品推荐

