Flutter nested StreamBuilders causing Bad state: Stream has already been listened to : <p>I'm trying to build a Flutter app using the BLoC pattern described in the video <a href="https://www.youtube.com/watch?v=PLHln7wHgPE&amp;feature=youtu.be" rel="noreferrer" title="Flutter / AngularDart – Code sharing, better together &#40;DartConf 2018&#41;">Flutter / AngularDart – Code sharing, better together (DartConf 2018)</a></p>

<p>A BLoC is basically a view model with <code>Sink</code> inputs and <code>Stream</code> outputs. In my example it looks a bit like this:</p>

<pre><code> class BLoC {
    // inputs
    Sink&lt;String&gt; inputTextChanges;
    Sink&lt;Null&gt; submitButtonClicks;

    // outputs
    Stream&lt;bool&gt; showLoading;
    Stream&lt;bool&gt; submitEnabled;
 }
</code></pre>

<p>I have the BLoC defined in a widget near the root of the hierarchy and it is passed down to widgets beneath it, including nested <code>StreamBuilders</code>. Like so:</p>

<p><a href="https://i.stack.imgur.com/RQlJL.png" rel="noreferrer"><img src="https://i.stack.imgur.com/RQlJL.png" alt="BLoC widget hierarchy"></a></p>

<p>The top <code>StreamBuilder</code> listens to a <code>showLoading</code> stream on the BLoC so that it can rebuild to show an overlaid progress spinner. The bottom <code>StreamBuilder</code> listens to a <code>submitEnabled</code> stream to enable/disable a button.</p>

<p>The problem is whenever the <code>showLoading</code> stream causes the top <code>StreamBuilder</code> to rebuild the widget it rebuilds nested widgets too. This in itself is fine and expected. However this results in the bottom <code>StreamBuilder</code> being recreated. When this happens it attempts to re-subscribe to the existing <code>submitEnabled</code> stream on the BLoC causing <code>Bad state: Stream has already been listened to</code></p>

<p>Is there any way to accomplish this without making all of the outputs <code>BroadcastStreams</code>?</p>

<p>(There is also a chance that I'm fundamentally misunderstanding the BLoC pattern.)</p>

<hr>

<p>Actual code example below:</p>

<pre><code>import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'dart:async';

void main() =&gt; runApp(BlocExampleApp());

class BlocExampleApp extends StatefulWidget {

  BlocExampleApp({Key key}) : super(key: key);

  @override
  _BlocExampleAppState createState() =&gt; _BlocExampleAppState();
}

class _BlocExampleAppState extends State&lt;BlocExampleApp&gt; {

  Bloc bloc = Bloc();

  @override
  Widget build(BuildContext context) =&gt;
      MaterialApp(
        home: Scaffold(
            appBar: AppBar(elevation: 0.0),
            body: new StreamBuilder&lt;bool&gt;(
                stream: bloc.showLoading,
                builder: (context, snapshot) =&gt;
                snapshot.data
                    ? _overlayLoadingWidget(_buildContent(context))
                    : _buildContent(context)
            )
        ),
      );

  Widget _buildContent(context) =&gt;
      Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: &lt;Widget&gt;[
            TextField(
              onChanged: bloc.inputTextChanges.add,
            ),
            StreamBuilder&lt;bool&gt;(
                stream: bloc.submitEnabled,
                builder: ((context, snapshot) =&gt;
                    MaterialButton(
                      onPressed: snapshot.data ? () =&gt; bloc.submitClicks.add(null) : null,
                      child: Text('Submit'),
                    )
                )
            )
          ]
      );

  Widget _overlayLoadingWidget(Widget content) =&gt;
      Stack(
        children: &lt;Widget&gt;[
          content,
          Container(
            color: Colors.black54,
          ),
          Center(child: CircularProgressIndicator()),
        ],
      );
}

class Bloc {
  final StreamController&lt;String&gt; _inputTextChanges = StreamController&lt;String&gt;();
  final StreamController&lt;Null&gt; _submitClicks = StreamController();

  // Inputs
  Sink&lt;String&gt; get inputTextChanges =&gt; _inputTextChanges.sink;

  Sink&lt;Null&gt; get submitClicks =&gt; _submitClicks.sink;

  // Outputs
  Stream&lt;bool&gt; get submitEnabled =&gt;
      Observable&lt;String&gt;(_inputTextChanges.stream)
          .distinct()
          .map(_isInputValid);

  Stream&lt;bool&gt; get showLoading =&gt; _submitClicks.stream.map((_) =&gt; true);

  bool _isInputValid(String input) =&gt; true;

  void dispose() {
    _inputTextChanges.close();
    _submitClicks.close();
  }
}
</code></pre>
