AutoDisposeStreamProvider 未在注销时处理

问题描述

目前,我们正在使用 Firebase 在我们的应用程序上实现一个简单的聊天。

我们使用 Riverpod 处理应用程序的启动和身份验证。

启动过程如下:

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

目前,我们只允许从主屏幕和房间屏幕注销,如下所示:

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

signOut 在哪里:

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

问题是,每次我们转到 RoomsPage 并从它或从主页(从房间回来)注销时,我们都会遇到与 firebase 相同的问题:

enter image description here

The caller does not have permission to execute the specified operation.。 当然,signout 关闭Firebase,因此 Firebase 会抛出这个错误;但是,应该是从 RoomsScreen 出来后(即使回到主屏幕也会发生),这个小部件被处理,因此应该关闭连接,处理,但似乎它仍然在内存中。

RoomPage 屏幕如下:

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

提供者很简单:

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",);

我想 Autodispose 构造函数会在移除小部件时自动处理此提供程序,因此,它应该关闭Firebase 的连接(如文档所述)。

这里有什么问题?

我错过了什么?

我应该就此开一个问题吗?

解决方法

编辑: 由于您不能直接取消 Stream,您可以只转发 FirebaseCore.instance.rooms() 并让提供程序进行清理:

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

上一个答案:

autoDispose 仅关闭提供的流本身(您使用 async* 创建的流),但您仍然需要自己关闭 Firebase 流。

您可以使用 onDispose(),如Riverpod documentation

  ref.onDispose(() => rooms.close());
,

documentation 中,示例使用基于 StreamStreamController

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