Using Keys to Maintain Scroll State After an Orientation Change
A Flutter Case Study

Welcome to the second article in our Case Study Series. If you haven’t checked out the first article on catching back button presses on Android when using Navigator 2.0, please check it out here. Today, we will discuss the roll Key
s play in maintaining state between widget re-creation. A great explanation of Key
s and their use with widgets can be found in this excellent video from the Flutter team.
If you’d like to get your hands dirty with an example to better understand this in practice, stick around and follow along.
A user on StackOverflow wanted to provide a different screen layout when the device orientation changed (Row
in landscape & Column
in portrait). This is, of course, a piece of cake with the OrientationBuilder
widget. However, the user noticed that when the device orientation changed while using a PageView
, the current page index was lost. This is due to a new PageView
widget being created on orientation change and the state being lost. In fact, the same thing would happen with any Scrollable
or similar stateful widget. See for yourself by running the following code.
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}class _HomePageState extends State<HomePage> {
final _controller = PageController();
final _modes = ['Bike', 'Boat', 'Car', 'Foot'];
final _icons = [
Icons.directions_bike,
Icons.directions_boat_outlined,
Icons.directions_car,
Icons.directions_run,
];
var _page = 0; Widget _buildHeader() {
return ColoredBox(
color: Colors.grey,
child: Center(
child: Text(
'Travel Information',
style: TextStyle(color: Colors.white, fontSize: 24.0),
),
),
);
} @override
Widget build(BuildContext context) {
return Scaffold(
body: OrientationBuilder(builder: (context, orientation) {
return orientation == Orientation.portrait
? Column(
children: [
Expanded(child: _buildHeader()),
Expanded(
child: PageView(
controller: _controller,
children: _modes.map((mode) =>
Center(child:Text(mode))).toList(),
),
),
],
)
: Row(
children: [
Expanded(child: _buildHeader()),
Expanded(
child: PageView(
controller: _controller,
children: _modes.map((mode) =>
Center(child: Text(mode))).toList(),
),
),
],
);
}),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _page,
onTap: (page) {
setState(() => _page = page));
_controller.jumpToPage(page);
},
items: [
BottomNavigationBarItem(
icon: Icon(_icons[0]),
label: _modes[0],
),
BottomNavigationBarItem(
icon: Icon(_icons[1]),
label: _modes[1],
),
BottomNavigationBarItem(
icon: Icon(_icons[2]),
label: _modes[2],
),
BottomNavigationBarItem(
icon: Icon(_icons[3]),
label: _modes[3],
),
],
),
);
}
}
You will notice that the PageView
's current page is reset every time the device is rotated, but the selected item in the BottomNavigationBar
is not, causing us to not only lose our place but for the app navigation to become out-of-sync. Thankfully, there is a simple solution.
We can create a PageStorageKey
and pass it to both PageView
widgets. This tells the Flutter framework that this is actually the same widget and that the page state should be maintained between instances.
class _HomePageState extends State<HomePage> {
// create a PageStorageKey, passing in a value for identification
final _key = PageStorageKey('pageStorageKey');
final _controller = PageController(); ...
// pass to the Columns PageView
child: PageView(
key: _key,
controller: _controller,
...
// pass to the Row's PageView
child: PageView(
key: _key,
controller: _controller,
...
}
And there you have it! Re-run the app and you will notice that the page is maintained between orientation changes keeping navigation in sync and maintaining the users place. As noted earlier, this same approach will maintain scroll state between Scrollable
widgets. I encourage you to give it a try, and definitely check out the video at the top of the article if you have not. It will provide a deeper understanding of when and where to use keys, as well as what kind are appropriate for certain situations.
Thank you for reading! If you found this article helpful or enjoyable and would like to read more case studies, please clap and follow. Happy coding!