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

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 对象在布局期间被赋予无限大小。
...[省略重复错误日志]

问题原因及修复方案

  1. 核心问题:ListView.builder设置shrinkWrap: true后,若父组件未提供明确高度约束,会试图占用无限高度;同时在itemBuilder中返回空Container(),导致列表项高度不确定,加剧布局冲突。
  2. 修复步骤:
    • 移除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 18:34:54