45

I'm trying to change the state from a different widget in Flutter. For example, in the following example I set the state after a few seconds.

Here is the code for that:

class _MyAppState extends State<MyApp> {

  int number = 1;

  @override
  void initState() {
    super.initState();
    new Future.delayed(new Duration(seconds: 5)).then((_) {
      this.setState(() => number = 2);
      print("Changed");
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Center(
        child: new FlatButton(
          color: Colors.blue,
          child: new Text("Next Page"),
          onPressed: () {
            Navigator.of(context).push(new MaterialPageRoute(
              builder: (BuildContext context) => new StatefulBuilder(builder: (BuildContext context, setState) =>new MySecondPage(number))
            ));
          },
        ),
      ),
    );
  }
}

I tried using an InheritedWidget, but that won't work unless I wrap it around my top level widget, which is not feasible for what I'm trying to do (the code above is a simplification of what I'm trying to achieve).

Any ideas on what the best way of achieving this is in Flutter?

Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
Bram Vanbilsen
  • 5,763
  • 12
  • 51
  • 84

2 Answers2

92

Avoid this whenever possible. It makes these widgets depends on each others and can make things harder to maintain in the long term.

What you can do instead, is having both widgets share a common Listenable or something similar such as a Stream. Then widgets interact with each other by submitting events.

For easier writing, you can also combine Listenable/Stream with respectively ValueListenableBuilder and StreamBuilder which both do the listening/update part for you.

A quick example with Listenable.

class MyHomePage extends StatelessWidget {
  final number = new ValueNotifier(0);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ValueListenableBuilder<int>(
        valueListenable: number,
        builder: (context, value, child) {
          return Center(
            child: RaisedButton(
              onPressed: () {
                number.value++;
              },
              child: MyWidget(number),
            ),
          );
        },
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  final ValueListenable<int> number;

  MyWidget(this.number);

  @override
  Widget build(BuildContext context) {
    return new Text(number.value.toString());
  }
}

Notice here how we have our UI automatically updating when doing number.value++ without ever having to call setState.

Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 1
    That looks really promising. I'll test it out first thing in the morning! But one thing I do not fully get is how Flutter knows to re-render the widget. Could you explain that part? – Bram Vanbilsen May 19 '18 at 23:50
  • 1
    It's the `AnimatedWidget`/`StreamBuilder` which actually contains rerender logic. They both listen to their respective object, and call `setstate` when they are notified of a change where `builder` gets called again. – Rémi Rousselet May 19 '18 at 23:53
  • The trick behind it is that, in my example, `MyHomePage` isn't actually updating. It's `AnimatedWidget` which does. – Rémi Rousselet May 19 '18 at 23:55
  • Aha, that explains it! – Bram Vanbilsen May 19 '18 at 23:59
  • I tried this out and I have to say that I was rather disappointed with the result. The performance seems very low. I try passing absolute coordinates to my popup route from my previous route to update the UI whenever the coordinates change. But the UI in my popup (using the `AnimatedBuilder` updates only about a second later. Is this expected? If so, how can I resolve this? – Bram Vanbilsen May 20 '18 at 11:49
  • That's very unexpected. Tons of thing in flutter follow this principle, and they still update at a 60 FPS. I don't have enough info to solve your problem. – Rémi Rousselet May 20 '18 at 11:59
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/171417/discussion-between-remi-rousselet-and-bram-vanbilsen). – Rémi Rousselet May 20 '18 at 12:00
  • Alright, then it is something on my side! There is too much code to just drop on here (partly because it's a mess atm). I'm going to clean everything up and try to pinpoint the problem. I'll keep you posted! – Bram Vanbilsen May 20 '18 at 12:01
  • If a ListView contains multiple widgets and each item having 2 widgets with individual ValueListenableBuilder values, /i see some issues. When one of the widget value changes, the other list children are also painted. Any idea on this? – Purus Nov 10 '18 at 13:40
  • That depends on far too many things for me to answer. Consider making an SO question about it. – Rémi Rousselet Nov 10 '18 at 13:49
  • 1
    @BramVanbilsen If you have a lot of stuff nested inside the widget, instead of having `ValueListenableBuilder` rebuild everything every time, you should look into the `child` parameter, and use that to significantly optimize rendering. Read more: https://docs.flutter.io/flutter/widgets/ValueListenableBuilder-class.html – WSBT Dec 27 '18 at 04:56
  • can u help https://stackoverflow.com/questions/61665353/widget-value-not-updating – Hoo May 08 '20 at 07:20
  • @RémiRousselet In the case where the requirement is to always listen to changes, we have to leave streams open all the time. Is that not bad? – Yudhishthir Singh Jun 01 '20 at 06:15
10

Actually the most effective way to do this is using BLoC package in flutter and implement it from the top of the widget tree so all inheriting widgets can use the same bloc. If you have worked with Android before - it works like Android Architecture Components - you separate data and state management from the UI - so you do not setState in the UI, but instead use the block to manage state. So you can set and access the same data - from any widget that inherits from the top widget where the bloc is implemented, for more complex apps, it is very useful.

This is where you can find the package: https://pub.dev/packages/flutter_bloc#-readme-tab-

Write-up: https://www.didierboelens.com/2018/08/reactive-programming-streams-bloc/

And a great tutorial on youtube https://www.youtube.com/watch?v=hTExlt1nJZI&list=PLB6lc7nQ1n4jCBkrirvVGr5b8rC95VAQ5&index=7

matshidis
  • 489
  • 5
  • 11