rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
6.32k stars 961 forks source link

Cannot use "ref" after the widget was disposed #3644

Open SdxCoder opened 4 months ago

SdxCoder commented 4 months ago

Describe the bug In our production app from firebase crashlytics, we are getting this exception being logged .i.e Bad state: Cannot use "ref" after the widget was disposed.

Crash Log

  Non-fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: Bad state: Cannot use "ref" after the widget was disposed.
       at ConsumerStatefulElement._assertNotDisposed(consumer.dart:550)
       at ConsumerStatefulElement.read(consumer.dart:619)
       at StoresMapComponent.build.<fn>(stores_map_component.dart:35)
       at _GoogleMapState.onPlatformViewCreated(google_map.dart:441)

To Reproduce We are setting google map controller on onMapCreated callback

 GoogleMap(
            onMapCreated: (controller) {
              ref
                  .read(
                    getMapControllerProvider(MapToLoad.stores).notifier,
                  )
                  .update(IMapController.googleaMaps(controller));
            },
            zoomControlsEnabled: false,
            myLocationButtonEnabled: false,
            buildingsEnabled: false,
            myLocationEnabled: true,
            markers: markers.values.map((e) => e.toGoogleMapMarker()).toSet(),
            initialCameraPosition: cameraPosition.toGoogleMapCameraPosition(),
          )

// Map Controller Provider 
final getMapControllerProvider = NotifierProvider.autoDispose
    .family<GetMapControllerNotifier, IMapController?, MapToLoad>(
  () {
    return GetMapControllerNotifier();
  },
);

class GetMapControllerNotifier
    extends AutoDisposeFamilyNotifier<IMapController?, MapToLoad> {
  @override
  IMapController? build(MapToLoad args) {
    return null;
  }

  void update(IMapController controller) {
    state = controller;
  }
}

And its being used in another provider


final mapControllerProvider = NotifierProvider.autoDispose
    .family<MapControllerNotifier, IMapController?, StoresTypeToFetch>(
  () {
    return MapControllerNotifier();
  },
);

class MapControllerNotifier
    extends AutoDisposeFamilyNotifier<IMapController?, StoresTypeToFetch> {
  @override
  IMapController? build(StoresTypeToFetch args) {
    state = ref.watch(getMapControllerProvider(MapToLoad.stores));

    ref.listen(
      selectedLocationCameraPositionProvider(args),
      fireImmediately: true,
      (previous, next) {
        if (previous != next) {
          state?.animateCamera(next);
        }
      },
    );

    return state;
  }
}

Is there any thing wrong with this approach which is causing this error? Can i get help on this ?

Expected behavior This bug shouldn't appear.

rrousselGit commented 4 months ago

The problem is in your widget, but you haven't shared your widget, so I cannot know.

SdxCoder commented 4 months ago

@rrousselGit i here is the attached widget can you have a look kindly.

class StoresMapComponent extends ConsumerWidget {
  final StoresTypeToFetch arg;
  final bool enableMyLocationButton;
  const StoresMapComponent({
    super.key,
    required this.arg,
    this.enableMyLocationButton = true,
  });

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.listen(mapControllerProvider(arg), (previous, next) {});
    final cameraPosition =
        ref.watch(selectedLocationCameraPositionProvider(arg));
    final markers = ref.watch(addMarkersProvider(arg));
    final isPlayServicesAvailable =
        ref.watch(googleServicesProvider).valueOrNull;

    return Stack(
      children: [
        if (isPlayServicesAvailable == null)
          const Offstage()
        else if (isPlayServicesAvailable)
          GoogleMap(
            onMapCreated: (controller) {
              ref
                  .read(
                    getMapControllerProvider(MapToLoad.stores).notifier,
                  )
                  .update(IMapController.googleaMaps(controller));
            },
            zoomControlsEnabled: false,
            myLocationButtonEnabled: false,
            buildingsEnabled: false,
            myLocationEnabled: true,
            markers: markers.values.map((e) => e.toGoogleMapMarker()).toSet(),
            initialCameraPosition: cameraPosition.toGoogleMapCameraPosition(),
          )
        else
          HuaweiMap(
            onMapCreated: (controller) {
              ref
                  .read(
                    getMapControllerProvider(MapToLoad.stores).notifier,
                  )
                  .update(IMapController.huaweiMaps(controller));
            },
            zoomControlsEnabled: false,
            myLocationButtonEnabled: false,
            buildingsEnabled: false,
            myLocationEnabled: true,
            markers: markers.values.map((e) => e.toHuaweiMapMarker()).toSet(),
            initialCameraPosition: cameraPosition.toHuaweiMapCameraPosition(),
          ),
        if (enableMyLocationButton)
          Align(
            alignment: Alignment.bottomRight,
            child: Container(
              margin: const EdgeInsets.all(Dimens.padding16),
              decoration: const BoxDecoration(
                shape: BoxShape.circle,
                boxShadow: [
                  BoxShadow(
                    color: Color(0x14000000),
                    blurRadius: 10,
                  ),
                ],
              ),
              child: FloatingActionButtonWidget(
                key: UniqueKey(),
                svg: AssetNames.loactionCenterIcon,
                color: AppTheme.white,
                svgColor: AppTheme.darkGray,
                elevation: 0,
                onPressed: () {
                  ref
                      .read(shouldSetCurrentLocationProvider.notifier)
                      .update(true);
                  ref
                      .read(currentLocationProvider.notifier)
                      .getCurrentLocation();
                },
              ),
            ),
          ),
      ],
    );
  }
}
SdxCoder commented 4 months ago

@rrousselGit Can you kindly have a look at the widget and share ur thoughts kindly.

Peetee06 commented 1 week ago

@SdxCoder from what I understand it seems like the StoresMapComponent widget was already disposed when the onMapCreated callback is called.

Can you share the parent widget of StoresMapComponent? The issue might stem from there. You can check if the StoresMapComponent is built initially but disposed again before the onMapCreated callback is called.