flutter_map icon indicating copy to clipboard operation
flutter_map copied to clipboard

[BUG] LateInitializationError: Field '_interactiveViewerState' has already been initialized

Open astraube opened this issue 1 year ago • 4 comments

What is the bug?

image

Error Message: LateInitializationError: Field '_interactiveViewerState@4245162146' has already been initialized.


LateInitializationError: Field '_interactiveViewerState@4245162146' has already been initialized.

StackTrace: {#0 LateError._throwFieldAlreadyInitialized (dart:_internal-patch/internal_patch.dart:184:5)
MapControllerImpl._interactiveViewerState= (package:flutter_map/src/map/controller/map_controller_impl.dart:19:40)
MapControllerImpl.interactiveViewerState= (package:flutter_map/src/map/controller/map_controller_impl.dart:47:7)
MapInteractiveViewerState.initState (package:flutter_map/src/gestures/map_interactive_viewer.dart:103:23)
StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5602:55)
ComponentElement.mount (package:flutter/src/widgets/framework.dart:5447:5)
Element.inflateWidget (package:flutter/src/widgets/framework.dart:4326:16)
Element.updateChild (package:flutter/src/widgets/framework.dart:3831:20)
_LayoutBuilderElement._layout.layoutCallback (package:flutter/src/widgets/layout_builder.dart:132:18)
BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2835:19)
 _LayoutBuilderElement._layout (package:flutter/src/widgets/layout_builder.dart:150:12)
RenderObject.invokeLayoutCallback. (package:flutter/src/rendering/object.dart:2657:59)
PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:1071:15)
RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:2657:14)
RenderConstrainedLayoutBuilder.rebuildIfNecessary (package:flutter/src/widgets/layout_builder.dart:225:7)
_RenderLayoutBuilder.performLayout (package:flutter/src/widgets/layout_builder.dart:308:5)
RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:52:11)
RenderStack._computeSize (package:flutter/src/rendering/stack.dart:581:43)
RenderStack.performLayout (package:flutter/src/rendering/stack.dart:608:12)
RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
RenderPadding.performLayout (package:flutter/src/rendering/shifted_box.dart:238:12)
RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
RenderProxyBoxMixin.performLayout (package:flutter/src/rendering/proxy_box.dart:104:21)
RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:52:11)
RenderFlex._computeSizes (package:flutter/src/rendering/flex.dart:868:45)
RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:903:32)
RenderObject.layout (package:flutter/src/rendering/object.dart:2546:7)
RenderBox.layout (package:flutter/src/rendering/box.dart:2389:11)
MultiChildLayoutDelegate.layoutChild (package:flutter/src/rendering/custom_layout.dart:173:12)
_ScaffoldLayout.performLayout (package:flutter/src/material/scaffold.dart:1062:7)
MultiChildLayoutDelegate._callPerformLayout (package:flutter/src/rendering/custom_layout.dart:237:7)
RenderCustomMultiChildLayoutBox.performLayout (package:flutter/src/rendering/custom_layout.dart:403:14)
RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:2385:7)
PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1025:18)
PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:1038:15)
RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:591:23)
WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:986:13)
RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:457:5)
SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1325:15)
SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1255:9)
SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1113:5)
_invoke (dart:ui/hooks.dart:312:13)
PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:383:5)
_drawFrame (dart:ui/hooks.dart:283:31)
}

How can we reproduce it?

  1. Create a page with two TabBar
  2. Include FlutterMap in the first TabBar
  3. When navigating between tabs, the FlutterMap component will crash.

Do you have a potential solution?

_interactiveViewerState

Platforms

Android 13

Severity

Minimum: Allows normal functioning

astraube avatar Feb 02 '24 13:02 astraube

Hi @astraube,

