Case Study: Externally rebuilding StatefulWidget
Tags: flutter, todayilearned, programming, mobile
Published: 2021-11-04 09:48:35

![Stock image of alphabet blocks that altogether constitute to a word of REBUILDING](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i5fe2tzp6cgv0r8pj2to.jpg)

If you have been developing apps on Flutter long enough, chances are you've been making use of a state management tool, like [Provider](https://pub.dev/packages/provider), [BLoC](https://pub.dev/packages/flutter_bloc), [MobX](https://pub.dev/packages/flutter_mobx), or the like.

If so, there might be a chance you've stumbled upon a certain problem, whereby a `StatefulWidget` doesn't choose to update itself upon state change.

This is a pretty specific problem. So in this article, we will walk through the problem and its solution via examples.

If you just want to know the summary, feel free to scroll downwards to the <strong>Summary</strong> section.

## Building the Counter App
Let's start off with the Hello World of Flutter app, the Counter App. For this, we will be using MobX as its state management tool.

<strong>counter_store.dart</strong>
```dart
import 'package:mobx/mobx.dart';

part 'counter_store.g.dart';

class CounterStore = _CounterStore with _$CounterStore;

abstract class _CounterStore with Store {
  @observable
  int sum = 0;

  @action
  void increment() {
    sum++;
  }
}
```
Whether you are familiar with MobX or not, just understand that we have a store that contains a data member of `sum`, and a method that adds said data member by 1.

<strong>my_home_page.dart</strong>
```dart
class MyHomePage extends StatelessWidget {
  final counterStore = CounterStore();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter'),
      ),
      body: Center(
        child: Observer(
          builder: (_) => StatelessCounterBody(
            value: counterStore.sum,
            onPressedAddButton: counterStore.increment,
          ),
        ),
      ),
    );
  }
}
```

<strong>stateless_counter_body.dart</strong>
```dart
class StatelessCounterBody extends StatelessWidget {
  final int value;
  final GestureTapCallback onPressedAddButton;

  const StatelessCounterBody({
    Key? key,
    required this.value,
    required this.onPressedAddButton,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text(
          'You have pushed the button this many times:',
        ),
        Text(
          '$value',
          style: Theme.of(context).textTheme.headline4,
        ),
        const Padding(padding: EdgeInsets.all(16.0)),
        FloatingActionButton(
          onPressed: onPressedAddButton,
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ],
    );
  }
}
```
This Counter App here is done a bit differently. We've refactored the content as a `StatelessWidget` with the name of `StatelessCounterBody`.

From where this `StatelessCounterBody` is used (my_home_page.dart), once the `onPressedAddButton` callback is triggered, `counterStore.increment()` is called to set the store's `value`. Therefore the wrapping `Observer` widget will discard the current instance and rebuild a new instance of `StatelessCounterBody`, where the value of its `value` parameter is the new value of `sum`.

