Riverpod:是不是有在另一个 StreamProvider 中读取 StreamProvider 的正确方法?

Posted

技术标签:

【中文标题】Riverpod:是不是有在另一个 StreamProvider 中读取 StreamProvider 的正确方法?【英文标题】:Riverpod: Is there a correct way to read a StreamProvider within another StreamProvider?Riverpod:是否有在另一个 StreamProvider 中读取 StreamProvider 的正确方法? 【发布时间】:2020-11-04 02:31:05 【问题描述】:

我一直在尝试使用从我的身份验证Provider 获得的 uid 创建流到 Firestore 文档:

class AuthService 
  ...

  static final provider = StreamProvider.autoDispose((ref) => FirebaseAuth.instance.onAuthStateChanged);
  ...

但是,我很难根据来自身份验证 Provider 的值实际创建一个 StreamProvider

class User 
  ...
  static final provider = StreamProvider((ref) 
    final stream = ref.read(AuthService.provider);
    // Returns AsyncValue<Stream<User>> instead of desired AsyncValue<User>
    return stream.map((auth) => Service.user.stream(auth.uid));
  );
  ...

我还尝试使用 Computed 返回 uid 或流本身,但您无法从 Provider 读取 Computed(回想起来这很有意义)。

This question 与此主题最相关,但它处理的是 Provider,而不是 Riverpod。

附:可以创建 Riverpod 标签吗?

编辑:

答案并不完全正确。 await for loop 只会触发一次,而侦听器会捕获所有事件。

static final provider = StreamProvider((ref) async* 
  final stream = ref.read(AuthService.provider);
  print('userProvider init');

  stream.listen((auth) 
    print('LISTENED: $auth?.uid');
  );

  await for (final auth in stream) 
    print('uid: $auth?.uid');
    yield* Service.user.stream(auth?.uid);
  
);

此代码在登录时产生以下内容:

userProvider init
LISTENED: <redacted UID>
uid: <redacted UID>

然后退出:

LISTENED: null

我也希望看到uid: null,这将更新流,但在任何更多的身份验证事件中,只有侦听器被触发,await for loop 不会捕获任何事件。

有趣的是,使用颤振检查器,身份验证提供者发出的值也永远不会改变:

AutoDisposeStreamProvider&lt;FirebaseUser&gt;#95f11: AsyncValue&lt;FirebaseUser&gt;.data(value: FirebaseUser(Instance of 'PlatformUser'))

通过登录/注销事件持续存在,这可以解释这种行为,但我不确定如何修复它。

有什么想法吗?我已经被这个问题困扰了一段时间,无法纠正这个问题。

【问题讨论】:

我已将语法更新为 0.6.0-dev @RémiRousselet 太棒了,谢谢!我一直在关注您在 Riverpod 上的工作,很高兴看到我的问题将得到解决。一旦 0.6.0 发布,我将接受您的回答,我可以测试更改。 Riverpod 文档说不要在 Provider 构造函数中使用“ref.read(”...但我看到 Remi 已经审查了这个问题并且没有反对你的代码。我有一个github 上的未决问题寻求澄清这一点。 @Dewey 这个问题是在更改为使用 ref.watch 之前出现的。所以,你是对的。 【参考方案1】:

问题是,您的提供者没有创建Stream&lt;User&gt;,而是创建Stream&lt;Stream&lt;User&gt;&gt;

作为 0.6.0-dev 的一部分,您可以使用 ref.watch 轻松组合流:

class User 
  ...
  static final provider = StreamProvider((ref) 
    final auth = ref.watch(AuthService.provider);
    return Service.user.stream(auth.uid);
  );
  ...

【讨论】:

这是一些非常好的语法,我有一些阅读要做。谢谢!唯一的问题是我似乎没有从流中接收任何事件,因此永远不会提供流。我在您发布的 await for 循环中放置了一条打印语句以确保。但是,事件从以下位置触发:final auth = useProvider(AuthService.provider); return auth.when(... 正在创建提供程序,但由于似乎从未通过任何身份验证事件,因此永远不会创建流​​。有什么想法吗? 由于某种原因,这似乎只收听第一个身份验证流事件。我可以放置一个外部监听器并查看事件,但用户提供者只收到第一个。无法弄清楚这里发生了什么...... 注销后没有收到更改,是的。【参考方案2】:

我想先说我只使用 Dart/Flutter 几个月,所以请随时更正我所说的任何内容,我会更新答案。

经过反复试验和多次重新审查文档,我解决了这个问题。我想我做了一个糟糕的假设,即当他们依赖的 Provider 发生变化时,Providers 会更新。

我发现一旦 StreamProvider 返回(或产生),直到它被释放,它总是返回它最初所做的相同值,而不管依赖项更改或来自 Stream 的事件。这是 Computed 有用的地方,但当您希望从您的提供者返回 AsyncValue(StreamProvider 的行为方式)时,它并不能很好地工作。

此外,Flutter Inspector 未正确更新 ProviderScope 小部件导致了额外的混乱。我必须四处点击并刷新几次才能看到对提供者或其状态的更新。总之……

class AuthService 
  ...
  static final provider = StreamProvider.autoDispose((ref) 
    final sub = FirebaseAuth.instance.onAuthStateChanged.listen((auth) => ref.read(uidProvider).state = auth?.uid);
    ref.onDispose(() => sub.cancel());
    return FirebaseAuth.instance.onAuthStateChanged;
  );

  static final uidProvider = StateProvider<String>((_) => null);
  ...

class User 
  ...
  static final provider = StreamProvider.autoDispose((ref) 
    final uid = ref.read(AuthService.uidProvider)?.state;
    return Service.user.stream(uid);
  );
  ...

此解决方案有效,因为当用户退出您的应用时,您的 UI 不再依赖于提供程序(允许正确处理它们)。

【讨论】:

回复:“一旦 StreamProvider 返回(或产生),直到它被处理,它将始终返回与最初相同的值”我认为这是不正确的......我敢打赌如果您将打印/日志放在该 Stream Provider 的构造函数中,您将看到它一遍又一遍地重建。这就是为什么你不断获得初始值。

以上是关于Riverpod:是不是有在另一个 StreamProvider 中读取 StreamProvider 的正确方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何覆盖 Riverpod StateNotifier 的状态以进行测试

Riverpod,在 BuildContext 和 Provider 之外读取状态

Riverpod - 如何在消费者中包装 PreferredSizeWidget

Flutter 用 provider 和 riverpod 制作表单

Riverpod 条件提供程序状态更新

Flutter状态管理之Riverpod