title: Flutter infinite loop when StreamBuilder inside LayoutBuilder
body: <p>So i am making a page with a <strong>LayoutBuilder</strong> as described <a href="https://docs.flutter.io/flutter/widgets/SingleChildScrollView-class.html#expanding-content-to-fit-the-viewport" rel="nofollow noreferrer">here</a> </p>
  
  <p>Inside the LayoutBuilder i put a <strong>StreamBuilder</strong> with a <strong>TextField</strong> powered by the bloc class <strong>SignupFormBlo</strong>c. The stream is a <strong>BehaviorSubject</strong></p>
  
  <p>When someone put something in the input it trigger the onChanged function which is the sink for my stream. So i add the value in the stream then i pass the value in a <strong>StreamTransformer</strong> to validate the value and then i let the StreamBuilder to build the TextField again with an error message(if value not valid). </p>
  
  <p>This is were the problem starts.</p>
  
  <p>When i click on the TextField and enter something it starts an infinite loop like this:</p>
  
  <ul>
  <li>The StreamBuilder sees the new value in the stream</li>
  <li>The StreamBuilder try to rebuild TextField</li>
  <li>Some how this triggers the LayoutBuilder builder function</li>
  <li>The LayoutBuilder builder function builds again the StreamBuilder</li>
  <li>StreamBuilder find a value in stream(because of the BehaviorSubject)</li>
  <li>and all start again from the first bulled in an endless loop</li>
  </ul>
  
  <p><strong>Hint:</strong> If i change the BehaviorSubject to a PublishSubject everything is ok</p>
  
  <p><strong>Hint 2:</strong> If i remove the StreamBuilder completely and just let a blank TextField, you can see that in every entry the LayoutBuilder builder function run. Is that a normal behavior?</p>
  
  <pre><code>import 'dart:async';
  
  import 'package:flutter/material.dart';
  import 'package:rxdart/rxdart.dart';
  
  void main() =&gt; runApp(MyApp());
  
  class MyApp extends StatelessWidget {
    // This widget is the root of your application.
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Flutter Demo Home Page'),
      );
    }
  }
  
  class MyHomePage extends StatefulWidget {
    MyHomePage({Key key, this.title}) : super(key: key);
  
    final String title;
  
    @override
    _MyHomePageState createState() =&gt; _MyHomePageState();
  }
  
  class _MyHomePageState extends State&lt;MyHomePage&gt; {
  
  
    SignupFormBloc _signupFormBloc;
  
    @override
    void initState() {
      super.initState();
      _signupFormBloc = SignupFormBloc();
    }
  
    @override
    Widget build(BuildContext context) {
      print('Build Run!!!!!');
      return Scaffold(
        appBar: AppBar(
  
          title: Text(widget.title),
        ),
        body: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints viewportConstraints) {
            print('Layout Builder!!!');
            return SingleChildScrollView(
              child: ConstrainedBox(
                constraints: BoxConstraints(
                  minHeight: viewportConstraints.maxHeight,
                ),
                child: IntrinsicHeight(
                  child:         StreamBuilder&lt;String&gt;(
                    stream: _signupFormBloc.emailStream,
                    builder: (context, AsyncSnapshot&lt;String&gt; snapshot) {
  
                      return TextField(
                        onChanged: _signupFormBloc.onEmailChange,
                        keyboardType: TextInputType.emailAddress,
                        decoration: InputDecoration(
                          hintText: 'Email',
                          contentPadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 18),
                          filled: true,
                          fillColor: Colors.white,
                          errorText: snapshot.error,
                          border: new OutlineInputBorder(
                            borderSide: BorderSide.none
                          ),
                        ),
  
                      );
                    }
                  ),
                ),
              ),
            );
          },
        )
      );
    }
  
    @override
    void dispose() {
      _signupFormBloc?.dispose();
      super.dispose();
    }
  
  }
  
  
  class SignupFormBloc  {
  
    ///
    /// StreamControllers
    ///
    BehaviorSubject&lt;String&gt; _emailController = BehaviorSubject&lt;String&gt;();
  
  
    ///
    /// Stream with Validators
    ///
    Observable&lt;String&gt; get emailStream =&gt; _emailController.stream.transform(StreamTransformer&lt;String,String&gt;.fromHandlers(handleData: (email, sink){
  
      final RegExp emailExp = new RegExp(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$");
  
      if (!emailExp.hasMatch(email) || email.isEmpty){
        print('has error');
        sink.addError('Email format is invalid');
      } else {
        sink.add(email);
      }
    }));
  
  
    ///
    /// Sinks
    ///
    Function(String) get onEmailChange =&gt; _emailController.sink.add;
  
  
    void dispose() {
      _emailController.close();
    }
  
  
  
  }
  </code></pre>