首页 > 解决方案 > AutoDisposeStreamProvider is not being disposed at loggin out

问题描述

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?

标签: firebaseflutterfirebase-authenticationriverpod

解决方案


文档中,该示例使用Stream基于 aStreamController

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

在您的情况下,您的方法返回一个Stream. 这改变了游戏规则。只需返回Stream.

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

推荐阅读