google_maps_flutter_web页面重载释放控制器报错求助
解决google_maps_flutter_web网页重载时dispose阶段的断言错误
核心解决方案
- 彻底清理定时器资源,避免Widget销毁后仍有任务执行
- 在所有更新UI的逻辑前增加
mounted检查,防止触发已销毁Widget的状态更新 - 可选:将
google_maps_flutter升级至最新稳定版本,官方可能已修复该web端的生命周期bug
修改后的完整代码
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:jm_connect_front/services/machines.dart'; import 'package:provider/provider.dart'; import '../../models/machine_model.dart'; import '../../providers/machine_provider.dart'; class JMMap extends StatefulWidget { const JMMap({super.key}); @override State<JMMap> createState() => JMMapState(); } class JMMapState extends State<JMMap> { late final Completer<GoogleMapController> _controller = Completer(); List<MachineModel>? allMachines; Map<MarkerId, Marker> markers = {}; Timer? timer; BitmapDescriptor? machineIcon; BitmapDescriptor? machineOnIcon; @override void initState() { super.initState(); _setCustomMarker(); WidgetsBinding.instance.addPostFrameCallback((_) { timer = Timer.periodic(const Duration(seconds: 1), (currentTimer) async { // 定时器已被取消或Widget已销毁,直接终止执行 if (!mounted || timer != currentTimer) return; await Machines().getMachines(context); if (!mounted) return; allMachines = Provider.of<MachineProvider>(context, listen: false).machineItems; updateMarkers(); }); }); } void _setCustomMarker() async { machineIcon = await BitmapDescriptor.fromAssetImage( const ImageConfiguration(), 'assets/images/machine_marker.png'); machineOnIcon = await BitmapDescriptor.fromAssetImage( const ImageConfiguration(), 'assets/images/machine_marker_on.png'); } @override void dispose() { // 取消定时器并置空,避免后续引用 timer?.cancel(); timer = null; // 清空标记,防止后续残留任务触发地图更新 setState(() { markers.clear(); }); super.dispose(); } void updateMarkers() { // Widget已销毁,直接返回 if (!mounted) return; final newMarkers = <MarkerId, Marker>{}; if (allMachines == null) return; for (var machine in allMachines!) { int lastUpdate = DateTime.now() .difference(DateTime.parse(machine.updatedAt.toString())) .inMinutes; final markerId = MarkerId(machine.id.toString()); final marker = Marker( markerId: markerId, position: LatLng(machine.latitude, machine.longitude), icon: lastUpdate > 2 ? machineIcon! : machineOnIcon!, ); newMarkers[markerId] = marker; } setState(() { markers.clear(); markers.addAll(newMarkers); }); } @override Widget build(BuildContext context) { return Scaffold( body: GoogleMap( mapType: MapType.hybrid, buildingsEnabled: false, compassEnabled: false, liteModeEnabled: false, indoorViewEnabled: false, mapToolbarEnabled: false, trafficEnabled: false, myLocationEnabled: false, tiltGesturesEnabled: false, zoomControlsEnabled: false, myLocationButtonEnabled: false, fortyFiveDegreeImageryEnabled: false, markers: Set<Marker>.of(markers.values), initialCameraPosition: CameraPosition( target: LatLng( allMachines?.isNotEmpty == true ? allMachines![0].latitude : -20.90381991398391, allMachines?.isNotEmpty == true ? allMachines![0].longitude : -47.59624569562974, ), zoom: 18, ), onMapCreated: (GoogleMapController controller) { _controller.complete(controller); }, ), ); } }
错误原因分析
报错的核心是网页重载时,Widget的dispose方法执行后,之前启动的定时器任务仍可能在微任务队列中等待执行。此时调用updateMarkers触发setState,会尝试更新GoogleMap的标记,但web端插件内部的GoogleMapController已经被释放,导致controller != null的断言失败,抛出Maps cannot be retrieved before calling buildView!错误。
通过在定时器回调、标记更新逻辑中增加mounted检查,以及在dispose时彻底清理定时器和标记数据,可以避免在Widget销毁后触发地图相关的更新操作,从而解决该错误。
内容的提问来源于stack exchange,提问作者Victor Garcia
相关产品推荐
相关产品推荐

