Контекст обратного вызова Flutter BottomSheet с провайдером - PullRequest
0 голосов
/ 02 марта 2020

У меня проблемы с плагином флейта GoogleMap, когда я закрываю showModalBottomSheet, получаю правильный обратный вызов с правильным контекстом.

Когда создается моя карта, вызывается onMapCreated(), а затем FAB_DrawerMarkers() это запустить. Он получает список объектов из БД SQFlite через провайдера, преобразует их в маркеры и затем dr aws при первом запуске приложения. У меня есть FAB, который открывает showModalBottomSheet, который используется в качестве диалогового окна настроек (охватывающих половину экрана). Когда я коснусь спутникового или обычного, я могу изменить GoogleMap MapType, и setState() сделает так, чтобы он мгновенно покрасил aws карту. Я также могу изменить возвращенный список объектов, отфильтрованный и т. Д. c.

Когда я закрываю showModalBottomSheet (или даже когда выбор сделан), я хочу, чтобы Карта запрашивала БД и перерисовывала маркеры. Тем не менее, я получаю контекстную ошибку, когда поставщик не может быть найден. Я попытался showModalBottomSheet .whenComplete(() => {}) и .then((value) {}), чтобы вызвать некоторую форму callback . Однако, когда я пытаюсь вызвать _FAB_DrawMarkers();, я обнаруживаю, что виджет еще не смонтирован, и поэтому провайдер не может быть найден.

Я привел несколько примеров состояния потока переменных с print() ниже. Поэтому я бы хотел:

  • Чтобы понять, как правильно выполнять обратный вызов после внесения изменений в настройки, заставить карту запрашивать БД (через провайдера), используя правильный контекст или время.
  • Если я могу сделать это без какого-либо обратного вызова (например, вызвать маркеры отрисовки при изменении настроек), это нормально.
  • Я пытался держать вещи в порядке, помещая их в отдельные файлы дротиков, и хочу убедиться, что это сделано правильно.

MAIN.dart >> SectionMain.dart >> SectionSettings - (основное состояние приложения передается дочерним элементам для возможности вызова setState ())

Main.dart:

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<MyAppStateContainer>(
      create: (context) => MyAppStateContainer(),
      child: MaterialApp(
        home: Builder(
          builder: (context) => SectionMain(mainAppStateRef: this)

MyAppStateContainer.dart:

class MyAppStateContainer extends ChangeNotifier {
  MyAppStateContainer();

  MapType _mapType = MapType.terrain;
  String _someValue = "ABC";

  String get getSomeValue => _someValue;
}

SectionMain.dart:

SectionSettings sectionSettings = SectionSettings();

class SectionMain extends StatefulWidget {
  const SectionMain({Key key, @required this.mainAppStateRef}) : super(key: key);
  final State mainAppStateRef;

  @override
  _SectionMain createState() => _SectionMain();
}

class _SectionMain extends State<SectionMain> {

  BuildContext thisMapPageContext;

  final Set<Marker> _markers = {};
  GoogleMapController mapController;
  MapMarkers mapMarkers = MapMarkers();

  ...

  void callbackTest() {
    print("************************ callback again");
    _FAB_DrawMarkers();
  }


  void _onMapCreated(GoogleMapController controller) {
    mapController = controller;

    //Load custom marker images
    mapMarkers.init(); //load custom marker images

    //trigger draw markers
    _FAB_DrawMarkers();
  }


  @override
  Widget build(BuildContext context) {

    this.thisMapPageContext = context;

    return Scaffold(
      appBar: AppBar(
        title: TextMy Map'),
        backgroundColor: Colors.green[700],
      ),

      //Put in a stack widget so can layer other widgets on top of map widget
      body: Stack(
        children: <Widget>[
          //Builder so we can get a CTX made, and then Provider.of() finds the Provider
          Builder(
            builder: (context) => GoogleMap(
              mapType: Provider.of<MyAppStateContainer>(context).getMapType,    <--- works fine
              minMaxZoomPreference: MinMaxZoomPreference(5,8),
              markers: _markers,
              onMapCreated: _onMapCreated,
              initialCameraPosition: CameraPosition(
                target: _center,
                zoom: 6, //11.0,
              ),
              mapToolbarEnabled: false,
              myLocationEnabled: true,
              onTap: (LatLng location) {
                setState(() {
                  ...
                });
              },
            ),
          ),


          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Align(
                alignment: Alignment.bottomCenter,
                child:
                Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
                  Builder(
                    builder: (context) =>
                        FloatingActionButton(
                            child: Icon(Icons.settings, size: 36.0),
                            backgroundColor: Colors.green,
                            onPressed: () {
                              sectionSettings.onSheetShowContents(widget.mainAppStateRef, context);
                            }),
                  ),

                  ...

                  Builder(
                    builder: (context) =>
                        FloatingActionButton(
                          onPressed: () {
                            _FAB_DrawMarkers();
                          },
                          materialTapTargetSize: MaterialTapTargetSize.padded,
                          backgroundColor: Colors.green,
                          child: const Icon(Icons.pin_drop, size: 36.0),
                        ),
                  ),

            ...
                ])),
          ),
        ],
      ),
    );
  }

  void _FAB_DrawMarkers() async {
    BuildContext getContext = this.thisMapPageContext;

    print("############ PROBLEM HERE:");
    print(getContext);
    print(this.widget);
    print(this);
    print(this.mounted);
    print("############");

    final selectedValue = Provider.of<MyAppStateContainer>(getContext, listen:false).getSomeValue;     <--- works first time

    db_manager.DatabaseHelper helper = db_manager.DatabaseHelper.instance;

   ...

  }
}

