Repository: flutter
Issue: StreamBuilder not setting the correct value for connectionState for some Streams
Labels: framework,a: quality,P2,team-framework,triaged-framework

In order to simplify reproduction of this issue, I created a sample App [here](https://github.com/feinstein/streambuilder_connectionstate). 

I found this bug while addressing an issue reported in [my library](https://github.com/feinstein/firebase_user_stream), so I adapted the user's example here. I know that `StreamBuilder` shouldn't be used like this for this particular use case, but still the bug shouldn't exist either if the user chooses to use it.

The App has 2 bottom navigation buttons. On the first page click to login, this will login a test user, then go to the second page, look at the console outputs, then go back into the first page, then again go to the second page, keep alternating between the pages and looking at the logs, they should look like this:

```
I/flutter (28560): created
I/flutter (28560): init
I/flutter (28560): ConnectionState.waiting
I/flutter (28560): snapshot has data: false
I/flutter (28560): user name: null
I/flutter (28560): ConnectionState.active
I/flutter (28560): snapshot has data: true
I/flutter (28560): user name: Test user
I/flutter (28560): disposed
I/flutter (28560): init
I/flutter (28560): ConnectionState.waiting
I/flutter (28560): snapshot has data: true
I/flutter (28560): user name: Test user
I/flutter (28560): disposed
I/flutter (28560): init
I/flutter (28560): ConnectionState.waiting
I/flutter (28560): snapshot has data: true
I/flutter (28560): user name: Test user
I/flutter (28560): disposed
```

As you can see in the first time there's an emission by the stream, things works just fine, `snapshot.connectionState` goes from `ConnectionState.waiting` to `ConnectionState.active` and we get a result. But then afterwards `snapshot.connectionState` stays at `ConnectionState.waiting` even though we get a value from the stream, which goes against the `ConnectionState` docs:

```dart
  /// Not currently connected to any asynchronous computation.
  ///
  /// For example, a [FutureBuilder] whose [FutureBuilder.future] is null.
  none,

  /// Connected to an asynchronous computation and awaiting interaction.
  waiting,

  /// Connected to an active asynchronous computation.
  ///
  /// For example, a [Stream] that has returned at least one value, but is not
  /// yet done.
  active,

  /// Connected to a terminated asynchronous computation.
  done,
```

A `StreamBuilder` on `ConnectionState.waiting` shouldn't have any data on `snapshot.data` (`awaiting interaction`), data should only be available after it goes to `ConnectionState.done` (`a [Stream] that has returned at least one value`).

I think I found the origin of the bug, looking at `_StreamBuilderBaseState` we can find this:

```dart
  void _subscribe() {
    if (widget.stream != null) {
      _subscription = widget.stream.listen((T data) {
        setState(() {
          _summary = widget.afterData(_summary, data);
        });
      }, onError: (Object error) {
        setState(() {
          _summary = widget.afterError(_summary, error);
        });
      }, onDone: () {
        setState(() {
          _summary = widget.afterDone(_summary);
        });
      });
      _summary = widget.afterConnected(_summary);
    }
  }
```

Some streams, I suspect streams with a cached value (if you know more about it, please do let me know), will go immediately into the `listen` callback activating `_summary = widget.afterData(_summary, data);`, which should trigger `ConnectionState.active`, but since this was called at the same time that the `_subscribe()` function is running, `_summary = widget.afterConnected(_summary);` will be called just afterwards, overriding the value of `_summary` to `ConnectionState.waiting`.

One solution is to check if `_summary` can be set to `ConnectionState.waiting` before setting it, which isn't simple as this is a base class calling generic functions. So another soluting might be using a `try-catch` block, something like this:

```dart
  void _subscribe() {
    if (widget.stream != null) {      
      try {
        S _oldSummary = _summary;
        _summary = widget.afterConnected(_summary);

        _subscription = widget.stream.listen((T data) {
          setState(() {
            _summary = widget.afterData(_summary, data);
          });
        }, onError: (Object error) {
          setState(() {
            _summary = widget.afterError(_summary, error);
          });
        }, onDone: () {
          setState(() {
            _summary = widget.afterDone(_summary);
          });
        });
      } catch(error) {
         _summary = _oldSummary;
         rethrow;
      }      
    }
  }
```

I didn't test it, but I believe this will set the `StreamBuilder` to `ConnectionState.waiting` at first and if some callback is ready to be called synchronously it will just set the correct value to `_summary`. In case there were any errors while subscribing the listener the `catch` block sets `_summary` back to its previous value, as this is the behavior we have right now. I believe that's why `ConnectionState.waiting` was being set last, just in case if there were any errors on subscription it's value wouldn't change, but still this solution isn't flawless as `widget.afterConnected(_summary);` being called first on a derived class could have side effects.

This is just a recommendation on how to fix this, as I didn't test this code and I am not that much experienced with all the details of `StreamBuilder` and how this might effect it.