riverpod
riverpod copied to clipboard
Mention in a migration guide that `await ref.read(provider.future)` can cause an infinite await. => use listen
The solution is to instead do:
final sub = ref.listen(provider.future, (p, n){});
try {
await sub.read();
} finally {
sub.close();
}
This is inconvenient, but generally speaking mutations should cover most cases where we currently use ref.read.
In what cases does it cause the infinite await?
It's 3.0 stuff
Is it possible for the infinite await issue to occur in 2.x versions as well?
I’m not completely sure if this is the root cause in my case, but I’ve noticed that when I have a Provider B that depends on Provider A (where Provider A runs invalidateSelf on a timer), Provider B sometimes gets stuck in a perpetual loading state until Provider A refreshes again. It seems to happen when the invalidation of Provider A coincides with the rebuilding of Provider B.
Could this be related to the issue you described?
No this is unrelated
Is this due to the retry feature #3623?
Is this only happening in version 3? Because I can reproduce this in version 2.6.1. This happens when the provider's future that's being awaited has its dependency changed while computing.
I can fix this using the solution above (using ref.listenManual)
dartpad: https://dartpad.dev/?id=486e613330292488eef8f36e67168ee9
Code
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MaterialApp(home: Home())));
}
final dependencyProvider = FutureProvider(
(ref) async {
await Future.delayed(const Duration(milliseconds: 200));
return Random().nextInt(100);
},
);
final dataProvider = FutureProvider(
(ref) async {
ref.onDispose(() => print('dispose'));
print('computing dependency...');
final dependency = await ref.watch(dependencyProvider.future);
print('computing data from dependency ($dependency)...');
await Future.delayed(const Duration(seconds: 1));
return dependency + 1;
},
);
class Home extends ConsumerStatefulWidget {
const Home({super.key});
@override
ConsumerState<Home> createState() => _MyHomePageState();
}
class _MyHomePageState extends ConsumerState<Home> {
@override
void initState() {
super.initState();
() async {
// Wait for the dependency to finish computing
await ref.read(dependencyProvider.future);
() async {
await Future.delayed(const Duration(milliseconds: 100));
ref.invalidate(dependencyProvider);
}();
// Use listenManual to fix
// final sub = ref.listenManual(
// dataProvider.future,
// (previous, next) {},
// );
//
// try {
// final data = await sub.read();
// print(data);
// } finally {
// sub.close();
// }
final data = await ref.read(dataProvider.future); // Infinite await
print(data);
}();
}
@override
Widget build(BuildContext context) {
ref.watch(dependencyProvider);
return const Placeholder();
}
}
Our codebase utilizes the await container.read(provider.future) pattern in over 50 places, primarily to create/get objects during initialization and within background task handlers.
So I use the following extension with Riverpod 3.0 to keep it a single call:
extension ProviderContainerReadAsyncExt on ProviderContainer {
Future<ValueT> readAsync<ValueT>(
AsyncProviderListenable<ValueT> provider,
) async {
final ProviderSubscription<Future<ValueT>> sub = listen(
provider.future,
(_, _) {},
);
try {
return await sub.read();
} finally {
sub.close();
}
}
}
Hi, as per above, I would also like to know when is this actually a concern.
Our code base is calling await ref.read(myProvider.future) in several places and they have not gotten stuck yet.
The recommendation above was to use ref.listen(), but this returns void instead of a subscription, perhaps it should be ref.listenManual()?
Ref.listen or WidgetRef.listenManual
Ok, thanks for confirming. Will riverpod_lint be updated to provide a warning in this case?
Edit: using below based on karelklic's answer, no issues found in some quick testing, simply replaced ref.read() with ref.readAsync() due to same interface, though in our case ref.read(provider.future) wasn't getting stuck anyway but better to follow the recommended approach.
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/misc.dart';
extension RefExtensions on Ref {
// docs: https://github.com/rrousselGit/riverpod/issues/3745
StateT readAsync<StateT>(
ProviderListenable<StateT> listenable,
) {
final sub = listen(listenable, (_, _) {});
try {
return sub.read();
} finally {
sub.close();
}
}
}
extension WidgetRefExtensions on WidgetRef {
// docs: https://github.com/rrousselGit/riverpod/issues/3745
StateT readAsync<StateT>(
ProviderListenable<StateT> listenable,
) {
final sub = listenManual(listenable, (_, _) {});
try {
return sub.read();
} finally {
sub.close();
}
}
}
Perhaps it makes sense to update ref.read() to do this internally? Then users won't be affected and can avoid updating riverpod_lint.
I suspect that won't work - you need to await within the try or else you're back to normal read()
A crucial issue here is that Ref.read sometimes seems to work fine with FutureProvider and StreamProvider. When it doesn't work, it creates a difficult-to-diagnose bug: An awaited future somewhere deep inside your provider never finishes, and nothing in the logs or the debugger tells you why. Or one of your streams never yields a value, even when it is guaranteed to do so.
This makes Ref.read dangerous to use. It should throw an exception when called with an async provider.
The main issue is that this:
await ref.read(provider.notifier).doSomethingAsync();
is equally unsafe.
I'm heavily considering deprecating ref.read altogether, in favour of the approach taken by mutations
@rrousselGit am I correct by using it the following way?:
Future<void> onPushNotificationsChanged({required bool enabled}) async {
final scheduledSubscription = ref.container.listen(hasScheduledNotificationsProvider.future, (previous, next) {});
try {
final hasScheduledNotifications = await scheduledSubscription.read();
} finally {
scheduledSubscription.close();
}
}
or should it be:
final sub = ref.listenManual(hasScheduledNotificationsProvider.future, (previous, next) {});
I really liked the option to read a Stream/Future provider and basically skip the first loading state. I mean, this is more or less what .future was doing in v2 and we were using it a lot. Now we are migrating to a safer approach after reading this issue, but it would be amazing to have this back somehow.
This would be useful especially in situations where we need to use a provider in a background thread or in some place that is not UI so we don't want updates, but we just want the first real value the provider emits.
Has anyone got a version of readAsync for refresh?