When using PageView and moving to another page and coming back to the original page, its state is clear. I just wanted to check another page’s state but I have to input the value again. It’s not nice. How can we implement to retain the state?
Structure of widget tree
My widget structure looks like the following.
The state of ItemSelectView is clear When switching the page. ItemWidget has one of the widgets for the input. It depends on the entries stored in a database in which items are shown on the page. I want to retain the value of the child widget of ItemWidget. Namely, they are CounterWidget and NumberInputWidget.
Using PageStorage for widgets located at the bottom layer
Firstly, I tried to use PageStorageKey for the widgets located at the bottom layer. However, I’ve already added GlobalObjectKey to ItemWidget and specified it to the constructor of CounterWidget because ItemWidget needs to clear the value of the child widget. It means that I can’t add an additional key to the widget.
I tried the following code but it doesn’t work in my app because GlobalObjectKey is specified as the key in ItemWidget which is the parent widget.
class NumberInputWidget extends StatefulWidget {
final double maxWidth;
final bool showCounter;
NumberInputWidget({
this.maxWidth = 80,
this.showCounter = false,
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _NumberInputWidget();
}
class _NumberInputWidget
extends ValueControllerState<NumberInputWidget> {
TextEditingController _controller = TextEditingController();
String _text = "";
@override
void initState() {
super.initState();
String? storedValue = PageStorage.of(context)?.readState(context);
if (storedValue != null) {
_controller.text = storedValue;
} else {
_text = "";
}
_controller.addListener(() {
_text = _controller.text;
PageStorage.of(context)?.writeState(context, _text);
});
}
@override
void dispose() {
super.dispose();
this._controller.dispose();
}
...
}
It tries to get the previous state but it is always null because the key is not PageStorageKey. In this case, writeState function in the TextEditingController’s listener doesn’t save the data. To save the data, we need PageStorageKey.
If this is used in a different place with PageStorageKey it should work.
// main.dart
var pageView = PageView(
controller: _controller,
children: [
ItemSelectView(),
// added new page that has input widget
Scaffold(
body: Center(
child: NumberInputWidget(
key: PageStorageKey<String>("KKK"),
),
)),
TimerView(),
],
);
It didn’t work!! Following is the error message.
════════ Exception caught by widgets library ═══════════════════════════════════ The following _CastError was thrown building MouseRegion(listeners: <none>, cursor: defer, state: _MouseRegionState#df326): type 'String' is not a subtype of type 'double?' in type cast The relevant error-causing widget was TextField lib\Widget\NumberInputWidget.dart:63 When the exception was thrown, this was the stack #0 ScrollPosition.restoreScrollOffset package:flutter/…/widgets/scroll_position.dart:420 #1 new ScrollPosition package:flutter/…/widgets/scroll_position.dart:105 #2 new ScrollPositionWithSingleContext package:flutter/…/widgets/scroll_position_with_single_context.dart:56 #3 ScrollController.createScrollPosition package:flutter/…/widgets/scroll_controller.dart:234 #4 ScrollableState._updatePosition package:flutter/…/widgets/scrollable.dart:422 ...
It took me a while to find the wrong line. The following listener causes this error. _text holds string value but it seems that the value is handled as double value inside of writeState function.
_controller.addListener(() {
_text = _controller.text;
PageStorage.of(context)?.writeState(context, _text);
});
The problem has gone when I added the identifier.
@override
void initState() {
super.initState();
// id is specified to identifier arg
final id = ValueKey("value");
String? storedValue =
// id is specified
PageStorage.of(context)?.readState(context, identifier: id);
if (storedValue != null) {
_controller.text = storedValue;
} else {
_text = "";
}
_controller.addListener(() {
_text = _controller.text;
// id is specified
PageStorage.of(context)?.writeState(context, _text, identifier: id);
});
}
It retains its value even when switching the views. You might recognize that the same value is shown in a different view. This is because the same key "value"
is used for a different instance. To fix the bug, we can use for example hasCode.
final id = ValueKey(widget.hashCode);
The new instance is created many times for State class but StatefulWidget class doesn’t change. Therefore, we should use widget hashCode instead of State class hashCode.
It might be better to define onChanged event in TextField than using TextEditingController in this simple case.
Setting PageStorageKey to parent widget
Let’s look at my app structure again.
In the previous example, PageView widget has NumberInputWidget directly. It means that it can assign PageStorageKey to the NumberInputWidget. However, ItemWidget has already assigned GlobalObjectKey to NumberInputWidget in my app. I couldn’t assign PageStorageKey to those input widgets in ItemWidget anymore.But, wait… It is enough to retain the data in ItemSelectView. Following code does what I want without any other changes.
This is my misunderstanding. It looked working because the view showed correct contents when I switched the two views quickly.
var pageView = PageView(
controller: _controller,
children: [
ItemSelectView(key:PageStorageKey("ItemSelectView")),
TimerView(),
],
);
Comments
Thanks for the article.
Is there a way to keep the pageView state itself when switching to another app page? My use case is with flutterflow. A feed page based on pageView that uses infinite loading. Now the user swiped down 20 times, click on “View profile” button and then clicked on “Return” button. He/she expects to be in the same swiping position and see the last post again.
There is no built in way to preserve the list of read posts and the pageView index and infinite scrolling offset.
Any idea how this problem may be solved?
Thanks
Hi, Yair. Thank you for reading my article.
The way of this article is not a popular way in Flutter. To keep the current state, I recommend that you should use Provider or Riverpod. I would use Riverpod.
You can keep the content and the position in variables defined in Riverpod. You can use the stored value until you remove them in a specific action.
I wrote some articles Riverpod. This article helps you consider the structure.
Flutter How to implement Bloc like pattern with Riverpod