last property
Exposes a Future
which resolves with the last value or error emitted.
This can be useful for scenarios where we want to read the current value exposed by a StreamProvider, but also handle the scenario were no value were emitted yet:
final configsProvider = StreamProvider<Configuration>((ref) async* {
// somehow emit a Configuration instance
});
final productsProvider = FutureProvider<Products>((ref) async {
// If a "Configuration" was emitted, retrieve it.
// Otherwise, wait for a Configuration to be emitted.
final configs = await ref.watch(configsProvider.last);
final response = await httpClient.get('${configs.host}/products');
return Products.fromJson(response.data);
});
Why not use StreamProvider.stream.first
instead?
If you are familiar with streams, you may wonder why not use Stream.first instead:
final configsProvider = StreamProvider<Configuration>((ref) {...});
final productsProvider = FutureProvider<Products>((ref) async {
final configs = await ref.watch(configsProvider.stream).first;
...
}
The problem with this code is, unless your StreamProvider is creating
a BehaviorSubject
from package:rxdart
, you have a bug.
By default, if we call Stream.first after the first value was emitted,
then the Future
created will not obtain that first value but instead
wait for a second one – which may never come.
The following code demonstrate this problem:
final exampleProvider = StreamProvider<int>((ref) async* {
yield 42;
});
final anotherProvider = FutureProvider<void>((ref) async {
print(await ref.watch(exampleProvider.stream).first);
// The code will block here and wait forever
print(await ref.watch(exampleProvider.stream).first);
print('this code is never reached');
});
void main() async {
final container = ProviderContainer();
await container.read(anotherProvider.future);
// never reached
print('done');
}
This snippet will print 42
once, then wait forever.
On the other hand, if we used StreamProvider.last, our code would correctly execute:
final exampleProvider = StreamProvider<int>((ref) async* {
yield 42;
});
final anotherProvider = FutureProvider<void>((ref) async {
print(await ref.watch(exampleProvider.last));
print(await ref.watch(exampleProvider.last));
print('completed');
});
void main() async {
final container = ProviderContainer();
await container.read(anotherProvider.future);
print('done');
}
with this modification, our code will now print:
42
42
completed
done
which is the expected behavior.
Implementation
@override
AutoDisposeProviderBase<Object?, Future<T>> get last {
return _last ??= Provider.autoDispose(
(ref) => _readLast(ref as ProviderElement, this),
name: name == null ? null : '$name.last',
);
}