【问题标题】:Real-Time Updates with Streambuilder in Flutter and Google Maps (Firestore)Flutter 和 Google Maps (Firestore) 中的 Streambuilder 实时更新
【发布时间】:2025-12-08 02:35:01
【问题描述】:

我正在构建一个示例应用程序,它从 Firestore 获取多个标记并将它们显示在 Google 地图上。

问题在于更改(添加/删除/更新的标记)不会实时显示,因为它仅在初始化状态下重建。我认为我可以使用 Stream Builder 来监听变化并实时更新它们。

我不知道如何使用 Google 地图和 Firestore 来实现,因为快照的语法有点奇怪,我尝试了很多方法,但都没有成功。这是我的代码:

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


populateMarkers() {
    FirebaseFirestore.instance.collection('marker').get().then((documents) {
      if (documents.docs.isNotEmpty) {
        for (int i = 0; i < documents.docs.length; i++) {
          initMarker(
              documents.docs[i].data(), documents.docs[i].id); 
        }
      }
    });
      }

  void initMarker(request, requestId) {
    
    var markerIdVal = requestId;
    final MarkerId markerId = MarkerId(markerIdVal);
    //creating new markers
    final Marker marker = Marker(
      markerId: markerId,
      position:
          LatLng(request['location'].latitude, request['location'].longitude),
      infoWindow:
          InfoWindow(title: request['name'], snippet: request['description']),
      onTap: () => print('Test'),
    );
    setState(() {
      markers[markerId] = marker;
      //print(markerId);
    });
  }

Widget loadMap() {
    return GoogleMap(
      
      markers: Set<Marker>.of(markers.values),
      mapType: MapType.normal,
      initialCameraPosition:
          CameraPosition(target: LatLng(43.8031287, 20.453008), zoom: 12.0),
      onMapCreated: onMapCreated,

      // },
    );
  }

在构建器中,我只是调用 loadMap() 函数作为主体。正如我所提到的,这很好用,但我想使用 Stream Builder 来实时更新这些功能。有什么想法吗?

这个帖子有类似的问题,但我觉得这不是最好的方法:

Flutter - Provide Real-Time Updates to Google Maps Using StreamBuilder

下面是完整的代码,以防有人想测试它:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class MapScreen extends StatefulWidget {
  @override
  _MapScreenState createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
  Map<MarkerId, Marker> markers = <MarkerId, Marker>{}; //--> google
  GoogleMapController _controller;
  
   @override
  void initState() {
    populateMarkers();
    super.initState();
  }
  
   onMapCreated(GoogleMapController controller) async {
    _controller = controller;
    String value = await DefaultAssetBundle.of(context)
        .loadString('assets/map_style.json');
    _controller.setMapStyle(value);
  }

  populateMarkers() {
    FirebaseFirestore.instance.collection('marker').get().then((documents) {
      if (documents.docs.isNotEmpty) {
        for (int i = 0; i < documents.docs.length; i++) {
          initMarker(
              documents.docs[i].data(), documents.docs[i].id); //maybe error
          //documents.docs[i].data, documents.docs[i].id
        }
      }
    });
  }
  
   void initMarker(request, requestId) {
   
    var markerIdVal = requestId;
    final MarkerId markerId = MarkerId(markerIdVal);
    //creating new markers
    final Marker marker = Marker(
      markerId: markerId,
      position:
          LatLng(request['location'].latitude, request['location'].longitude),
      infoWindow:
          InfoWindow(title: request['name'], snippet: request['description']),
      onTap: () => print('Test'),
    );
    setState(() {
      markers[markerId] = marker;
      //print(markerId);
    });
  }

  Widget loadMap() {
    return GoogleMap(
      // markers: markers.values.toSet(),
      markers: Set<Marker>.of(markers.values),
      mapType: MapType.normal,
      initialCameraPosition:
          CameraPosition(target: LatLng(44.8031267, 20.432008), zoom: 12.0),
      onMapCreated: onMapCreated,
      cameraTargetBounds: CameraTargetBounds(LatLngBounds(
          northeast: LatLng(44.8927468, 20.5509553),
          southwest: LatLng(44.7465138, 20.2757283))),
      mapToolbarEnabled: false,
      //  onMapCreated: (GoogleMapController controller) {
      //   _controller = controller;
      // },
    );
  }
  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

//-----------------------------------
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('App'),
        centerTitle: true,
        backgroundColor: Colors.blue[700],
        actions: [
          // ElevatedButton(
          //   onPressed: () => refresh(),
          //   child: Text('REFRESH'),
          // )
        ],
      ),
      body: loadMap(),
        );
  }
}

