I'm building a flutter app using BLoC pattern and flutter_map package. I'd like to move camera to particular position. I'm trying to pass map controller to my bloc structure and move camera from there but im getting an error:
NoSuchMethodError: The getter 'onReady' was called on null.
I'm not sure if this is the right approach.
class HomePage extends StatelessWidget {
const HomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
...,
BlocProvider<MapBloc>(
create: (BuildContext context) => MapBloc(mapController: MapController()) // passing map controller
..add(MapDataInit()),
)
],
...
);
}
}
map_bloc.dart
class MapBloc extends Bloc<MapEvent, MapState> {
final MapController mapController;
LocationRepository _locationRepository = LocationRepository();
MapBloc({@required this.mapController});
@override
get initialState => MapDataUninitialized();
@override
Stream<MapState> mapEventToState(MapEvent event) async* {
final currentState = state;
if (event is AddMarker) {
yield MapDataLoaded(
mapController: this.mapController,
markers: [...]);
this.add(MoveCamera(event.latLan)); // not sure about this
}
if (event is MoveCamera) {
mapController.onReady.then((result) { // i'm getting an error here
mapController.move(event.latLan, 15.0);
});
}
}
}
Widget with map
class SelectLocationView extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<MapBloc, MapState>(
builder: (context, state) {
...
if (state is MapDataLoaded) {
return Container(
child: Center(
child: Container(
child: FlutterMap(
mapController: state.mapController, // I'm trying to get previously defined controller
options: MapOptions(...),
layers: [
TileLayerOptions(
urlTemplate:
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c']),
MarkerLayerOptions(...),
],
))),
);
}
},
);
}
}
I have no idea why the map controller has a problem with onReady method.
I had a similar problem using GetX.
I decided by applying some premises: First, I kept in the Widget (stateless) any and all manipulation of the map because I need to switch between the google plugin and the flutter_map.
Then, in the controller (in your case in BloC), it just triggers the necessary information so that the view receives from it the new location that must be centered, but the view is the one who knows and who centralizes the map and not the BloC.
In short, I had to work with the static variable to keep the singleton reference of the mapController in the application because the "onReady ()" method was only called once and so I kept the bool to control the state of the map whereas, while "onReady "it is not executed, we do not have access to map objects such as" zoom "and" move ".
My sample code:
class FlutterMapWidget extends StatelessWidget {
static MapController _mapController;
bool isMapRead = false;
FlutterMapWidget() {
// create instance only not exists another reference
if(_mapController == null) {
_mapController = MapController();
}
// after onReady, flag local control variable
_mapController.onReady.then((v) {
isMapRead = _mapController.ready;
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
_buildMap(),
// add another map components ...
],
);
}
void _gotoLocation(double lat,double long) {
//check if map is ready to move camera...
if(this.isMapRead) {
_mapController.move(LatLng(lat, long), _mapController?.zoom);
}
}
Widget _buildMap(){
return GetBuilder<MapEventsController>( // GetX state control
init: _mapEventsController, // GetX events controller
builder: (value) { // GetX event fire when update state in controller
updatePinOnMap(value.liveUpdate, value.location);
if(value.liveUpdate){
_gotoLocation(value.location.lat, value.location.lng);
}
return FlutterMap(
mapController: _mapController,
options: MapOptions(
center: LatLng(value?.location?.lat, value?.location?.lng),
zoom: 13.0,
plugins: [
],
onTap: _handleTap,
),
layers: [
TileLayerOptions(
urlTemplate:
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c']
),
MarkerLayerOptions(markers: markers), //markers
],
);
},
);
}
void updatePinOnMap(bool liveUpdate, Location location) async {
if(location == null) {
return;
}
this.markers.clear();
Marker mark = Marker(
point: LatLng(location.lat, location.lng),
anchorPos: anchorPos,
builder: (ctx) =>
GestureDetector(
onTap: () => _configureModalBottomSheet(ctx),
child: Container(
child: Icon(Icons.my_location, size: 28, color: Colors.red,), // FlutterLogo(),
),
),
);
markers.add(mark);
}
void _handleTap(LatLng latlng) {
}
void _configureModalBottomSheet(context) {
}
}