void settingsClosedCallBack() {
  print("CallBack");
  _SectionMain().callbackTest();

}

SectionSettings.dart:

class SectionSettings {
  State MainState;

  Future<dynamic> onSheetShowContents(State mainAppState, BuildContext context) {

    MainState = mainAppState;

    return showModalBottomSheet(
        //showBottomSheet(

        context: context,
        builder: (context) {
          return ListView(
            padding: EdgeInsets.all(15.0),
            children: <Widget>[
              ListTile(
                title: Text("Map Settings"),
                selected: true,
              ),

              ...

                          ChoiceChip(
                            label: Text("Normal Map"),
                            selected: Provider.of<MyAppStateContainer>(context,listen: false).getMapType == MapType.terrain,
                            onSelected: (value) {

                              Provider.of<MyAppStateContainer>(context,listen: false).setMapType(MapType.terrain);

                              MainState.setState(() {

                              });

                            },
                          ),

                          ChoiceChip(
                            label: Text("Satellite Map"),
                            selected: Provider.of<MyAppStateContainer>(context,listen: false).getMapType == MapType.satellite,
                            onSelected: (value) {

                              Provider.of<MyAppStateContainer>(context, listen: false).setMapType(MapType.satellite);

                              MainState.setState(() {

                              });

                            },
                          ),

                  ...

            ],
          );

        //}).whenComplete(() => {
        }).then((value) {
          //TODO Callback...
          //TODO - 1 - Try giving the right context
          //TODO - 2 - Try updating the map when items are changed, not during close. The widget seems to be unmounted
          //callback to trigger redraw if changed/dirty
          //print("*** ShowBottomSheetClosed")
          //mainAppState.
          //context
          //mainAppState.setState(() { })
          //MainState.setState(() { reloadMapMarkers(ctx); });

          settingsClosedCallBack();

          //mainAppState.setState(() {  settingsClosedCallBack(); })
        }
        );
  }
}

Отладочные выходы:

  void _FAB_DrawMarkers() async {
    BuildContext getContext = this.thisMapPageContext;

    print("############ PROBLEM HERE:");
    print(getContext);
    print(this.widget);
    print(this);
    print(this.mounted);
    print("############");

_FAB_DrawMarkers() - Первый прогон, вызванный после onMapCreated():

I/flutter (11115): ############ CONTEXT:
I/flutter (11115): SectionMain(state: _SectionMain#d8dda)
I/flutter (11115): SectionMain
I/flutter (11115): _SectionMain#d8dda
I/flutter (11115): true
I/flutter (11115): ############

_FAB_DrawMarkers() - Второй прогон, после попытки обратного вызова из закрытия BottomSheet:

I/flutter (11115): ############ CONTEXT:
I/flutter (11115): null
I/flutter (11115): null
I/flutter (11115): _SectionMain#2e084(lifecycle state: created, no widget, not mounted)
I/flutter (11115): false
I/flutter (11115): ############

Основная ошибка, которую я получаю это:

[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The getter 'owner' was called on null.
E/flutter (11115): Receiver: null
E/flutter (11115): Tried calling: owner
E/flutter (11115): #0      Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
E/flutter (11115): #1      Provider.of (package:provider/src/provider.dart:193:15)
E/flutter (11115): #2      _SectionMain._FAB_DrawMarkers (package:flutter_app/section_main.dart:255:38)
E/flutter (11115): #3      _SectionMain.callbackTest (package:flutter_app/section_main.dart:60:5)
E/flutter (11115): #4      settingsClosedCallBack (package:flutter_app/section_main.dart:344:18)
E/flutter (11115): #5      SectionSettings.onSheetShowContents.<anonymous closure> 

, где ошибка, конечно, указывает на строку поставщика: final selectedValue = Provider.of<MyAppStateContainer>(getContext, listen:false).getSomeValue; Я надеялся, что whenCompleted() или then() из нижней таблицы будет гарантировать, что ма в виджете был смонтирован до вызова этих функций. Возможно, проблема в том, что я звоню им, когда лист закрывается, но до того, как основной виджет сможет перемонтировать.

Я довольно новичок во Флаттере, поэтому большое спасибо за совет, который вы можете предложить здесь , Я уверен, что это связано с тем, что я неправильно передаю контекст или не развиваюсь корректно декларативно во Flutter.

Большое спасибо, J

...