flutterdartblocflutter-blocfluttermap

flutter_map move camera with map controller through BLoC


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.


Solution

  • 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) {
      
      }
    
    }