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
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:
_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(),
),
],
),
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.
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),
],
),
);
}
}