Search code examples
flutterdartrxdart

StreamBuilder not correctly refreshing on new value


Probably it's my fault or a dumb error but this is what is happening to me:

authController.dart:

class AuthController {
BehaviorSubject _hometownController = BehaviorSubject();
Observable get hometown => _hometownController.stream;
void updateHometown(String hometown) {
    _hometownController.add(hometown);
    print(_hometownController.value); <- This always prints the expected value: "Hello!"`
}
}

Login_screen.dart:

class Login2 extends StatelessWidget {
  AuthController authController = new AuthController();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Builder(
        builder: (BuildContext context) =>
          Container(
            padding: EdgeInsets.all(32.0),
            child: Center(
              child:
                  //Hometown:
                  StreamBuilder(
                    stream: authController.hometown,
                    builder: (BuildContext context, AsyncSnapshot snap) {
                      return ListTile(
                        leading: Icon(Icons.map),
                        title: Text("Hometown:"),
                        subtitle: snap.hasData ? Text(snap.data) : Text("Not set"),
                        onTap: () {
                          authController.updateHometown("Hello!");
                        },
                      );
                    }
                  ),
  ))));}}

And everything goes well. But if I add an asynchronous function with async and await before authController.updateHometown:

Login_screen.dart:

                            onTap: () async {
                              var x = await something(); <- This always successfully completes
                              authController.updateHometown("Hello!");
                            },

The stream never receives the new value and the StreamBuilder never rebuilds!

But if I use a statefulWidget class:

class Login2 extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => Login2State();
}

class Login2State extends State<Login2> {
  AuthController authController;

  @override
  void initState() {
    authController = new AuthController();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Builder(
        builder: (BuildContext context) =>
          Container(
            padding: EdgeInsets.all(32.0),
            child: Center(
              child:
                  //Hometown:
                  StreamBuilder(
                    stream: authController.hometown,
                    builder: (BuildContext context, AsyncSnapshot snap) {
                      return ListTile(
                        leading: Icon(Icons.map),
                        title: Text("Hometown:"),
                        subtitle: snap.hasData ? Text(snap.data) : Text("Not set"),
                        onTap: () async {
                          var x = await something();
                          authController.updateHometown("Hello!");
                        },
                      );
     }),))));}}

Everything goes well, even if I never call setState Why is this happening?


Solution

  • From Framework.dart :

    A StatelessWidget builds itself depending on data that has two conditions:

    1. can be read synchronously when the widget is built. And :
    2. might change during the lifetime of the widget.

    That's why when you make Login2 as StatelessWidget, and then change the data in authController synchronously it is immediately reflected because it satisfies both conditions. But when you use an async function inside onTap() you break the first one.

    And why StatefulWidget works with async function is because its build function only needs a data change to trigger setState() implicitly irrespective of whether this info is available synchronously or asynchronously.