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<FirebaseUser>#95f11: AsyncValue<FirebaseUser>.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<User>
,而是创建Stream<Stream<User>>
作为 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