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

Flutter:加载地理位置后用FutureBuilder查询Firebase引发索引越界错误

问题描述

我想先加载用户地理位置,再根据该位置查询指定半径范围内的事件,最后用FutureBuilder展示这些事件。但遇到以下问题:

  • 必须在initState中初始化Future<List<Events>> futureEvents,否则Flutter报错,但此时还未获取到地理位置。
  • 获取到位置后重新调用查询函数,但在拿到地理位置并重新获取数据前,会抛出错误:RangeError (index): Invalid value: Valid value range is empty :1

(注:这是我的第一个项目,代码可能不够规范)

初始化代码(InitState)

class _LocationPageState extends State<LocationPage> {
  String? _currentAddress;
  Position? _currentPosition;
  late Future<List<Events>> futureEvents;
  double locationRadius = 5;

  @override
  void initState() {
    super.initState();
    _getCurrentPosition();
    futureEvents = _getEvents();
  }
}

调用的函数

Future<void> _getCurrentPosition() async {
    final hasPermission = await _handleLocationPermission();

    if (!hasPermission) return;
    await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high)
        .then((Position position) {
      setState(() => _currentPosition = position);
      _getAddressFromLatLng(_currentPosition!);
    }).catchError((e) {
      debugPrint(e);
    });
  }

  Future<void> _getAddressFromLatLng(Position position) async {
    await placemarkFromCoordinates(
        _currentPosition!.latitude, _currentPosition!.longitude)
        .then((List<Placemark> placemarks) {
      Placemark place = placemarks[0];
      setState(() {
        _currentAddress = ' ${place.postalCode} ${place.locality}  ';
      });
      currentPLZ = place.postalCode.toString();
      futureEvents = _getEvents() as Future<List<Events>>;
    }).catchError((e) {
      debugPrint(e);
    });
  }


Future<List<Events>> _getEvents() async {
    // get all PLZ in a radius
    final response = await http.get(Uri.parse(
        'https://www.suche-postleitzahl.org/geo-api.json?action=plz-umkreis&subaction=umkreis-osm&plz=' +
            currentPLZ +
            '&radius=' + locationRadius.toString()));
    if (response.statusCode == 200) {
      // If the server did return a 200 OK response,
      // then parse the JSON.
      var jsondataPlz = jsonDecode(response.body);
      List<PLZData> plzdataList = [];

      for (var u in jsondataPlz) {
        PLZData plzData = PLZData(u['postal_code'], u['name'], u['distance']);
        plzdataList.add(plzData);
      }
      print(plzdataList.length);
      print(plzdataList[1].name +
          " Distanz:" +
          plzdataList[1].distance +
          " PLZ only" +
          plzdataList[1].postal_code);

      // get all events in the radius
      List<Events> events = [];


      if (plzdataList.isNotEmpty) {
        for (var i = 0; plzdataList.length > i; i++) {
          var singleEvent = await FirebaseFirestore.instance
              .collection('Events')
              .where('postCode', isEqualTo: plzdataList[i].postal_code)
              .get();

          if (singleEvent.docs.isNotEmpty) {
            var singleEventList =singleEvent.docs.map((d) => Events.fromJson(d.data())).toList();

            //add distance and regionname

            for(var j = 0; singleEventList.length > j; j++){
              singleEventList[j].distance = plzdataList[i].distance;
              singleEventList[j].regionName = plzdataList[i].name;
            }
            events = events + singleEventList;



            if (events[0].userID != null) {
              print(events[0].userID);
              print(events[i].distance);
            }
          }
        }
      }

      //get userdata to the events
      if (events.isEmpty) {
        print("Es wurden keine Events gefunden");
      } else {
        for (var i = 0; events.length > i; i++) {
          var userInformationSnap = await FirebaseFirestore.instance
              .collection('users')
              .where('__name__', isEqualTo: events[i].userID)
              .get();
          events[i].userInformation = userInformationSnap.docs
              .map((d) => UsersForPosts.fromJson(d.data()))
              .toList();
          print(events[i].userInformation[0].username);
        }
      }
      return events;
    } else {
      // If the server did not return a 200 OK response,
      // then throw an exception.
      throw Exception('Failed to load');
    }
  }