【问题讨论】:

  • 我在车辆跟踪应用中使用谷歌地图。我所做的是每 10 秒更新一次标记。我使用由计时器调用的流构建器执行此操作,并且始终在加载数据之前执行 GoogleMap.of(_key).clearMarkers();清除所有标记。因此,那些被排除的会消失,其余的会被更新。
  • 要从流构建器中添加标记,我执行 GoogleMap.of(_key).addMarkerRaw( GeoCoord(item.latidute, item.longitude), icon: icone, infoSnippet: "${ item.listaMotoristas}", 标签: item.placa, );在快照中的每个项目中。

标签: flutter dictionary dart


【解决方案1】:

所以您想订阅来自 firestore 集合的流,并使用此数据实时更新地图上的标记,对吗?

请注意,您绝对需要将服务与小部件分开,否则您很快就会得到一团乱麻的代码。

在一个文件中,您拥有定义对数据库的访问权限的类(例如 API 调用,或本例中的 Firestore)。使用 Firestore 进行的任何写入或读取都将通过此服务。

class FirestoreService {
  FirestoreService._();
  static final instance = FirestoreService._();

  Stream<List<T>> collectionStream<T>({
    required String path,
    required T Function(Map<String, dynamic> data, String documentID) builder,
  }) {
    Query query = FirebaseFirestore.instance.collection(path);
    final Stream<QuerySnapshot> snapshots = query.snapshots();
    return snapshots.map((snapshot) {
      final result = snapshot.docs
          .map((snapshot) =>
              builder(snapshot.data() as Map<String, dynamic>, snapshot.id))
          .where((value) => value != null)
          .toList();
      return result;
    });
  }
}

在另一个文件中,您将拥有包含 CRUD 操作的存储库服务。每个操作都会调用 DB 服务(在上一步中定义)并使用对象的序列化(toJson,发送到 DB)或解析(fromJson,从 DB 获取)方法。

class MarkersRepo {
  final _service = FirestoreService.instance;

  Stream<List<Marker>> getMarkers() {
    return _service.collectionStream(
      path: 'marker',
      builder:(json, docId){
        return Marker.fromJson(json);
      }
    );
  }
}

在另一个文件中,您使用序列化和解析方法定义您的 Marker 对象模型。请不要使用字符串直接访问代码中的文档属性,因为这样容易出错并且会再次导致代码混乱。相反,您可以在对象模型中一劳永逸地定义它们。

class Marker{
  //properties of the object such as location, name and description
  //toJson and fromJson methods
}

最后在您的小部件文件中,正如您自己指出的那样,您只在 initstate 中读取文档一次,因此视图不会更新。相反,一个简单的选择是在您的构建方法中使用 StreamBuilder,在那里提取标记,然后显示它们:

Widget build(Buildcontext context){
  return StreamBuilder(
    stream: MarkersRepos().getMarkers(),
    builder:(context, snapshot){
     //check for connection state and errors, see streambuilder documentation
      final List<Marker> markers = snapshot.data;
      return loadMap(markers);
    }
  );
}

编辑:添加更多细节

【讨论】:

  • 非常感谢您提供全面的答案,我会尽快测试并返回此主题,希望它有效。
  • 您是否设法实施了解决方案,还是需要更多帮助?如果有帮助,您能否将我的答案标记为已接受?
最近更新 更多