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

Flutter嵌套ListView加载屏幕外组件触发RangeError问题求助

问题描述

首次接触Flutter开发,搭建了连接Firebase Realtime Database的应用,登录后获取Map格式嵌套数据,数据结构如下:

{
  "title1": {
        "item-A": {
                 "0": "line1",
                 "1": "line2",
                 "2": "line3",
                },
        "item-B": {
                 "0": "line1",
                 "1": "line2",
                 "2": "line3",
                }
          },
  "title2": {
        "item-A": {
                 "0": "line1",
                 ...
        }
}

通过FutureBuilder从数据库获取数据并格式化后,用嵌套ListView展示。数据获取和基础展示正常,但仅显示1-2个列表组件就抛出红色错误框:

The following IndexError was thrown building:
RangeError (index): Index out of range: index should be less than 1: 1

每次热刷新、热重载或重新加载浏览器后,可显示更多组件,错误索引递增,比如:

The following IndexError was thrown building:
RangeError (index): Index out of range: index should be less than 4: 5

多次刷新后能完整显示列表,但行为不符合预期。相关核心代码(省略细节):

Widget build(BuildContext context) {
  return Consumer<ApplicationState>(
      builder: (context, appState, child) {
        selectedIndex.add(-1);

        return Scaffold(
            ...
            body: FutureBuilder(
              future: futureDataRetreiverFunction(),
              builder: (BuildContext context, AsyncSnapshot<Map> snapshot) {

              if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
                ... // 转换snapshot.data为snapshotData
                return Container(
                    ...
                    child: ListView(
                        shrinkWrap: true,
                        children: [
                          Form(
                          key: _formKey,
                          child: Column(
                              children: [
                                ListView.builder(
                                  scrollDirection: Axis.vertical,
                                  controller: ScrollController(),
                                  shrinkWrap: true,
                                  itemCount: snapshotData.keys.length, // snapshotData是Map
                                  itemBuilder: (BuildContext context, int index) =>
                                    Column(
                                        children: [
                                          ...
                                          Row(
                                            children: [
                                              Text(snapshotData.keys.elementAt(index)),
                                            ]
                                          ),
                                          Row(
                                            children: [
                                              Text(selectedIndex[index].toString())
                                            ]
                                          ),
                                          Row(
                                            children: [
                                            Flexible(
                                              fit: FlexFit.loose,
                                              child: Padding(
                                                padding: const EdgeInsets.fromLTRB(5, 0, 0, 10),
                                                  child: ListView.builder(
                                                    shrinkWrap: true,
                                                    primary: false,
                                                    itemCount: snapshotData.values.elementAt(index)[1].length,
                                                    itemBuilder: (BuildContext context, int index2) =>
                                                      Text(snapshotData.values.elementAt(index)[1][index2])
                                                  )
                                                )
                                              )]
                                            )
                                          ])
                                         )
                                       ])
                                      ])
                                     );
...                                                  
问题原因
  1. selectedIndex列表长度不匹配:在Consumer的builder方法中,每次组件重建都会执行selectedIndex.add(-1)。而builder会因FutureBuilder状态更新、组件重绘等多次触发,导致selectedIndex长度逐步增加。但ListView.builder的itemCount是snapshotData.keys.length,初始时selectedIndex长度小于该值,访问selectedIndex[index]就会触发索引越界错误。多次热刷新后,selectedIndex长度逐渐赶上snapshotData.keys.length,因此能正常显示。

  2. 嵌套ListView的冗余设置:外层ListView包裹Form和Column的结构存在冗余,且在itemBuilder中重复创建ScrollController,可能引发不必要的重绘,但这不是当前索引错误的直接原因。

解决方法
  1. 同步selectedIndex与数据长度:

    • 移除builder中的selectedIndex.add(-1),改为在获取到snapshotData后,根据数据长度初始化或重置selectedIndex,确保两者长度一致:
      // 在转换snapshot.data为snapshotData后执行
      selectedIndex = List.filled(snapshotData.keys.length, -1);
      
      若selectedIndex是状态变量,需通过setState(StatefulWidget)或状态管理工具(如ApplicationState)更新状态。
  2. 优化嵌套ListView结构:

    • 移除外层不必要的ListView,直接用SingleChildScrollView包裹Form和Column,避免多层滚动嵌套的性能问题。
    • 将ScrollController提升为类成员变量,不在itemBuilder中重复创建,或在不需要滚动控制时直接省略。
  3. 增加数据空安全判断:

    • 针对snapshotData.values.elementAt(index)[1]的长度计算,添加空安全校验,避免因数据转换错误引发的异常:
      itemCount: (snapshotData.values.elementAt(index)[1] as List?)?.length ?? 0,
      

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 09:31:05