FutureBuilder代码

FutureBuilder<List<Events>>(
                  future: futureEvents,
                  builder: (context, snapshot) {
                    if (snapshot.hasData &&
                        snapshot.connectionState == ConnectionState.done) {
                      return ListView.builder(
                        scrollDirection: Axis.vertical,
                        shrinkWrap: true,
                        physics: const NeverScrollableScrollPhysics(), //<--here
                        itemCount: snapshot.data!.length,
                        itemBuilder: (context, index) {
                          return Eventcard(
                            userPhoto: snapshot.data?[index].userInformation[0]
                                .userPhoto ?? "keine Info",
                            age: snapshot.data?[index].userInformation[0].age ??
                                "keine Info",
                            username: snapshot.data?[index].userInformation[0]
                                .username ?? "keine Info",
                            gender: snapshot.data?[index].userInformation[0]
                                .gender ?? "keine Info",
                            meetState: snapshot.data?[index].meetState ??
                                "keine Info",
                            postCode: snapshot.data?[index].postCode ??
                                "keine Info",
                            distance: snapshot.data?[index].distance ??
                                "keine Info",
                            regionName: snapshot.data?[index].regionName ??
                                "keine Info",
                            comment: snapshot.data?[index].comment ??
                                "keine Info",
                            headline: snapshot.data?[index].headline ??
                                "keine Info",
                          );
                        },
                      );
                    } else if (snapshot.hasError) {
                      return Text('${snapshot.error}');
                    } else {
                      // By default, show a loading spinner.
                      return const CircularProgressIndicator();
                    }
                  },
                ),

解决方案

1. 延迟初始化futureEvents,等待地理位置获取完成

initState中直接调用_getEvents()时,currentPLZ还未赋值,会导致API请求参数错误,后续解析数组触发越界。改为先获取位置再初始化:

@override
void initState() {
  super.initState();
  _initData();
}

Future<void> _initData() async {
  await _getCurrentPosition();
  if (currentPLZ.isNotEmpty) {
    setState(() {
      futureEvents = _getEvents();
    });
  }
}

2. 修复_getEvents()中的数组越界问题

直接访问plzdataList[1]和events[i]时未判断长度,容易触发错误,添加长度校验:

// 替换原print(plzdataList[1]...)的代码
if (plzdataList.length >= 2) {
  print(plzdataList[1].name +
      " Distanz:" +
      plzdataList[1].distance +
      " PLZ only" +
      plzdataList[1].postal_code);
}

// 替换原print(events[i].distance)的代码
if (i < events.length) {
  print(events[i].distance);
}

3. 优化FutureBuilder的状态判断与空值处理

简化状态判断逻辑,同时处理数据为空的场景,避免潜在的空指针:

FutureBuilder<List<Events>>(
  future: futureEvents,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('加载失败: ${snapshot.error}');
    } else if (snapshot.hasData) {
      final events = snapshot.data!;
      if (events.isEmpty) {
        return const Text("暂无事件");
      }
      return ListView.builder(
        scrollDirection: Axis.vertical,
        shrinkWrap: true,
        physics: const NeverScrollableScrollPhysics(),
        itemCount: events.length,
        itemBuilder: (context, index) {
          final event = events[index];
          final userInfo = event.userInformation?.firstOrNull;
          return Eventcard(
            userPhoto: userInfo?.userPhoto ?? "keine Info",
            age: userInfo?.age ?? "keine Info",
            username: userInfo?.username ?? "keine Info",
            gender: userInfo?.gender ?? "keine Info",
            meetState: event.meetState ?? "keine Info",
            postCode: event.postCode ?? "keine Info",
            distance: event.distance ?? "keine Info",
            regionName: event.regionName ?? "keine Info",
            comment: event.comment ?? "keine Info",
            headline: event.headline ?? "keine Info",
          );
        },
      );
    } else {
      return const Text("暂无数据");
    }
  },
)

4. 修正_getAddressFromLatLng中的冗余类型转换

_getEvents()本身返回Future<List<Events>>,无需强制类型转换,直接赋值并触发状态更新:

// 替换原futureEvents = _getEvents() as Future<List<Events>>;的代码
setState(() {
  futureEvents = _getEvents();
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 03:40:29