liodali / osm_flutter

OpenStreetMap plugin for flutter
https://pub.dev/packages/flutter_osm_plugin
MIT License
236 stars 98 forks source link

Can't use setState or provider listen:true #454

Open suzannetyers opened 1 year ago

suzannetyers commented 1 year ago

If I try to use Provider.of(context, listen: true) or setState() in the didChangeDependencies method of the stateful widget containing the OSMFlutter map I get the following error.

I was implementing a successful work around where the data required for the map was being loaded from the API via provider on the previous screen, then the map screen simply requesting the data from memory on widget build. This worked fine but now I face problems updating the map locations on coming back to the map screen with Navigator.pop. Because there was no state change it doesn't call didChangeDependencies so the map screen doesn't know there is new data to retrieve to update the map.

══╡ EXCEPTION CAUGHT BY SCHEDULER LIBRARY ╞═════════════════════════════════════════════════════════ The following assertion was thrown during a scheduler callback: Assertion failed: org-dartlang-sdk:///lib/_engine/engine/canvaskit/embedded_views.dart:533:7 debugInvalidViewIds == null || debugInvalidViewIds!.isEmpty "Cannot render platform views: 3. These views have not been created, or they have been deleted."

When the exception was thrown, this was the stack: dart-sdk/lib/_internal/js_dev_runtime/private/ddcruntime/errors.dart 294:49 throw dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 35:3 assertFailed lib/_engine/engine/canvaskit/embedded_views.dart 533:57 submitFrame lib/_engine/engine/canvaskit/rasterizer.dart 36:33 draw lib/_engine/engine/canvaskit/renderer.dart 367:16 renderScene lib/_engine/engine/platform_dispatcher.dart 744:14 render lib/ui/window.dart 108:50 render packages/flutter/src/rendering/view.dart 239:13 compositeFrame packages/flutter/src/rendering/binding.dart 498:18 drawFrame packages/flutter/src/widgets/binding.dart 918:13 drawFrame packages/flutter/src/rendering/binding.dart 360:5 [_handlePersistentFrameCallback] packages/flutter/src/scheduler/binding.dart 1297:15 [_invokeFrameCallback] packages/flutter/src/scheduler/binding.dart 1227:9 handleDrawFrame packages/flutter/src/scheduler/binding.dart 1085:5 [_handleDrawFrame] lib/_engine/engine/platform_dispatcher.dart 1304:13 invoke lib/_engine/engine/platform_dispatcher.dart 278:5 invokeOnDrawFrame lib/_engine/engine/initialization.dart 185:45 dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 574:37 _checkAndCall dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 579:39 dcall ════════════════════════════════════════════════════════════════════════════════════════════════════ Another exception was thrown: Assertion failed: org-dartlang-sdk:///lib/_engine/engine/canvaskit/embedded_views.dart:533:7 Error: PlatformException(unregistered_view_type, A HtmlElementView widget is trying to create a platform view with an unregistered type: ., If you are the author of the PlatformView, make sure registerViewFactory is invoked., null) dart-sdk/lib/_internal/js_dev_runtime/private/ddcruntime/errors.dart 294:49 throw packages/flutter/src/services/message_codecs.dart 652:7 decodeEnvelope packages/flutter/src/services/platform_channel.dart 310:18 _invokeMethod dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 45:50 dart-sdk/lib/async/zone.dart 1661:54 runUnary dart-sdk/lib/async/future_impl.dart 156:18 handleValue dart-sdk/lib/async/future_impl.dart 840:44 handleValueCallback dart-sdk/lib/async/future_impl.dart 869:13 _propagateToListeners dart-sdk/lib/async/future_impl.dart 641:5 [_completeWithValue] dart-sdk/lib/async/future_impl.dart 715:7 callback dart-sdk/lib/async/schedule_microtask.dart 40:11 _microtaskLoop dart-sdk/lib/async/schedule_microtask.dart 49:5 _startMicrotaskLoop dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 181:15 TypeError: Cannot read properties of null (reading 'firstChild') packages/flutter_osm_web/src/asset/map.js 212:58 getIframe packages/flutter_osm_web/src/asset/map.js 201:22 setUpMap packages/flutter_osm_web/src/controller/web_osm_controller.dart 91:13 initPositionMap dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 84:54 runBody dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 127:5 _async packages/flutter_osm_web/src/controller/web_osm_controller.dart 87:31 initPositionMap packages/flutter_osm_interface/src/map_controller/base_map_controller.dart 51:31 dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 84:54 runBody dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 127:5 _async packages/flutter_osm_interface/src/map_controller/base_map_controller.dart 50:50 dart-sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart 48:19 internalCallback

liodali commented 1 year ago

can you provide us sample code ?

suzannetyers commented 1 year ago

Same code I posted on the Heroku / mobile Safari issue. You can see the didChangeDependencies method commented out in that file because of this error.

suzannetyers commented 1 year ago