I could not reproduce this, using this code (adpated from the Flutter guide for working with tabs).
DefaultTabController(
        length: 2,
        child: Scaffold(
          appBar: AppBar(
            bottom: const TabBar(
              tabs: [
                Tab(icon: Icon(Icons.directions_car)),
                Tab(icon: Icon(Icons.directions_transit)),
              ],
            ),
            title: const Text('Tabs Demo'),
          ),
          body: TabBarView(
            children: [
              FlutterMap(
                options: const MapOptions(
                  initialCenter: LatLng(51.5, -0.09),
                  initialZoom: 11,
                ),
                children: [
                  openStreetMapTileLayer,
                  CircleLayer(
                    circles: [
                      CircleMarker(
                        point: const LatLng(51.5, -0.09),
                        color: Colors.blue.withOpacity(0.7),
                        borderColor: Colors.black,
                        borderStrokeWidth: 2,
                        useRadiusInMeter: true,
                        radius: 2000, // 2000 meters
                      ),
                      CircleMarker(
                        point: const LatLng(51.4937, -0.6638),
                        // Dorney Lake is ~2km long
                        color: Colors.green.withOpacity(0.9),
                        borderColor: Colors.black,
                        borderStrokeWidth: 2,
                        useRadiusInMeter: true,
                        radius: 1000, // 1000 meters
                      ),
                    ],
                  ),
                ],
              ),
              Icon(Icons.directions_transit),
            ],
          ),
        ),
      ),

The only way for this error to occur is if the state of the internal map 'viewer' becomes detached from the state of the internal controller - which could cause the initState of one to be fired 'without' the other - which should not happen when using FM correctly.

Your fix might fix the issue for you for now, but it likely hides an issue with your usage - are you using it in some kind of other state which is not intialised/disposed of correctly?

Please check you are running the latest version of FM, and try using MapOptions.keepAlive if necessary. You may also need to try the keep alive mixin on any states above the map.

JaffaKetchup avatar Feb 03 '24 11:02 JaffaKetchup

I'm getting this a lot as well. Not sure how to reproduce though. Sorry I don't have the whole stack but it was on the order of:

Exception caught by widgets library The following LateError was thrown building LayoutBuilder:

LateInitializationError: Field '_interactiveViewerState@1070162146' has already been initialized.

The relevant error-causing widget was: FlutterMap FlutterMap

corepuncher avatar Feb 11 '24 01:02 corepuncher

We need an MRE to reproduce this. Similar issues in the past have been very hard to reproduce. I would also suggest trying keepAlive, that might make a difference?

JaffaKetchup avatar Feb 12 '24 12:02 JaffaKetchup

This can apparently be reproduced by providing a UniqueKey to the FlutterMap, then rebuilding.

JaffaKetchup avatar Feb 13 '24 18:02 JaffaKetchup

Encountering this as well during a rebuild. This only happened after instantiating the MapController outside of the widget that used FlutterMap. The difference in my case is that I am not utilizing keys. FYI The code below results in a different exception/error, but this is essentially the same layout I have in my non-MRE, production app.

