[Newbie] Chapter 4. Widget’s state

[Newbie] Chapter 1. Flutter overview

[Newbie] Chapter 2. Flutter getting started

[Newbie] Chapter 3. Flutter initial setup

Content

  • Widget lifecycle
  • State management approach

Widget’s lifecycle

Flutter has two types of widget: StatefulWidget vs StatelessWidget

  • The widget has its own properties which passed via constructor
  • The stateless widget doesn’t keep any state
  • The stateful widget keeps a state object which contains all value of that state

What is the state’s lifecycle?

We have 2 approaches when implemented UI

  • Approach 1: Implement all child widgets inside the root widget. In this case, we just use only one state stored at the root widget. Any changes from any child widget will impact on state and rebuild all children on the widget tree. The problem with this approach is when the widget tree grows too big, the set state needs to take much of the performance to render. 
  • Approach 2: Separate child widget by new StatefulWidget (not separated by function returned widget) In this case, the state will be separated also. If you change state from root it will impact all cascade child of course. But if you change state on child widget, it does not affect friends widgets. The problem with this approach is having a lot of states need to handle smoothly.

Some notes for the widget when using

  • Ensure WidgetsFlutterBinding initialized by at this line to main function of main.dart before runApp(MyApp());
WidgetsFlutterBinding.ensureInitialized();
  • To make sure executing after widget built by addPostFrameCallback
class AppContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) => onAfterBuild(context));

    return …..;
  }

  // @nhancv 10/25/2019: After widget initialized.
  void onAfterBuild(BuildContext context) {
    // do anything here
  }
}

AppLifecycleState

With Flutter, how to know the app go to the background or foreground via WidgetsBindingObserver

class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
  @override
  void initState() {
    WidgetsBinding.instance.addObserver(this);
    super.initState();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }


  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.paused) {
      // Background
    }
    if (state == AppLifecycleState.resumed) {
      // Foreground
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AppLifecycleState'),
      ),
      body: Center(),
    );
  }
}

To get more details of AppLifecycleState

https://api.flutter.dev/flutter/dart-ui/AppLifecycleState-class.html

State management approach

For more detail about some state management approaches: 

I introduce to you a lib called bflutter  to help you handle state with Bloc approach.

For simple, just keep in mind:

  • Anywhere can emit a signal
  • Anywhere can catch a signal
  • Emit and catch meet each other via stream

Therefore, with bflutter you just define the logic from data input, config place which wants to take data for display or filter or something. When you want to trigger a logic, just push out a signal. That’s it.

Go to an example

Based on the initial project, I added an EmptyWidget extend from StatelessWidget, put print log in in build function

Here is setState approach
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    print('build $_counter');
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            EmptyWidget(),
            
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class EmptyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('build empty widget');
    return Container();
  }
}

I added a log in build function, run the app, click on ‘+’ to five and let’s see

Apply bflutter

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  final bloc = BlocDefault<int>();

  void _incrementCounter() {
    _counter++;
    bloc.push(_counter);
  }

  @override
  Widget build(BuildContext context) {
    print('build $_counter');
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            EmptyWidget(),
            Text(
              'You have pushed the button this many times:',
            ),
            StreamBuilder(
              stream: bloc.stream,
              initialData: 0,
              builder: (context, snapshot) {
                return Text(
                  '${snapshot.data}',
                  style: Theme.of(context).textTheme.display1,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

class EmptyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('build empty widget');
    return Container();
  }
}
  • Run the app, press ‘+’ to increase counter to 5 and see the magic
  • The app does not print build widget anymore and the counter keeps working fine. Yeah.

Leave a Reply

Your email address will not be published.Required fields are marked *