CodeX
Published in

CodeX

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 Keys play in maintaining state between widget re-creation. A great explanation of Keys 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!

--

--

--

Everything connected with Tech & Code. Follow to join our 900K+ monthly readers

Recommended from Medium

How to Create Animations and Transitions for your Android App

Barista UI Testing(on top of Espresso)

Run a command in a running Android Emulator or Device

ANDROID FILE SYSTEM

State of Mobile App Technology Stack

Huawei Panorama Kit

Learn asynchronous programming with Kotlin Coroutines

It has never been easier to understand how to write Unit Tests on Android — Part 1

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Lee Phillips

Lee Phillips

Software developer. Flutter fanatic. Other interests include photography, sports, coffee, and food.

More from Medium

Create a scrollable and reorderable nav bar package Flutter

How to Run CocoaPods on Apple Silicon (M1)?

Handling Firebase DeepLink Fallback for Desktop(website) in Flutter

Flutter Navigator.pushNamed Widget