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

Google Maps Flutter: 如何从Firebase RTDB加载多个动态标记到地图

功能需求

调用getAllDriversLocation()方法从Firebase RTDB获取司机坐标数据,实时渲染并更新到Google地图上,数据库层级为users > Driver Coordinates > 随机司机ID。

我的Firebase RTDB结构示意图

现有代码问题
  1. LocationCoordinates模型解析错误,longitude字段错误赋值为latitude的值
  2. 监听获取到司机坐标列表后,没有关联更新地图标记逻辑,标记不会同步渲染
  3. initState和onMapCreated中重复调用getAllDriversLocation(),会产生重复监听
  4. 用索引作为markerId的逻辑不合理,无法精准对应单个司机的坐标更新,容易出现重复标记
  5. 没有对数据库监听做取消处理,存在内存泄漏风险
修复后代码
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 16:18:03