Google Maps Flutter: 如何从Firebase RTDB加载多个动态标记到地图
功能需求
调用getAllDriversLocation()方法从Firebase RTDB获取司机坐标数据,实时渲染并更新到Google地图上,数据库层级为users > Driver Coordinates > 随机司机ID。

现有代码问题
LocationCoordinates模型解析错误,longitude字段错误赋值为latitude的值- 监听获取到司机坐标列表后,没有关联更新地图标记逻辑,标记不会同步渲染
initState和onMapCreated中重复调用getAllDriversLocation(),会产生重复监听- 用索引作为markerId的逻辑不合理,无法精准对应单个司机的坐标更新,容易出现重复标记
- 没有对数据库监听做取消处理,存在内存泄漏风险
修复后代码
import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:location/location.dart'; import 'package:flutter/services.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:fluttertoast/fluttertoast.dart'; // 你自己引入的其他依赖 class MainScreen extends StatefulWidget { static const String idScreen = "MainScreen"; @override _MainScreenState createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> { final FirebaseAuth auth = FirebaseAuth.instance; final DatabaseReference databaseReference = FirebaseDatabase.instance.ref(); final Location _locationTracker = Location(); final Completer<GoogleMapController> _controllerGoogleMaps = Completer(); late GoogleMapController newGoogleMapController; final Map<MarkerId, Marker> _markers = <MarkerId, Marker>{}; late BitmapDescriptor pinLocationIcon; LocationData? currentLocation; List<double> currentCoordinates = []; double currentLatForAdress = 0.0; double currentLongForAdress = 0.0; StreamSubscription? _locationSubscription; StreamSubscription? _driversLocationSubscription; MapType? _mapType; static final CameraPosition _kGooglePlex = CameraPosition( target: LatLng(39.04475400135195, 125.75316892920314), zoom: 18.0, tilt: 70, ); void getCurrentLocation() async { try { var position = await _locationTracker.getLocation(); currentCoordinates.add(position.latitude!); currentCoordinates.add(position.longitude!); if (_locationSubscription != null) { _locationSubscription!.cancel(); } _locationSubscription = _locationTracker.onLocationChanged.listen( (newLocalData) { if (_controllerGoogleMaps.isCompleted) { setState(() { currentLatForAdress = newLocalData.latitude!; currentLongForAdress = newLocalData.longitude!; }); } }, ); } on PlatformException catch (e) { if (e.code == 'PERMISSION_DENIED') { debugPrint("Permission Denied"); } } } logOut() async { auth.signOut(); } Future<void> startForegroundService() async { await ForegroundService().start(); } Future getAllDriversLocation() async { // 避免重复监听,先取消已有的监听 if(_driversLocationSubscription != null){ _driversLocationSubscription!.cancel(); } _driversLocationSubscription = databaseReference .child("users") .child("Driver Coordinates") .onValue .listen((event) { _markers.clear(); if (event.snapshot.value == null) { setState(() {}); return; } Map.from(event.snapshot.value).forEach((randomId, driverData) { LocationCoordinates driverLoc = LocationCoordinates.fromJson(Map.from(driverData)); final MarkerId markerId = MarkerId(driverLoc.name); final Marker marker = Marker( markerId: markerId, position: LatLng(driverLoc.latitude, driverLoc.longitude), infoWindow: InfoWindow(title: driverLoc.name), icon: pinLocationIcon, ); _markers[markerId] = marker; }); setState(() {}); }); } @override void dispose() { if (_locationSubscription != null) { _locationSubscription!.cancel(); } if (_driversLocationSubscription != null) { _driversLocationSubscription!.cancel(); } super.dispose(); } @override initState() { super.initState(); getCurrentLocation(); BitmapDescriptor.fromAssetImage(ImageConfiguration(devicePixelRatio: 2.5), 'assets/images/customPIN.png') .then( (onValue) { pinLocationIcon = onValue; getAllDriversLocation(); }, ); } @override Widget build(BuildContext context) { return Scaffold( extendBodyBehindAppBar: true, appBar: AppBar( iconTheme: IconThemeData(color: Colors.black), elevation: 0, toolbarHeight: 50, backgroundColor: Colors.white.withOpacity(0.0), title: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Multiple Moving Markers", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20, color: Colors.black, ), ), ], ), ), body: Stack( children: [ GoogleMap( padding: const EdgeInsets.only(top: 100), markers: Set<Marker>.of(_markers.values), trafficEnabled: true, compassEnabled: false, myLocationEnabled: true, tiltGesturesEnabled: true, zoomGesturesEnabled: true, zoomControlsEnabled: false, initialCameraPosition: _kGooglePlex, mapType: _mapType ?? MapType.normal, onMapCreated: (GoogleMapController controller) { Future.delayed(Duration(milliseconds: 100)); controller.setMapStyle(Utils.mapStyles); _controllerGoogleMaps.complete(controller); newGoogleMapController = controller; }, ), ], ), ); } } class LocationCoordinates { final double latitude; final double longitude; final String name; LocationCoordinates({ required this.latitude, required this.longitude, required this.name, }); static LocationCoordinates fromJson(Map<dynamic, dynamic> json) { return LocationCoordinates( latitude: json["latitude"]?.toDouble() ?? 0.0, longitude: json["longitude"]?.toDouble() ?? 0.0, name: json["DriverUID"] ?? "未知司机"); } } displayToastMessage(String message, BuildContext context) { Fluttertoast.showToast(msg: message); }
补充说明
- 用司机唯一ID作为markerId,坐标更新时会直接覆盖对应标记,不会出现重复标记
- 增加了空安全处理,避免数据为空时崩溃
- 监听前先取消已有监听,避免重复监听问题
- 图标加载完成后再调用拉取司机坐标的方法,避免自定义图标不显示的问题
内容的提问来源于stack exchange,提问作者waldokhalid
相关产品推荐
相关产品推荐