![Voila! The Counter App works!](https://media.giphy.com/media/ra8LDGzWVlOFNTa5qi/giphy.gif)

Sweet! Works well so far.

## Going Reactive, Going Stateful
![Messi in his usual self, being "reactive"](https://idsb.tmgrup.com.tr/ly/uploads/images/2021/08/30/140354.jpg)
Now comes a thought, of making a `StatefulWidget` version of `StatelessCounterBody`, and we want to call it as `StatefulCounterBody`.

Why would we want to do that? That's because now we want to make this widget reactive than static.

And yes, `setState` shall be used for internal rebuilding upon state changes for this widget in mind, because we do not want it to be reliant on other external means to have its state mutated (for example, expecting a certain store of a certain state management tool as its parameter and mutations are done with it).

<strong>stateful_counter_body.dart</strong>
```dart
class StatefulCounterBody extends StatefulWidget {
  final int initialValue;
  final Function(int) onValueIncrement;

  const StatefulCounterBody({
    Key? key,
    required this.initialValue,
    required this.onValueIncrement,
  }) : super(key: key);

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

class _StatefulCounterBodyState extends State<StatefulCounterBody> {
  late int _value;

  @override
  void initState() {
    super.initState();

    _value = widget.initialValue;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text(
          'You have pushed the button this many times:',
        ),
        Text(
          '$_value',
          style: Theme.of(context).textTheme.headline4,
        ),
        const Padding(padding: EdgeInsets.all(16.0)),
        FloatingActionButton(
          onPressed: () {
            setState(() {
              _value++;
              widget.onValueIncrement(_value);
            });
          },
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ],
    );
  }
}
```

For the case of `StatefulCounterBody`, we want it to react to the value changes all in itself. From the outside, the `initialValue` is to be provided, as well as a callback called `onValueIncrement`, where in itself returns the updated value upon incrementing.

## Changed My Mind
So now, we decided that we want to add another button at the home page, where upon click adds the `sum` by 5.

<strong>counter_store.dart</strong>
```dart
import 'package:mobx/mobx.dart';

part 'counter_store.g.dart';

class CounterStore = _CounterStore with _$CounterStore;

abstract class _CounterStore with Store {
  @observable
  int sum = 0;

  @action
  void increment() {
    sum++;
  }
  
  // Newly added!
  @action
  void incrementByFive() {
    sum += 5;
  }
}
```
Hence, adding `incrementByFive()` to the store.

## Making Use Of That StatefulWidget
<strong>my_home_page.dart</strong>
```dart
class MyHomePage extends StatelessWidget {
  final counterStore = CounterStore();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter'),
      ),
      body: Center(
        child: Observer(
          builder: (_) => StatefulCounterBody(
            initialValue: counterStore.sum,
            onValueIncrement: (value) {
              counterStore.sum = value;
            },
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: counterStore.incrementByFive,
        tooltip: 'Add by 5',
        child: Icon(Icons.add),
      ),
    );
  }
}
```
Button added, `StatefulCounterBody` ready. Let's give it a go.
![Nice](https://media.giphy.com/media/t87uW47nGLWK30in2J/giphy.gif)

Nice! Now let's try the newly added button; bottom right.

![Oh no](https://media.giphy.com/media/OG3jumGS8esXhEoDjK/giphy.gif)

Wait. Why isn't that working?!

## The Fact about StatefulWidgets
![Stateless and StatefulWidget diagrams](https://www.flutterclutter.dev/wp-content/uploads/2020/08/statelesswidget-vs-statefulwidget-diagram-1536x1231.png)
<strong>Diagram by Flutter Clutter. Check out his article about [StatelessWidget vs. StatefulWidget](https://www.flutterclutter.dev/flutter/basics/statelesswidget-vs-statefulwidget/2020/1195/)</strong>

Whenever a `StatefulWidget` is constructed, the `State` first gets created via `createState()`, then the `StatefulWidget` is built. As the state changes, while the `StatefulWidget` gets discarded and rebuilt, (internally via `setState()` or externally via `Observer`, `BlocBuilder`, etc.), the `State` remains.

For our case, as `counterStore.incrementByFive()` was called and the `Observer` tries to externally rebuild a new instance of `StatefulCounterBody`, the instance was rebuilt, but the `State` persisted.

That is because we are yet to add something that informs the `StatefulWidget` to look out for the differences between the old and the new instances that should affect the state.

## Art Thou Updated?
```dart
@override
void didUpdateWidget(covariant StatefulCounterBody oldWidget) {
  super.didUpdateWidget(oldWidget);

  if (oldWidget.initialValue != widget.initialValue) {
    _value = widget.initialValue;
  }
}
```

Therefore, the lifecycle method we can make use of is `didUpdateWidget()`.

If there's a difference between the previous instance and the new instance, then it should update the state with that of the new instance accordingly.

![Oh no](https://media.giphy.com/media/pCA4UTQ2aIVUcIKWaI/giphy.gif)

Brilliant! It now works! :D

## Summary
If you have problems trying to externally rebuild a `StatefulWidget` with `Observer`, `BlocBuilder` or by however rebuilding is done in other state management tools, then the widget has to know how to be aware of updating its `State` accordingly if there's found to be differences between the old and the new instance by using the `didUpdateWidget()` lifecycle method.

{% gist https://gist.github.com/benz93chung/0d831f7a01d6f3b8a326b6087fee2a46.js %}