Same error occurs using Consumer(builder: (ctx, auth, _) => Scaffold( body: OSMFlutter( ... in widget build return

liodali commented 1 year ago

put OSMFlutter in child of the Consumer better

liodali commented 1 year ago

map should be not be rebuild even if the data change because you have the mapController where you can remove all the marker and put another marker or even change current position try to make map doesn depend on those type of data

suzannetyers commented 1 year ago

My map displays a set of 40 locations downloaded via API. Its a treasure hunt game. When a QR code is scanned and a location is set to 'found' I want to update the map to show different icons for found / not yet found locations. So coming back to the map screen from the location detail screen it needs to refresh the map.

How then would I trigger asking the mapController to update the static points when we return to the map screen please?

suzannetyers commented 1 year ago

Just updated my project so that the map is in a stateless widget and the parent screen passes it the static points and mapcontroller.

I can now initially listen for provider changes and use setState in the parent screen. But if a change is triggered anywhere in the app, then the map screen calls didChangeDependencies and throws the same error.

Please help, this is making me crazy =D

liodali commented 1 year ago

for sure I will help. but I need to see more code and also use OSMMixinObserver in your parrent widget and observed in initState controller.addObserver(this); try to call Provider.of<ConfirmProvider>(context, listen: true).getTeamNameAndRoute(); in mapIsReadywhen isReady==true lets do this first

suzannetyers commented 1 year ago

thank you I appreciate it.

Yes I'm using the OSMMixinObserver in the parent screen.

Calling Provider.of(context, listen: true) in the mapIsReady function causes the following error:

"Tried to listen to a value exposed with provider, from outside of the widget tree.\n\nThis is likely caused by an event handler (like a button's onPressed) that called\nProvider.of without passing listen: false.\n\nTo fix, write:\nProvider.of(context, listen: false);\n\nIt is unsupported because may pointlessly rebuild the widget associated to the\nevent handler, when the widget tree doesn't care about the value.\n\nThe context used was: MapScreen2(dependencies: [MediaQuery], state: _MapScreen2State#dcf80)\n"

liodali commented 1 year ago

is the map widget or parent is outside of ChangeProviderNotifier ?

suzannetyers commented 1 year ago

Here's a simplified version of main.dart

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
        providers: [
          ChangeNotifierProvider<AuthProvider>(
              create: (_) => AuthProvider()),
          ChangeNotifierProvider<RouteProvider>(
              create: (_) => RouteProvider()),
        ],
        child: Consumer<AuthProvider>(
          builder: (ctx, auth, _) => MaterialApp(
            title: 'My Game',
            home: auth.loggedin
                ? const HomeScreen()
                : const LoginScreen(),
            },
            routes: {
              LoginScreen.routeName: (ctx) => const LoginScreen(),
              HomeScreen.routeName: (ctx) => const HomeScreen(),
              MapScreen.routeName: (ctx) => const MapScreen(),
            },
          ),
        ));
  }
}

MapScreen is the parent that has the OSMMixinObserver and passes mapcontroller and static Points to the stateless Map widget in its return so like:

return Scaffold ( body: MapWidget(controller, staticpoints)

liodali commented 1 year ago

the statics points are POIs that you get from api, is the api send the new point each time you enter to the page or you have some logic to change the POIs depend on region ? because maybe you dont need to pass staticpoints to MapWidget

liodali commented 1 year ago

can you show me how you're updating the statics in provider ? and how you're watching the update to keep update the map ?

suzannetyers commented 1 year ago

The static points initially come from the API as a List and displayed on the map. When a QR code is scanned the corresponding location object in the locations list gets updated with location.found = 1 The map would ideally then refresh to show different marker icons for locations that have been found, or not. With the map itself as a stateless widget and the parent screen using the mixin, I can successfully listen for and be notified of changes in the provider. But as soon as a change is happens (data in the list of routes has changed) the parent screen's didChangeDependencies method gets called, the staticPoints get regenerated with their updated markerIcon settings, and then -this where the error occurs- the map gets asked to redraw.

I'm having to rely on a work around for now that redirects the user back to an overview screen (which sits before the map screen in the stack) whenever they try to navigate to the map screen from somewhere else. This way I can generate the latest staticpoints from the locations list on the overview screen, then when the map screen gets pushed the new map loads fresh and displays fine.

If the map could be redrawn that would be much better for UX.

One thing that happens, once you Navigator.pop() the map screen, pushing it again throws this up in the console: SyntaxError: Identifier 'OSMJS' has already been declared

liodali commented 1 year ago

I will fix the issue of the OSMJS thnx for report you dont need to put Provider.of<ConfirmProvider>(context, listen: true) in didChangeDependencies that should be listen only once

suzannetyers commented 1 year ago

It is in there with a bool check so it only gets called once.

liodali commented 1 year ago

for OSMJS are you calling dispose of MapController in the parent widget ?

suzannetyers commented 1 year ago

yes like this


  void dispose() {
    _mapController.dispose();
    super.dispose();
  }
liodali commented 1 year ago

if you close and you open again too quickly you can see I will extract that code into another file and try to manage it separatly but can you provide me some updates on you issue are you still facing the same issue

suzannetyers commented 1 year ago

yes still the issue in v0.60.5 - the map cannot refresh/redraw when the points data changes.