Exception
======== Exception caught by widgets library =======================================================
The following assertion was thrown building KeyedSubtree-[GlobalKey#05349]:
Should not update options unless they change
'package:flutter_map/src/map/controller/map_controller_impl.dart':
Failed assertion: line 342 pos 7: 'newOptions != value.options'

The relevant error-causing widget was: 
  Scaffold Scaffold:file:///home/joe/repos/bug/lib/main.dart:97:12
When the exception was thrown, this was the stack: 
#2      MapControllerImpl.options= (package:flutter_map/src/map/controller/map_controller_impl.dart:342:7)
#3      _FlutterMapStateContainer._setMapController (package:flutter_map/src/map/widget.dart:188:22)
#4      _FlutterMapStateContainer.initState (package:flutter_map/src/map/widget.dart:61:5)
#5      StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5611:55)
#6      ComponentElement.mount (package:flutter/src/widgets/framework.dart:5456:5)
...     Normal element mounting (28 frames)
#34     Element.inflateWidget (package:flutter/src/widgets/framework.dart:4335:16)
#35     MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:6893:36)
#36     MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6905:32)
...     Normal element mounting (332 frames)
#368    Element.inflateWidget (package:flutter/src/widgets/framework.dart:4335:16)
#369    MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:6893:36)
#370    MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6905:32)
...     Normal element mounting (688 frames)
#1058   Element.inflateWidget (package:flutter/src/widgets/framework.dart:4335:16)
#1059   MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:6893:36)
#1060   Element.updateChild (package:flutter/src/widgets/framework.dart:3846:18)
#1061   Element.updateChildren (package:flutter/src/widgets/framework.dart:4033:32)
#1062   MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6918:17)
#1063   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1064   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1065   StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#1066   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1067   StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#1068   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1069   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1070   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1071   ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#1072   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1073   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1074   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1075   ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#1076   _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:105:11)
#1077   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1078   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1079   StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#1080   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1081   StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#1082   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1083   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1084   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1085   ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#1086   _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:105:11)
#1087   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1088   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1089   StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#1090   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1091   StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#1092   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1093   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1094   StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#1095   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1096   StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#1097   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1098   SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#1099   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1100   SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:6765:14)
#1101   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1102   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1103   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1104   ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#1105   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1106   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1107   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1108   ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#1109   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1110   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1111   StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#1112   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1113   StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#1114   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1115   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1116   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1117   ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#1118   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1119   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1120   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1121   ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#1122   _InheritedNotifierElement.update (package:flutter/src/widgets/inherited_notifier.dart:105:11)
#1123   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1124   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1125   StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#1126   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1127   StatefulElement.update (package:flutter/src/widgets/framework.dart:5666:5)
#1128   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1129   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1130   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1131   ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#1132   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1133   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1134   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1135   StatelessElement.update (package:flutter/src/widgets/framework.dart:5556:5)
#1136   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1137   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1138   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1139   ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#1140   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1141   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1142   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1143   ProxyElement.update (package:flutter/src/widgets/framework.dart:5809:5)
#1144   Element.updateChild (package:flutter/src/widgets/framework.dart:3824:15)
#1145   ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5505:16)
#1146   StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5643:11)
#1147   Element.rebuild (package:flutter/src/widgets/framework.dart:5196:7)
#1148   BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2904:19)
#1149   WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:989:21)
#1150   RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:448:5)
#1151   SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1386:15)
#1152   SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1311:9)
#1153   SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1169:5)
#1154   _invoke (dart:ui/hooks.dart:312:13)
#1155   PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:399:5)
#1156   _drawFrame (dart:ui/hooks.dart:283:31)
main.dart
# main.dart
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
import 'package:go_router/go_router.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';

final MapController mapController = MapController();

final router = GoRouter(
  initialLocation: "/",
  routes: [
    GoRoute(path: "/", builder: (context, state) => const MyApp()),
    GoRoute(
      path: "/test",
      builder: (context, state) => Container(
        color: Colors.white,
        child: Center(
          child: TextButton(
            onPressed: () => context.go("/"),
            child: const Text("Go Home"),
          ),
        ),
      ),
    ),
  ],
);

void main() async {
  await FMTCObjectBoxBackend().initialise(
    rootDirectory: p.join((await getApplicationSupportDirectory()).absolute.path, "flutter_map_tile_cache"),
  );
  runApp(MaterialApp.router(
    routerConfig: router,
  ));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: SizedBox.expand(
        child: FlutterMap(
          mapController: mapController,
          children: [
            TileLayer(
              urlTemplate: 'https://api.mapbox.com/styles/v1/mapbox/streets-v12/tiles/256/{z}/{x}/{y}',
              userAgentPackageName: "com.V3ntus.test",
              maxZoom: 22,
              maxNativeZoom: 22,
              tileProvider: const FMTCStore('mapStore').getTileProvider(
                settings: FMTCTileProviderSettings(
                  cachedValidDuration: const Duration(days: 60),
                  errorHandler: (error) {
                    if (error.type == FMTCBrowsingErrorType.negativeFetchResponse) {
                      // do nothing - indicates cache miss and HTTP error
                    }
                  },
                ),
              ),
            ),
            Center(
              child: TextButton(
                onPressed: () => context.go("/test"),
                child: const Text("Navigate Away"),
              ),
            )
          ],
        ),
      ),
    );
  }
}
pubspec.yaml
# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.6
  flutter_map: ^6.1.0
  flutter_map_tile_caching: ^9.0.0-dev.8
  path: ^1.9.0
  path_provider: ^2.1.2
  go_router: ^13.2.2

dependency_overrides:
  flutter_map:
    git:
      url: https://github.com/fleaflet/flutter_map.git
      ref: 0fef8d6d36a735132b1b6cbe4624265beb4740bf

V3ntus avatar Apr 03 '24 18:04 V3ntus

This can apparently be reproduced by providing a UniqueKey to the FlutterMap, then rebuilding.

Just curious if anyone has tried this to reproduce, and, if it had the same effect as for me.

