future property Null safety

AlwaysAliveProviderBase<Future<State>> future
late, final

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.future, our code would correctly execute:

final exampleProvider = StreamProvider<int>((ref) async* {
  yield 42;
});

final anotherProvider = FutureProvider<void>((ref) async {
  print(await ref.watch(exampleProvider.future));
  print(await ref.watch(exampleProvider.future));
  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

late final AlwaysAliveProviderBase<Future<State>> future =
    AsyncValueAsFutureProvider(this, from: from, argument: argument);