flutter_map перемещает камеру с контроллером карты через BLoC

Я создаю приложение флаттера, используя шаблон BLoC и пакет flutter_map. Я хочу переместить камеру в определенное положение. Я пытаюсь передать контроллер карты в структуру моего блока и переместить камеру оттуда, но получаю сообщение об ошибке:
NoSuchMethodError: The getter 'onReady' was called on null.
Я не уверен, что это правильный подход.

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);   
      });
    }
  }
}


Виджет с картой

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

Я понятия не имею, почему у контроллера карты проблема с методом onReady.

5 ответов

У меня была аналогичная проблема с GetX.

Я решил, применив некоторые предпосылки: во-первых, я сохранил в виджете (без сохранения состояния) любые и все манипуляции с картой, потому что мне нужно переключаться между плагином google и flutter_map.

Затем в контроллере (в вашем случае в BloC) он просто запускает необходимую информацию, чтобы представление получило от него новое местоположение, которое должно быть центрировано, но представление - это тот, кто знает и централизует карту, а не BloC.

Короче говоря, мне пришлось работать со статической переменной, чтобы сохранить одноэлементную ссылку на mapController в приложении, потому что метод onReady () был вызван только один раз, поэтому я сохранил bool для управления состоянием карты, тогда как "onReady" не выполняется, у нас нет доступа к объектам карты, таким как "масштабирование" и "перемещение".

Мой пример кода:

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

}

Если вы используете BLoC (как управление состоянием), здесь есть пример, как вы можете сделать это безcontroller.

  1. поместите свой виджет внутри вашего пользовательского виджета.
  2. При вызове этого виджета используйтеkeyи оберните его с помощью BlocBuilder. В результатеFlutterMapвиджет будет перестроен с новым центром.

Пример:

      class _MapView extends StatelessWidget {
  const _MapView();

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<MapBloc, MapState>(
      builder: (context, state) {
        return Scaffold(
          body: MapWidget(
            key: Key('map${state.latitude}${state.longitude}'),
            latitude: state.latitude,
            longitude: state.longitude,
          ),
        );
      },
    );
  }
}


class MapWidget extends StatelessWidget {
  const MapWidget({
    super.key,
    required this.latitude,
    required this.longitude,
  });

  final double latitude;
  final double longitude;

  @override
  Widget build(BuildContext context) {
    final _searchingPoint = LatLng(
      latitude,
      longitude,
    );
    return FlutterMap(
      options: MapOptions(
        center: _searchingPoint,
        zoom: 11,
        keepAlive: true,
      ),
    );
  }
}

Источник: Гитхаб

Приведенные выше ответы верны, но я добавлю одну вещь, которая также была большой проблемой для меня. Как сохранить уровень масштабирования в соответствии с масштабированием пользователя, потому что, когда вы прослушиваете изменения текущего местоположения и обновляете положение камеры в соответствии с ним, уровень масштабирования также выбирает тот, который вы указали в начале.


например: вы предоставляете уровень масштабирования 13 в начале и масштабируете экран до 16, а затем, когда положение камеры обновляется, он снова возвращает вас на уровень масштабирования 13, и это повторяется каждую секунду, что действительно раздражает. Итак, у вас есть для динамического предоставления уровня масштабирования , который будет меняться в зависимости от уровня масштабирования пользователя.


Сначала прослушайте поток местоположения:

      location.onLocationChanged.listen((event) {
final newLatLng = LatLng(event.latitude!, event.longitude!);
// Use mapController to access the move method
// move() method is used for the camera position changing e.g: move(LatLng center,   double zoom)
mapController.move(newLatLng , 13);  // this will set the zoom level static to 13
// but we want it to be dynamic according to the user zoom level,
// so then use the mapController.zoom property will dynamically adjust your zoom level
 mapController.move(newLatLng , mapController.zoom); 
});

Звучит глупо, но вы инициализируете контроллер карты? Нравится MapController mapController = MapController();

Вы также можете сделать это с помощью BLOC. Просто используйтеBlocListenerи обновляйте значения контроллера в соответствии с вашим состоянием при каждом изменении состояния.

      Widget build(BuildContext context) {
    //create your map controller
    MapController mapController = MapController();
    ...
    //Attach a listener that is called when a state changes and control the MapController
    return BlocListener<AddFindingScreenBloc, AddFindingScreenState>(
        listener: (context, state) {
            mapController.move(LatLng(state.latitude, state.longitude), state.zoom);
         },
      child: 
          ...
          FlutterMap(
              mapController: mapController
     );
Другие вопросы по тегам