Controlling screen orientation in Flutter apps on a per-screen basis
While working on the new Flutter app, I needed to make some screens appear in portrait mode only, others in landscape mode, and some should have supported both orientations. I had never done it before; all my previous apps were in portrait mode.
After quick googling, I’ve found the SystemChrome
class and this question on StackOverflow. The mixins from StackOverflow answer work great until you begin to use Navigator
. When you push new screens to the navigation stack, everything is fine, but when you pop, the orientation settings of the previous screen are not restored. There’s no lifecycle function like viewWillAppear
in Flutter, and that screen doesn’t get any updates from the engine. I could have used a Future
returned from Navigator.push
, but the code would become ugly. Keeping track of all places where I need to update orientation settings is a bad idea. I needed a simple and elegant solution with as little overhead as possible.
I started developing my solution by asking myself two questions: “How can I know when the new screen goes to the stack?” and “How can I know when the screen leave the stack?” The answer was simple: use NavigatorObserver
. You can add multiple observers to any Navigator
, including the one created by MaterialApp
.
Let’s begin with writing the function which sets the screen orientation:
To make this function simpler to use, I defined an enum
with possible orientation options. You can add more options if needed. You may also want to add DeviceOrientation.portraitDown
for ScreenOrientation.portraitOnly
if your app is targeted to tablets too. Another reason to use your own enum
is to add an abstraction level. If Flutter gets another way to handle screen orientations, you only need to change the _setOrientation
function.
The next step is to create a reusable NavigatorObserver
subclass:
I’m using the arguments
field of the RouteSettings
class to store screen orientation settings. Don’t worry; you still can pass arguments to routes. If there are no arguments, or the type of arguments
field is not ScreenOrientation
, functions use the default option. In this example, it’s portrait-only, but you may change it.
Okay, now everything is ready to build the app:
I don’t like hardcoded values, so I always define constants for app routes. rotationSettings
is a little convenience function, it simplifies creating a RouteSettings
object with arguments
field set to rotation option. You may use the arguments
field of the settings object passed to _onGenerateRoute
to configure your widgets. I prefer to do it this way, instead of using ModalRoute.of(context).settings.arguments
in the widget. However, if you’re not like me, you still can use my solution, but you will need to adapt code a bit:
- Create a generic class for route arguments, which will have two fields: screen orientation and a generic field for route arguments.
- Change
didPop
anddidPush
methods to check for the object of this class in thearguments
field and then use the screen orientation field for_setOrientation
call.
I’m not including the code of HomeScreen
, PortraitScreen
, LandscapeScreen
, and RotatingScreen
, because there’s no code related to the screen orientation. Everything is localized in _onGenerateRoute
function. You can find the full demo app in GitHub repository.
My solution is not perfect because the screen rotation starts at the same time as route transition, which may lead to some undesired effects, but it’s good enough and worked for my app. I hope it will help some of you too. If you found a better way to implement a per-screen orientation control in Flutter, I’ll be happy to know it!