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

Flutter中StreamBuilder+GoogleMap用setState致UI卡顿求解决方案

问题

Flutter应用集成GoogleMap后,通过StreamBuilder从Firebase Firestore的beets集合获取数据,遍历文档时调用异步方法fetchUserData获取用户信息,生成自定义Marker后用setState添加到markers集合。但每次调用setState都会导致UI严重卡顿,移除setState又无法在数据变更时更新地图Marker,不使用setState时应用流畅度正常,求最优解决方案。

核心问题分析
  • 在StreamBuilder的builder回调里使用forEach+async/await,每个Marker生成完成后都调用一次setState,频繁触发UI刷新,直接引发卡顿。
  • 每次Stream推送新数据时,未清空旧的markers集合,导致重复添加相同Marker,进一步恶化性能。
  • 异步操作直接放在builder中,违背了Flutter Widget构建的同步原则,会导致builder多次无意义重建。
最优解决方案

1. 批量异步处理Marker生成,一次性更新状态

把所有Marker的异步生成逻辑合并成一个批量任务,完成后一次性调用状态更新,避免多次刷新UI。

2. 清理旧数据,避免重复添加

每次Stream获取新数据时,先清空旧的markers集合,防止重复Marker积累。

3. 缓存用户数据,减少Firestore请求

对fetchUserData的结果进行缓存,避免相同用户信息重复请求Firestore,提升加载速度。

优化后的代码

批量生成Marker的方法

Future<Set<Marker>> generateMarkers(List<QueryDocumentSnapshot> docs) async {
  final Set<Marker> newMarkers = {};
  // 遍历所有文档,批量生成Marker
  for (final docSnapshot in docs) {
    if (docSnapshot.data() is Map<String, dynamic>) {
      final beetData = docSnapshot.data() as Map<String, dynamic>;
      final geoPoint = beetData['coordinates'] as GeoPoint;
      final coordinates = LatLng(geoPoint.latitude, geoPoint.longitude);
      final email = beetData['email'] as String;

      // 优先从缓存取用户数据
      UserModel? user = _userCache[email];
      if (user == null) {
        user = await fetchUserData(email);
        if (user != null) {
          _userCache[email] = user;
        }
      }

      if (user != null) {
        final markerUser = await MarkerIcon.downloadResizePictureCircle(
          user.urlImage,
          size: 190,
          addBorder: true,
          borderColor: AppColors.whiteColor,
          borderSize: 24,
        );

        newMarkers.add(Marker(
          markerId: MarkerId(docSnapshot.id),
          onTap: () => openInfoModalUser(context, user),
          position: coordinates,
          icon: markerUser,
        ));
      }
    }
  }
  return newMarkers;
}

修改后的StreamBuilder

// 类中添加缓存变量
final Map<String, UserModel> _userCache = {};
Set<Marker> markers = {};

StreamBuilder<QuerySnapshot>(
  stream: FirebaseFirestore.instance
      .collection(CustomCollectionPath.beets)
      .where('email', isNotEqualTo: currentUser.email)
      .snapshots(),
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      // 用FutureBuilder处理批量异步任务
      return FutureBuilder<Set<Marker>>(
        future: generateMarkers(snapshot.data!.docs),
        builder: (context, markerSnapshot) {
          if (markerSnapshot.hasData) {
            // 一次性更新所有Marker
            markers = markerSnapshot.data!;
            return GoogleMap(
              onMapCreated: _onMapCreated,
              myLocationEnabled: true,
              buildingsEnabled: true,
              myLocationButtonEnabled: false,
              zoomControlsEnabled: false,
              markers: markers,
              mapToolbarEnabled: false,
              initialCameraPosition: _startPosition,
            );
          } else if (markerSnapshot.hasError) {
            return Text('生成Marker时出错');
          } else {
            // 显示加载状态,避免地图闪烁
            return GoogleMap(
              onMapCreated: _onMapCreated,
              myLocationEnabled: true,
              buildingsEnabled: true,
              myLocationButtonEnabled: false,
              zoomControlsEnabled: false,
              markers: markers, // 保留旧Marker直到新的生成完成
              mapToolbarEnabled: false,
              initialCameraPosition: _startPosition,
            );
          }
        },
      );
    } else if (snapshot.hasError) {
      return Text('获取数据失败');
    } else {
      return CircularProgressIndicator();
    }
  },
),

带缓存的fetchUserData

Future<UserModel?> fetchUserData(String email) async {
  // 先检查缓存
  if (_userCache.containsKey(email)) {
    return _userCache[email];
  }
  
  try {
    final userSnapshot = await FirebaseFirestore.instance
        .collection(CustomCollectionPath.userInfo)
        .doc(email)
        .get();

    if (userSnapshot.exists) {
      final userData = userSnapshot.data() as Map<String, dynamic>;
      final user = UserModel(
        id: Random().nextInt(999999),
        name: userData['name'],
        urlImage: userData['imageUrl'],
        roleDescription: 'Role',
        description: userData['description'],
        icon: Icons.abc,
        color: Colors.red,
      );
      // 存入缓存
      _userCache[email] = user;
      return user;
    } else {
      return null;
    }
  } catch (e) {
    print('获取用户数据错误: $e');
    return null;
  }
}
额外优化建议
  • 对Marker的图标进行本地缓存,避免每次都下载和裁剪图片,可以使用cached_network_image库配合本地缓存实现。
  • 如果beets集合数据量较大,考虑分页加载Marker,避免一次性生成过多Marker导致内存占用过高。
  • 可以使用ValueNotifier或ChangeNotifier来管理markers状态,替代setState,进一步优化状态更新的效率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 08:08:24