AutoDisposeStreamProvider is not being disposed at loggin out

428

Solution 1

In the documentation, the example is using a Stream based on a StreamController

final messageProvider = StreamProvider.autoDispose<String>((ref) async* {
  // Open the connection
  final channel = IOWebSocketChannel.connect('ws://echo.websocket.org');

  // Close the connection when the stream is destroyed
  ref.onDispose(() => channel.sink.close());

  // Parse the value received and emit a Message instance
  await for (final value in channel.stream) {
    yield value.toString();
  }
});

In your case, your method is returning a Stream. This changes the game rules. Just return the Stream.

final AutoDisposeStreamProvider<List<fc_types.Room>> roomsListProvider =
    StreamProvider.autoDispose<List<fc_types.Room>>(
  (_) => FirebaseChatCore.instance.rooms(),
  name: "List Rooms Provider",
);

Solution 2

Edit: As you cannot cancel a Stream directly, you could just forward the FirebaseCore.instance.rooms() and let the provider do the cleanup:

final AutoDisposeStreamProvider<List<fc_types.Room>> roomsListProvider =
    StreamProvider.autoDispose<List<fc_types.Room>>(
  (_) => FirebaseChatCore.instance.rooms(),
  name: "List Rooms Provider",
);

Previous Answer:

autoDispose only closes the provided Stream itself (the one you create by using async*), but you will still need too close the Firebase stream yourself.

You can use onDispose() as shown in the Riverpod documentation

  ref.onDispose(() => rooms.close());
Share:
428
SalahAdDin
Author by

SalahAdDin

Soy Ingeniero de Sistemas y Computación Egresado de la Universidad de Colombia, emprendedor, aprendo muy fácilmente y me gusta hacerlo autónomamente. Me encanta trabajar en Sistemas Operativos Linux, muy buenos para trabajar en programación y afines, y jugar en Windows. Actualmente me desempeño autónomamente en desarrollo de aplicaciones web. Me gusta ayudar y aprender. I'm a Computer and Systems Engineer graduated from the National University of Colombia, entrepreneur, I learn easily and I like to do it autonomously. I love Working on Linux Operative Systems, they are very good for working in programming and allied, and i like playing in Windows. Currently in involved in autonomous web applications development. I like to help and to learn.

Updated on December 30, 2022

