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

Flutter Google Maps返回地图页触发MissingPluginException问题求助

问题原因分析

你遇到的MissingPluginException本质是第二次进入地图页时,代码调用了已经失效的GoogleMapController实例。核心问题出在全局单例的MapController和页面导航逻辑上:

  1. 当用Get.offAll(()=>Nav())跳转到仪表盘时,之前的地图页会被完全销毁,对应的原生Google Maps控件也会被释放,但你的MapController是全局单例,里面的googleMapController还持有旧的失效引用。
  2. Completer<GoogleMapController>在MapController初始化时只创建一次,第二次进入地图页时onMapCreated尝试complete一个已经完成的Completer,导致新的控制器实例无法正确绑定。
  3. liveLocation方法中的位置监听Stream没有在页面销毁时取消,会持续调用旧控制器的animateCamera方法,进一步触发错误。
解决方案

针对上述问题,从控制器重置、引用有效性检查、资源清理三个方面修改代码:

1. 修改MapController,添加重置和资源清理逻辑

更新MapController,处理可空控制器、位置监听订阅的清理,以及Completer的重置:

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class MapController extends GetxController{
  String locationmessage = 'currentlocation of user';
  late String lat ;
  late String long ;

  Set<Marker>markers={};

  // 改为可空控制器,避免持有失效引用
  GoogleMapController? googleMapController;
  // 保存位置监听订阅,用于页面销毁时取消
  StreamSubscription<Position>? _positionSubscription;

  Completer<GoogleMapController> _controller = Completer();
  Completer<GoogleMapController> get completercontrol => _controller;

  static  CameraPosition initial =  const CameraPosition(target:LatLng(10.228370,76.198799),zoom: 15);

  @override
  void onInit() {
    lat = '10.228370';
    long ='76.198799';
    super.onInit();
  }

  // 添加重置方法,离开地图页时调用
  void resetMapController() {
    // 取消位置监听,避免内存泄漏和无效回调
    _positionSubscription?.cancel();
    _positionSubscription = null;
    // 重置Completer,确保下次进入能绑定新控制器
    _controller = Completer();
    // 清空旧控制器引用
    googleMapController = null;
    update();
  }

  Future<Position> getCurrentLocation() async {
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      return Future.error('the location is not enabled');
    }
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        return Future.error(
            'location permissions are permanantly denied, cannot grant acess');
      }
    }
    if (permission == LocationPermission.deniedForever) {
      return Future.error(
          'location permissions are permanantly denied, cannot grant acess');
    }
    return await Geolocator.getCurrentPosition();
  }

  void liveLocation() {
    loadingbar();
    // 先取消旧订阅,避免重复监听
    _positionSubscription?.cancel();
    LocationSettings settings = const LocationSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: 100,
    );
    _positionSubscription = Geolocator.getPositionStream(locationSettings: settings)
        .listen((Position position) {
      lat = position.latitude.toString();
      long = position.longitude.toString();
      // 调用animateCamera前检查控制器是否有效
      if (googleMapController != null) {
        googleMapController!.animateCamera(CameraUpdate.newCameraPosition(
          CameraPosition(target:LatLng(position.latitude,position.longitude),zoom: 15)
        ));
      }
      markers.clear();
      markers.add(Marker(
        markerId:const MarkerId('current user location'),
        position: LatLng(position.latitude,position.longitude)
      ));
      update();
    });
    loadingbaroff();
  }

  void getlatlong(){
    loadingbar();
    getCurrentLocation().then((value){
      lat = '${value.latitude}';
      long = '${value.longitude}';
      // 检查控制器有效性后再调用animateCamera
      if (googleMapController != null) {
        googleMapController!.animateCamera(CameraUpdate.newCameraPosition(
          CameraPosition(target:LatLng(value.latitude,value.longitude),zoom: 15)
        ));
      }
      markers.clear();
      markers.add(Marker(
        markerId:const MarkerId('current user location'),
        position: LatLng(value.latitude,value.longitude)
      ));
      update();
      if (kDebugMode) {
        print(lat);
      }
      if (kDebugMode) {
        print(long);
      }
      liveLocation();
    });
    loadingbaroff();
  }

  void liveLocationToUpload(){
    // 同样处理订阅,避免内存泄漏
    _positionSubscription?.cancel();
    LocationSettings settings = const LocationSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: 100,
    );
    _positionSubscription = Geolocator.getPositionStream(locationSettings: settings)
        .listen((Position position) {
      lat = position.latitude.toString();
      long = position.longitude.toString();
      update();
    });
  }

  Future<void> fetchMap()async {
    //markers.clear();
    //markers.add(Marker(markerId:const MarkerId('current user location'),position: LatLng(position.latitude,position.longitude)));
  }

  bool isloading = false;

  void loadingbar() {
    isloading = true;
    update();
  }
  void loadingbaroff() {
    isloading = false;
    update();
  }

  @override
  void onClose() {
    // 页面销毁时自动清理资源
    _positionSubscription?.cancel();
    super.onClose();
  }
}

2. 修改Mappage页面,添加离开时的控制器重置

在跳转到仪表盘的按钮点击事件中,先调用resetMapController清理资源:

ElevatedButton(
  style:const ButtonStyle(backgroundColor:MaterialStatePropertyAll(Colors.redAccent)) ,
  onPressed: () {
    // 离开地图页前重置控制器
    mapcontroller.resetMapController();
    Get.offAll(()=>Nav());
  },
  child:const Text('Go to Dashboard',style: TextStyle(color: Colors.white),)
),

同时更新onMapCreated逻辑,确保每次进入都正确绑定新控制器:

onMapCreated: (GoogleMapController controller){
  // 直接赋值新控制器
  mapcontroller.googleMapController = controller;
  // 只有当Completer未完成时才调用complete
  if (!mapcontroller.completercontrol.isCompleted) {
    mapcontroller.completercontrol.complete(controller);
  }
},

3. 额外优化:移除不必要的ScrollView嵌套

你的Mappage中SingleChildScrollView嵌套会导致地图手势失效,建议直接移除:

// 移除SingleChildScrollView后的代码
SizedBox(
  height: MediaQuery.of(context).size.height,
  width: MediaQuery.of(context).size.width,
  child: Column(
    children: [
      Stack(
        children: [
          SizedBox(
            height: MediaQuery.of(context).size.height,
            child: GetBuilder<MapController>(builder: (_)=>GoogleMap(
              initialCameraPosition:MapController.initial,
              mapType: MapType.normal,
              markers:mapcontroller.markers,
              onMapCreated: (GoogleMapController controller){
                mapcontroller.googleMapController = controller;
                if (!mapcontroller.completercontrol.isCompleted) {
                  mapcontroller.completercontrol.complete(controller);
                }
              },
            ),)
          ),
          // 下方按钮组件...
        ],
      ),
    ],
  ),
),
关键说明
  • 控制器重置:每次离开地图页时重置Completer和控制器引用,确保下次进入能绑定新的原生控件实例。
  • 有效性检查:调用animateCamera前必须检查控制器是否为非空,避免调用已失效的原生方法。
  • 资源清理:取消位置监听的StreamSubscription,防止内存泄漏和无效回调。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 11:25:30