Flutter Google Maps返回地图页触发MissingPluginException问题求助
问题原因分析
你遇到的MissingPluginException本质是第二次进入地图页时,代码调用了已经失效的GoogleMapController实例。核心问题出在全局单例的MapController和页面导航逻辑上:
- 当用
Get.offAll(()=>Nav())跳转到仪表盘时,之前的地图页会被完全销毁,对应的原生Google Maps控件也会被释放,但你的MapController是全局单例,里面的googleMapController还持有旧的失效引用。 Completer<GoogleMapController>在MapController初始化时只创建一次,第二次进入地图页时onMapCreated尝试complete一个已经完成的Completer,导致新的控制器实例无法正确绑定。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
相关产品推荐
相关产品推荐

