flutterdart

Flutter mapController LateInitializationError


I was trying to setup a mapController to return the bounds of Flutter_map but this "LateInitializationError: Field '_internalController@' has not been initialized" error keeps coming up

import 'package:flutter/material.dart';
import 'dart:io';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:dio_cache_interceptor_file_store/dio_cache_interceptor_file_store.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_cache/flutter_map_cache.dart';
import 'package:latlong2/latlong.dart';
import 'package:path_provider/path_provider.dart';

class CityMap extends StatefulWidget {
  const CityMap({super.key});

  @override
  State<CityMap> createState() => _CityMapState();
}

class _CityMapState extends State<CityMap> with TickerProviderStateMixin {
  final Future<CacheStore> _cacheStoreFuture = _getCacheStore();

  static Future<CacheStore> _getCacheStore() async {
    final dir = await getTemporaryDirectory();
    return FileCacheStore('${dir.path}${Platform.pathSeparator}MapTiles');
  }

   var _mapController = MapController();

  List<LatLng> markertram = [const LatLng(51.502762, -0.113125)];

  List<Marker> buildMarker() {

    List<Marker> markers = [];
    LatLngBounds bounder = _mapController.camera.visibleBounds;

    void createMarkers(
        List<LatLng> positions, String metroNumber, List<String> polyLineIds) {
      List<Marker> tramMarkers = positions.where((position) {
        return bounder.contains(position);
      }).map((position) {
        return Marker(
          width: 20.0,
          height: 20.0,
          point: position,
          rotate: true,
          alignment: Alignment.center,
          child: GestureDetector(
            onTap: () {},
            child: Tooltip(
              message: " Metro $metroNumber",
              child: const Icon(Icons.tram_outlined),
            ),
          ),
        );
      }).toList();
      markers.addAll(tramMarkers);
    }

    createMarkers(markertram, '2', ['tram2']);

    return markers;
  }

  @override
  void initState() {
    super.initState();
    _mapController = MapController();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: FutureBuilder<CacheStore>(
            future: _cacheStoreFuture,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                final cacheStore = snapshot.data!;
                return Stack(
                  children: [
                    FlutterMap(
                      mapController: MapController(),
                      options: const MapOptions(
                        initialCenter: LatLng(51.515635, -0.092354),
                        initialZoom: 15.5,
                        maxZoom: 19.5,
                      ),
                      children: [
                        TileLayer(
                          urlTemplate:
                              'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
                              tileProvider: CachedTileProvider(
                        store: cacheStore,
                      ),
                        ),
                        MarkerLayer(
                          markers: buildMarker(),
                        ),
                      ],
                    ),
                  ],
                );
                
          } else if (snapshot.hasError) {
            return Center(child: Text(snapshot.error.toString()));
          }
          return const Center(child: CircularProgressIndicator());
        },
      ));
              }
            }

Everywhere i look th solution seems to be setting the mapController in initstate but that didn't work. but i also used futurebuilder to Delay Marker Rendering


Solution

  • The problem is like the error said, you use MapController to manage the FlutterMap before the FlutterMap rendered. There's a mistakes that you can fix:

    1. You declare _mapController but you use MapController() in FlutterMap widget.
    FlutterMap(
      // Use [_mapController] instead of MapController.
      // 
      // So you can manage the map using [_mapController].
      mapController: _mapController,
      options: const MapOptions(
        initialCenter: LatLng(51.515635, -0.092354),
        initialZoom: 15.5,
        maxZoom: 19.5,
      ),
      children: [
        TileLayer(
          urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
          tileProvider: CachedTileProvider(
            store: cacheStore,
          ),
        ),
        MarkerLayer(
          markers: buildMarker(),
        ),
      ],
    ),
    
    1. Calling buildMarker() inside FlutterMap

    This will caused an error because FlutterMap not rendered yet. There is an attribute onMapReady in MapOptions that provides this kind of case. My recommendation is to create new variable:

    List<Marker> _markersList = []
    

    Then in your FlutterMap option, fill _markerList value in onMapReady attribute:

    options: MapOptions(
      initialCenter: const LatLng(51.502762, -0.113125),
      initialZoom: 15.5,
      maxZoom: 19.5,
      // This is where you can manage Map using _mapController for the first time rendered.
      //
      // Before use _mapController, make sure that FlutterMap is already rendered,
      // this [onMapReady] provided that case.
      onMapReady: () {
        // This is where the [_markers] value filled with [buildMarker()]
        // Then refresh the state using `setState` to effect.
        _markerList = buildMarker();
        setState(() {});
      },
    ),
    

    With this two fixes, your app will be fine.

    Additional Minimal Reproducible Code

    I provide additional minimal reproducible code for you to try out this solution, i made this based on your code with some modification but still same logic.

    import 'package:flutter/material.dart';
    import 'package:flutter_map/flutter_map.dart';
    import 'package:latlong2/latlong.dart';
    
    class FlutterMapPage extends StatefulWidget {
      const FlutterMapPage({super.key});
    
      @override
      State<FlutterMapPage> createState() => _FlutterMapPageState();
    }
    
    class _FlutterMapPageState extends State<FlutterMapPage> {
      /// [mapController] use to programatically control the map camera & access
      /// some helper functionality - control camera.
      MapController mapController = MapController();
    
      /// Markers list on map
      List<Marker> _markers = [];
    
      Marker _buildMarker(LatLng position, String metroNumber) {
        const double markerSize = 20.0;
    
        Widget child = GestureDetector(
          onTap: () {},
          child: Tooltip(
            message: "Metro $metroNumber",
            child: const Icon(Icons.tram_outlined),
          ),
        );
    
        return Marker(
          width: markerSize,
          height: markerSize,
          point: position,
          rotate: true,
          alignment: Alignment.center,
          child: child,
        );
      }
    
      List<Marker> _getMarkerList() {
        // Initiate tram position
        const List<LatLng> tramsPosition = [LatLng(51.502762, -0.113125)];
    
        // Initiate metroNumber
        const String metroNumber = '2';
    
        // Initiate markers variable to store list of marker data in this function.
        final List<Marker> markers = [];
    
        // Initiate the map camera bounder position.
        final LatLngBounds bounder = mapController.camera.visibleBounds;
    
        // Get list of trams position inside [bounder].
        List<LatLng> tramInBounder = tramsPosition
            .where(
              (position) => bounder.contains(position),
            )
            .toList();
    
        // Iterate [tramInBounder] for add [tramInBounder] value to [markers]
        for (LatLng position in tramInBounder) {
          markers.add(_buildMarker(position, metroNumber));
        }
    
        return markers;
      }
    
      @override
      Widget build(BuildContext context) {
        const String urlTemplate = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
    
        return Scaffold(
          appBar: AppBar(title: const Text('Flutter Map')),
          body: FlutterMap(
            mapController: mapController,
            options: MapOptions(
              initialCenter: const LatLng(51.502762, -0.113125),
              initialZoom: 15.5,
              maxZoom: 19.5,
              // This is where you run a function with [_mapController].
              //
              // Before use _mapController, make sure that FlutterMap is already rendered,
              // this [onMapReady] provided that case.
              onMapReady: () {
                // This is where the [_markers] value filled with [buildMarker()]
                // Then refresh the state using `setState` to effect.
                _markers = _getMarkerList();
                setState(() {});
              },
            ),
            children: [
              TileLayer(urlTemplate: urlTemplate),
              MarkerLayer(markers: _markers),
            ],
          ),
        );
      }
    }
    

    The output

    Output Flutter Map