corepuncher avatar Apr 03 '24 18:04 corepuncher

If you need to insert your controller into some state management, this is the way I do it:

  1. Define the map controller on the widget with the map, outside of the build method. i.e. it's only accessible to that widget and map.

  2. Attach the controller to the map as normal.

  3. Use the onMapReady callback, and set the controller to whichever state you use.

  4. Be careful not to use it when the widget is built, or define the destroy method on the widget and set the state controlled controller to null

If you're not trying to do state management and/or this doesn't work, can you explain what you're trying to do?

JaffaKetchup avatar Apr 03 '24 18:04 JaffaKetchup

In my case, I'm trying to control the map outside of FlutterMap, thus attempting to define the controller outside of the widget class

V3ntus avatar Apr 03 '24 18:04 V3ntus

Ok, so my suggestion above should work. That's what I needed to do as well.

JaffaKetchup avatar Apr 03 '24 18:04 JaffaKetchup

Just wanted to confirm that the method JaffaKetchup suggested worked. This was my implementation:

  1. Define MapController in your map widget and pass it to the FlutterMap widget.
  2. Define a nullable MapController variable somewhere globally accessible. Ex. GlobalHandler.mapController
  3. MapOptions.onMapReady callback assigns the map controller to GlobalHandler.mapController
  4. In my FlutterMap parent widget's overloaded dispose() where you call MapController.dispose(), I assign null to GlobalHandler.mapController just to ensure I'm not using a disposed controller.

V3ntus avatar Apr 03 '24 20:04 V3ntus

I'm going to close this for now. I think some careful consideration is required when hooking the controller up to state, but as the above suggestion seems to work fine (and that's part of the purpose of the onMapReady callback), I think this is all good.

JaffaKetchup avatar Apr 25 '24 17:04 JaffaKetchup

Not sure what the fix would be for my case.

Here is the entirety of how I use MapController:

  @override
  Widget build(BuildContext context) {
      final mapState = Provider.of<MapState>(context);
  
 FlutterMap(
      mapController: mapState.map,

And in mapState file:

class MapState extends ChangeNotifier {
  final MapController _mapController = MapController();
  LatLng _centerPosition = const LatLng(35, -100);
  double zoomLevel = 7;
  double minZoom = 4;
  double maxZoom = 12;
  double _imgScale = 1.8;

  MapController get map => _mapController;
  LatLng get center => _centerPosition;
  double get zoom => zoomLevel;
  double get imgScale => _imgScale;
  
  }

Wasn't exactly sure how to implement the fix since I'm using provider. Thanks!

corepuncher avatar Apr 25 '24 22:04 corepuncher

This was the same situation I was in.

Follow the guide above. Initialise the MapController directly in the widget around the map, not in the state provider. Use this controller directly in the map. Then use the onMapReady callback to add the controller into state at that point only.

Also consider implementing better disposal using the widget lifecycle method if you find yourself inadvertantly using it after disposal.

+  final mapController = MapController();

   @override
   Widget build(BuildContext context) {
-      final mapState = Provider.of<MapState>(context);
  
       FlutterMap(
-          mapController: mapState.map,
+          mapController: mapController,
+          options: MapOptions(
+              onMapReady: () {
+                  context.read<MapState>().map = mapController; // Can't remember if that's exactly correct or not
+              },
+          ),
 class MapState extends ChangeNotifier {
-  final MapController _mapController = MapController();
+  MapController? _mapController;
   LatLng _centerPosition = const LatLng(35, -100);
   double zoomLevel = 7;
   double minZoom = 4;
   double maxZoom = 12;
   double _imgScale = 1.8;

-  MapController get map => _mapController;
+  MapController get map => _mapController ?? (throw StateError('Not yet attached to map'));
+  set map(MapController newController) { // Consider also accepting null to implement disposal
+      _mapController = newController;
+  }
   LatLng get center => _centerPosition;
   double get zoom => zoomLevel;
   double get imgScale => _imgScale;
}

JaffaKetchup avatar Apr 26 '24 08:04 JaffaKetchup

I've updated the documentation which should help! https://docs.fleaflet.dev/v/v7-beta/usage/programmatic-interaction/external-custom-controllers#usage-within-a-state-system-model

JaffaKetchup avatar Apr 26 '24 11:04 JaffaKetchup