【问题标题】:AutoDisposeStreamProvider is not being disposed at loggin outAutoDispose StreamProvider 未在注销时处理
【发布时间】:2021-07-02 13:13:32
【问题描述】:

目前,我们正在使用 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 相同的问题: 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 的连接(如文档所述)。

这里有什么问题?

我错过了什么?

我应该就此提出问题吗?

【问题讨论】:

    标签: firebase flutter firebase-authentication riverpod


    【解决方案1】:

    documentation 中,该示例使用基于StreamControllerStream

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

    【讨论】:

      【解决方案2】:

      编辑: 由于您无法直接取消 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());
      

      【讨论】:

      • 没有为“Stream”类型定义“close”方法。
      • 这应该是我所做的,我也遵循了文档:pub.dev/documentation/riverpod/latest/riverpod/…
      • 还有,类型有误:final StreamProvider&lt;List&lt;fc_types.Room&gt;&gt; roomsProvider = StreamProvider&lt;List&lt;fc_types.Room&gt;&gt;( (_) =&gt; FirebaseChatCore.instance.rooms(), );
      猜你喜欢
      • 2017-02-04
      • 1970-01-01
      • 2017-07-18
      • 2015-06-14
      • 1970-01-01
      • 1970-01-01
      • 2015-11-24
      • 2014-01-10
      • 2019-01-31
      相关资源
      最近更新 更多