Flutter中StreamBuilder<QuerySnapshot>布局报错,订单距离筛选异常
Flutter + Firestore 布局无限大小错误修复方案
我正在开发一款配送App,需求是向司机展示距离其2公里以内的未接单demo类型订单。App基于Flutter开发,采用Firebase Firestore作为数据库,但当前StreamBuilder<QuerySnapshot>出现布局无限大小错误,将其包裹在Column中后问题仍存在。
原代码
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:geocoding/geocoding.dart'; import 'package:geolocator/geolocator.dart'; import 'package:homekitchen_delivery_app_new/globals.dart'; import 'package:homekitchen_delivery_app_new/models/DriversRequest.dart'; class DemoNearMe extends StatefulWidget { const DemoNearMe({super.key}); @override State<DemoNearMe> createState() => _DemoNearMeState(); } class _DemoNearMeState extends State<DemoNearMe> { Placemark? placemark; List positions = []; Position? position; List<Placemark>? placemarks; getCurrentLocation() async { await Geolocator.requestPermission().whenComplete(() async { Position newPos = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); position = newPos; placemarks = await placemarkFromCoordinates( position!.latitude, position!.longitude); Placemark pMark = placemarks![0]; placemark = pMark; }); return [placemark, position!.latitude, position!.longitude]; } @override void initState() { WidgetsBinding.instance.addPostFrameCallback((_) async { positions = await getCurrentLocation(); }); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: StreamBuilder<QuerySnapshot>( stream: FirebaseFirestore.instance .collection("driverRequest") .where("booked", isEqualTo: false) .where("orederType", isEqualTo: "demo") .where("city", isEqualTo: driverData!.city) .snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: CircularProgressIndicator()); } return snapshot.hasData ? ListView.builder( shrinkWrap: true, itemCount: snapshot.data!.docs.length, itemBuilder: (context, index) { final data = snapshot.data!.docs[index].data(); DriverRequest driverRequest = DriverRequest.fromJson(data as Map<String, dynamic>); double distanceBetween = Geolocator.distanceBetween( positions[1], positions[2], driverRequest.sellerLat, driverRequest.sellerLng) / 1000; if (distanceBetween <= 2) { return Padding( padding: const EdgeInsets.all(20.0), child: Material( elevation: 40, child: Container( child: InkWell( onTap: () { // Navigator.push(context, MaterialPageRoute(builder: (c) => )) }, child: Column( children: [ ListTile( leading: CircleAvatar( backgroundImage: NetworkImage( driverRequest.sellerProfilePic), ), title: Text(driverRequest.sellerName), subtitle: Text( '${distanceBetween.toStringAsFixed(2)} km'), ), ], ), ), ), ), ); } else { return Container(); } }, ) : Container( height: 0.0, ); }), ); } }
错误日志
应用已重启,耗时3940毫秒。 ════════ 渲染库捕获异常 ═════════════════════════════════ 执行布局时抛出以下断言: RenderCustomMultiChildLayoutBox 对象在布局期间被赋予无限大小。 这通常意味着该渲染对象试图尽可能大,但它被放置在另一个允许子项自行选择大小的渲染对象中。 提供无界高度约束的最近祖先为:_RenderSingleChildViewport#b1d96 relayoutBoundary=up10 NEEDS-LAYOUT NEEDS-COMPOSITING-BITS-UPDATE 应用于RenderCustomMultiChildLayoutBox的约束为:BoxConstraints(0.0<=w<=392.7, 0.0<=h<=Infinity) 它被赋予的精确大小为:Size(392.7, Infinity) 相关错误触发组件为 Scaffold ...[省略重复错误日志]
将StreamBuilder包裹在Column中时仍报错:
应用已重启,耗时3092毫秒。 ════════ 渲染库捕获异常 ═════════════════════════════════ 执行布局时抛出以下断言: RenderCustomMultiChildLayoutBox 对象在布局期间被赋予无限大小。 ...[省略重复错误日志]
问题原因及修复方案
- 核心问题:
ListView.builder设置shrinkWrap: true后,若父组件未提供明确高度约束,会试图占用无限高度;同时在itemBuilder中返回空Container(),导致列表项高度不确定,加剧布局冲突。 - 修复步骤:
- 移除
shrinkWrap: true,让ListView默认占用父组件(Scaffold body)的可用有界空间。 - 先过滤出距离<=2公里的订单,再传递给ListView构建,避免无效空列表项。
- 增加位置未获取时的加载状态,防止空指针异常。
- 移除
修复后的代码
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:geocoding/geocoding.dart'; import 'package:geolocator/geolocator.dart'; import 'package:homekitchen_delivery_app_new/globals.dart'; import 'package:homekitchen_delivery_app_new/models/DriversRequest.dart'; class DemoNearMe extends StatefulWidget { const DemoNearMe({super.key}); @override State<DemoNearMe> createState() => _DemoNearMeState(); } class _DemoNearMeState extends State<DemoNearMe> { Placemark? placemark; List positions = []; Position? position; List<Placemark>? placemarks; getCurrentLocation() async { await Geolocator.requestPermission().whenComplete(() async { Position newPos = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); position = newPos; placemarks = await placemarkFromCoordinates( position!.latitude, position!.longitude); Placemark pMark = placemarks![0]; placemark = pMark; }); setState(() { positions = [placemark, position!.latitude, position!.longitude]; }); } @override void initState() { WidgetsBinding.instance.addPostFrameCallback((_) async { await getCurrentLocation(); }); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: positions.isEmpty ? const Center(child: CircularProgressIndicator()) : StreamBuilder<QuerySnapshot>( stream: FirebaseFirestore.instance .collection("driverRequest") .where("booked", isEqualTo: false) .where("orederType", isEqualTo: "demo") .where("city", isEqualTo: driverData!.city) .snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { return const Center(child: Text("暂无附近的Demo订单")); } // 提前筛选符合距离要求的订单 final filteredRequests = snapshot.data!.docs .map((doc) => DriverRequest.fromJson(doc.data() as Map<String, dynamic>)) .where((request) { double distance = Geolocator.distanceBetween( positions[1], positions[2], request.sellerLat, request.sellerLng) / 1000; return distance <= 2; }) .toList(); if (filteredRequests.isEmpty) { return const Center(child: Text("2公里内无Demo订单")); } return ListView.builder( itemCount: filteredRequests.length, itemBuilder: (context, index) { final driverRequest = filteredRequests[index]; double distanceBetween = Geolocator.distanceBetween( positions[1], positions[2], driverRequest.sellerLat, driverRequest.sellerLng) / 1000; return Padding( padding: const EdgeInsets.all(20.0), child: Material( elevation: 40, child: InkWell( onTap: () { // Navigator.push(context, MaterialPageRoute(builder: (c) => )) }, child: ListTile( leading: CircleAvatar( backgroundImage: NetworkImage(driverRequest.sellerProfilePic), ), title: Text(driverRequest.sellerName), subtitle: Text('${distanceBetween.toStringAsFixed(2)} km'), ), ), ), ); }, ); }, ), ); } }
额外优化点
- 在
getCurrentLocation中调用setState,确保位置获取后UI实时刷新。 - 移除多余的
Container嵌套,简化布局结构提升性能。 - 增加空状态提示,优化用户体验。
内容的提问来源于stack exchange,提问作者Ultron
相关产品推荐
相关产品推荐

