Repository: flutter
Issue: Convert a Future<T> to a series of AsyncSnapshot's outside of the widget tree
Labels: c: new feature,framework,c: proposal,P3,team-framework,triaged-framework

### Is there an existing issue for this?

- [X] I have searched the [existing issues](https://github.com/flutter/flutter/issues)
- [X] I have read the [guide to filing a bug](https://flutter.dev/docs/resources/bug-reports)

### Use case

(I'll use `Future` for most of my examples throughout this feature request, though all this applies roughly the same to `Stream`)

A super common pattern (ex: `AsyncNotifierProvider` from Riverpod) is to fetch/initialize a controller asynchronously, store it in your SM, and then subscribe to it from a bunch of different places in your widget tree. Right now this is a hassle in vanilla Flutter because `Future` does not expose synchronous state so each widget subtree that needs to bind to the async work has to use a `FutureBuilder` which is non ideal because:
1. It's highly verbose
2. It's tied to the widget tree so it doesn't let me access the current state of the async work purely from within my state management system
3. It causes a first frame "flash" of the initial data/no data state when a `FutureBuilder` is initialized with a Future that has already completed. See details: https://github.com/flutter/flutter/blob/5b47fef613e2d74ae44e1e22c2a3495294db180e/packages/flutter/lib/src/widgets/async.dart#L467

I can write my own custom logic within my state management to "bind" my controller to async work and expose the current state of the async work as an `AsyncSnapshot` (or equivalent) but I've now just reimplemented `FutureBuilder`'s internal state machine. It's tedious to do this and really easy to introduce bugs.
Here's an example of reimplementing the snapshot state machine for streams from a controller:
https://github.com/alesalv/flutter_arch_comp/blob/43c8d89d4ebcfd314269d8e8af11745d5203a39b/lib/src/pokemon/controllers/pokemon_controller.dart#L15
Note the `_onData` etc methods that are more or less just one-off reimplementations of the `AsyncSnapshot` state machine in `StreamBuilder`.

RXDart solves this problem for Streams and Riverpod/other third party SM systems have their own custom controller class special cases to handle async initialization. The biggest problems here are:
1. Requiring third party solutions to core dev needs produces a bad dev experience, especially for developers new to Flutter (see the fragmented choice paralysis nightmare that is trying to learn React). Flutter state management has already fallen into this bad state on a smaller scale.
2. None of the third party solutions implement a common interface making them incompatible with each other

### Proposal

Expose a pure-dart listenable class that "binds" to a `Future` (and an equivalent for `Stream`) and exposes it's current state as an `AsyncSnapshot`.

Internally, the snapshot state machine logic in `Future/StreamBuilder` could be replaced with a listenable builder on top of this new class. Or, ideally, `Future/StreamBuilder` could be refactored to take in this class instead of `Future/Stream` respectively. This has the advantage of encouraging the developer to bind to the async work in their state management and thus avoid the "single frame flash" issue that comes from re-initializing a builder on async work that has already received data.

To give an idea of what this could look like, here's my proof of concept (emphasis on PoC, no promises that this is the ideal implementation and/or free of bugs):

```dart
class AsyncValueListenable<T> extends ValueListenable<AsyncSnapshot<T>>
    with ChangeNotifier {
  AsyncValueListenable(this._future)
      : _snap = const AsyncSnapshot.waiting(),
        _stream = null {
    unawaited(_subscribeToFuture());
  }

  AsyncValueListenable.fromStream(Stream<T> stream)
      : _snap = const AsyncSnapshot.waiting(),
        _future = null,
        _stream = stream {
    _subscribeToStream();
  }

  final Future<T>? _future;

  final Stream<T>? _stream;

  AsyncSnapshot<T> _snap;

  @override
  AsyncSnapshot<T> get value => _snap;

  T get requireValue => _snap.requireData;

  void _onStreamData(T event) {
    _setSnap(AsyncSnapshot.withData(ConnectionState.active, event));
  }

  Future<void> _subscribeToFuture() async {

    await _future!.then((data) {
      _setSnap(AsyncSnapshot.withData(ConnectionState.done, data));
    }).onError((error, stackTrace) {
      _setSnap(AsyncSnapshot.withError(ConnectionState.done, error));
    });
  }

  void _subscribeToStream() {
    _setSnap(_snap.inState(ConnectionState.waiting));
    _stream!.listen(_onStreamData, onError: (error, stackTrace) {
      _setSnap(AsyncSnapshot.withError(ConnectionState.done, error));
    }).onDone(() {
      _setSnap(_snap.inState(ConnectionState.done));
    });
  }

  void _setSnap(AsyncSnapshot<T> newSnap) {
    _snap = newSnap;
    notifyListeners();
  }
}

extension FutureNotifier<T> on Future<T> {
  AsyncValueListenable<T> asAsyncListenable() {
    return AsyncValueListenable(this);
  }
}
```

Now I can hold an `AsyncValueListenable` in my state and build off of it anywhere in my widget tree that has access to that state. Also, other parts of my state management system (eg callbacks outside of the widget tree) can synchronously access the current state of the async work via the async snapshot interface:
```dart
  // Somewhere in state-eg `initState` or a `provider`...
  final AsyncValueListenable myAsyncController = asyncGetController().asAsyncListenable();
  ...
  // In build.
  return ValueListenableBuilder(
    valueListenable: myState.myAsyncController,
    // Error, waiting, etc handling goes here.
    builder: (context, snap, child) { ... },
  );
  ...
  // In some callback where I know there's valid data.
  void myCallback() {
    doStuff(myState.myAsyncController.requirevalue);
  }
```