Comments

  • SalahAdDin
    SalahAdDin over 1 year

    Currently, we are using Firebase to implement a simple chat on our application.

    We handle the application's launch and authentication with Riverpod.

    Launching goes like as follows:

    @override
      Widget build(BuildContext context) {
        LocalNotificationService()
            .handleApplicationWasLaunchedFromNotification(_onSelectNotification);
        LocalNotificationService().setOnSelectNotification(_onSelectNotification);
        _configureDidReceiveLocalNotification();
    
        // final navigator = useProvider(navigatorProvider);
        final Settings? appSettings = useProvider(settingsNotifierProvider);
        final bool darkTheme = appSettings?.darkTheme ?? false;
        final LauncherState launcherState = useProvider(launcherProvider);
    
        SystemChrome.setEnabledSystemUIOverlays(
          <SystemUiOverlay>[SystemUiOverlay.bottom],
        );
    
        return MaterialApp(
          title: 'Thesis Cancer',
          theme: darkTheme ? ThemeData.dark() : ThemeData.light(),
          navigatorKey: _navigatorKey,
          debugShowCheckedModeBanner: false,
          home: Builder(
            builder: (BuildContext context) => launcherState.when(
              loading: () => SplashScreen(),
              needsProfile: () => LoginScreen(),
              profileLoaded: () => MainScreen(),
            ),
          ),
        );
      }
    

    Currently, we just enable logging out from main screen and rooms screen as follows:

    ListTile(
                        leading: const Icon(Icons.exit_to_app),
                        title: const Text('Çıkış yap'),
                        onTap: () =>
                            context.read(launcherProvider.notifier).signOut(),
                      ),
    

    Where signOut does:

    Future<void> signOut() async {
        tokenController.state = '';
        userController.state = User.empty;
        await dataStore.removeUserProfile();
        _auth.signOut();
        state = const LauncherState.needsProfile();
      }
    

    The problem is, every time we goes to the RoomsPage and we do logout from it or from the main page (coming back from rooms), we get the same problem with firebase: enter image description here The caller does not have permission to execute the specified operation.. Of course, signout closes the Firebase, thence Firebase throws this error; but, it is supposed after coming out from the RoomsScreen (it happens even when go back to the main screen), this widget is disposed therefore the connection should be closed, disposed, but it seems it is still on memory.

    The RoomPage screen is as follows:

    class RoomsPage extends HookWidget {
      @override
      Widget build(BuildContext context) {
        final AsyncValue<List<fc_types.Room>> rooms =
            useProvider(roomsListProvider);
        return Scaffold(
          appBar: Header(
            pageTitle: "Uzmanlar",
            leading: const BackButton(),
          ),
          endDrawer: ConstrainedBox(
            constraints: const BoxConstraints(maxWidth: 275),
            child: SideMenu(),
          ),
          body: rooms.when(
            data: (List<fc_types.Room> rooms) {
              if (rooms.isEmpty) {
                return Container(
                  alignment: Alignment.center,
                  margin: const EdgeInsets.only(
                    bottom: 200,
                  ),
                  child: const Text('No rooms'),
                );
              }
    
              return ListView.builder(
                itemCount: rooms.length,
                itemBuilder: (
                  BuildContext context,
                  int index,
                ) {
                  final fc_types.Room room = rooms[index];
    
                  return GestureDetector(
                    onTap: () => pushToPage(
                      context,
                      ChatPage(
                        room: room,
                      ),
                    ),
                    child: Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 16,
                        vertical: 8,
                      ),
                      child: Row(
                        children: <Widget>[
                          Container(
                            height: 40,
                            margin: const EdgeInsets.only(
                              right: 16,
                            ),
                            width: 40,
                            child: ClipRRect(
                              borderRadius: const BorderRadius.all(
                                Radius.circular(20),
                              ),
                              child: Image.network(room.imageUrl ?? ''),
                            ),
                          ),
                          Text(room.name ?? 'Room'),
                        ],
                      ),
                    ),
                  );
                },
              );
            },
            loading: () => const Center(
              child: CircularProgressIndicator(),
            ),
            error: (Object error, StackTrace? stack) => ErrorScreen(
              message: error.toString(),
              actionLabel: 'Home',
              onPressed: () => Navigator.of(context).pop(),
            ),
          ),
        );
      }
    }
    

    And the provider is simple:

    final AutoDisposeStreamProvider<List<fc_types.Room>> roomsListProvider =
        StreamProvider.autoDispose<List<fc_types.Room>>(
      (_) async* {
        final Stream<List<fc_types.Room>> rooms = FirebaseChatCore.instance.rooms();
        await for (final List<fc_types.Room> value in rooms) {
          yield value;
        }
      },
      name: "List Rooms Provider",
    );
    

    I suppose the AutoDispose constructor makes this provider auto disposed when the widget is removed, so, it should close the connection with Firebase (as de documentation says).

    WHat's the problem here?

    What am i missing?

    Should i open an issue about this?

  • SalahAdDin
    SalahAdDin almost 3 years
    The method 'close' isn't defined for the type 'Stream'.
  • SalahAdDin
    SalahAdDin almost 3 years
    It is suposed to be what i did, also i followed the documentation: pub.dev/documentation/riverpod/latest/riverpod/…
  • SalahAdDin
    SalahAdDin almost 3 years
    And also, the types there are wrong: final StreamProvider<List<fc_types.Room>> roomsProvider = StreamProvider<List<fc_types.Room>>( (_) => FirebaseChatCore.instance.rooms(), );