Preserve widget state when temporarily removed from tree in Flutter Preserve widget state when temporarily removed from tree in Flutter dart dart

Preserve widget state when temporarily removed from tree in Flutter


The reason why the widget loose its state when removed from the tree temporarily is, as Joshua stated, because it loose its Element/State.

Now you may ask:

Can't I cache the Element/State so that next time the widget is inserted, it reuse the previous one instead of creating them anew?

This is a valid idea, but no. You can't.Flutter judges that as anti-pattern and will throw an exception in that situation.

What you should instead do is to keep the widget inside the widget tree, in a disabled state.

To achieve such thing, you can use widgets like:

  • IndexedStack
  • Visibility/Offstage

These widgets will allow you to keep a widget inside the widget tree (so that it keeps its state), but disable its rendering/animations/semantics.

As such, instead of:

Widget build(context) {  if (condition)    return Foo();  else    return Bar();}

which would make Foo/Bar loose their state when switching between them

do:

IndexedStack(  index: condition ? 0 : 1, // switch between Foo and Bar based on condition  children: [    Foo(),    Bar(),  ],)

Using this code, then Foo/Bar will not loose their state when doing a back and forth between them.


Widgets are meant to store transient data of their own within their scope and lifetime.

Based on what you have provided, you are trying to re-create CounterButton child widget, by removing and adding it back to the widget tree.

In this case, the counter value that is under the CounterButton was not saved or not saving in the MyHomePage screen, the parent widget, without any reference to a view model or any state management within or at the top level.

A more technical overview how Flutter renders your widgets

Ever wonder what is the key if you try to create a constructor for a widget?

class CounterButton extends StatefulWidget {  const CounterButton({Key key}) : super(key: key);  @override  _CounterButtonState createState() => _CounterButtonState();}

keys (key) are identifiers that are automatically being handled and used by the Flutter framework to differentiate the instances of widgets in the widget tree. Removing and adding the widget (CounterButton) in the widget tree resets the key assigned to it, therefore the data it holds, its state are also removed.

NOTE: No need to create constructors for the a Widget if it will only contain key as its parameter.

From the documentation:

Generally, a widget that is the only child of another widget does not need an explicit key.

Why does Flutter changes the key assigned to the CounterButton?

You are switching between CounterButton which is a StatefulWidget, and Text which is a StatelessWidget, reason why Flutter identifies the two objects completely different from each other.

You can always use Dart Devtools to inspect changes and toggle the behavior of your Flutter App.

Keep an eye on #3a4d2 at the end of the _CounterButtonState.image-1

This is the widget tree structure after you have toggled the widgets. From CounterButton to the Text widget.image-2

You can now see that the CounterButton ending with #31a53, different from the previous identifier because the two widgets are completely different.image-2

What can you do?

I suggest that you save the data changed during runtime in the _MyHomePageState, and create a constructor in CounterButton with a callback function to update the values in the calling widget.

counter_button.dart

class CounterButton extends StatefulWidget {  final counterValue;  final VoidCallback onCountButtonPressed;  const CounterButton({Key key, this.counterValue, this.onCountButtonPressed})      : super(key: key);  @override  _CounterButtonState createState() => _CounterButtonState();}class _CounterButtonState extends State<CounterButton> {  @override  Widget build(BuildContext context) {    return MaterialButton(      color: Colors.orangeAccent,      child: Text(widget.counterValue.toString()),      onPressed: () => widget.onCountButtonPressed(),    );  }}

Assuming you named your variable _counterValue in the _MyHomePageState, you can use it like this:

home_page.dart

_showCounterWidget    ? CounterButton(        counterValue: _counterValue,        onCountButtonPressed: () {          setState(() {            _counterValue++;          });        })    : Text("Other widget"),

In addition, this solution will help you re-use CounterButton or other similar widgets in other parts of your app.

I've added the complete example in dartpad.dev.

Andrew and Matt gave a great talk how Flutter renders widgets under the hood:

Further reading


The real solution to this problem is state management. There are several good solutions for this available as concepts and flutter packages. Personally I use the BLoC pattern regularly.

The reason for this is that widget state is meant to be used for UI state, not application state. UI state is mostly animations, text entry, or other state that does not persist.

The example in the question is application state as it is intended to persist longer than the live time of the widget.

There is a little Tutorial on creating a BLoC based counter which could be a good starting point.