Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set state from another widget?

Tags:

flutter

dart

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?

like image 559
Bram Vanbilsen Avatar asked May 19 '18 23:05

Bram Vanbilsen


2 Answers

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.

like image 182
Rémi Rousselet Avatar answered Oct 12 '22 22:10

Rémi Rousselet


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

like image 26
matshidis Avatar answered Oct 12 '22 23:10

matshidis