Handle your Android app orientation changes in semi-automatic
Tags: android, kotlin, mobile
Published: 2020-05-15 01:53:17

**TLDR**: Using the power of a shared ViewModel + LiveData (and some debugging to see what is happening) you could listen to orientation changes in any activity/fragment by overriding the _onConfigurationChanged_ function and observing the new Configuration values.

When developing an Android application, most teams point to a portrait-only experience, which is good if it fills your needs, but it often limits the user experience and will result of users uninstalling the app.

Today, I will show you how to manage configuration changes in a reactive way and from both sides: the sensor and from a user interaction.

The example comes from a pet project I have been working: An app to see Twitch clips from a streamer and download them.
First, let’s see why I needed to listen to orientation changes:
1.	Change the orientation if the user pressed a button.
2.	Update the player’s UI when the orientation changed.
3.	If leaving the player, the user’s orientation must remain the same it had previously.

The 1st point was easy to do: The Android API provides a function to change the orientation:
    
```
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
```
    
And that is. The screen orientation changes, and the app will be in landscape mode.

The second, however, was trickier since I needed a variable that tell me the current orientation and update the UI in base of that.
 
My first approach was to have a variable attached to my fragment. It failed because the user can enter either on landscape or in portrait. So, at start, I needed to know the current configuration.

I started to dig into the ActivityInfo code to see how many orientations are. Turns out there are **17 different orientations**. Four of them contains the name _PORTRAIT_, so we will start from that. Let’s define an extension function that check is the current activity is in portrait:

```
fun Activity.isInPortrait() = requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT ||
        requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT ||
        requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT ||
        requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
```

However, I was missing one definition that is the default orientation when you have not locked your screen: _UNSPECIFIED_. This is the default orientation of activities if in the manifest we define the _configChanges_ value and add the _orientation_ specification.

This specify the system that we will manually take care of configuration changes. I am using ViewModels and Room so there is no problem with doing it by myself.
On a side note, this also let’s ExoPlayer to behave like in Youtube app: The video does not stop if your orientation changes.

Now, our function we look like this:

```
fun Activity.isInPortrait() = requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT ||
        requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT ||
        requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT ||
        requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT ||
        requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED //The new orientation
```

Now the UI updates properly if we enter on PORTRAIT, LANDSCAPE and if an user triggers the full screen mode. 

But we still lack a way of knowing when the configuration changes when the devices is rotated with the sensor. 

After digging through StackOverflow and Github, I knew about the _onConfigurationChanged_ method. It’s an overridable method of the Activities that receives a Configuration object as a parameter. The configuration object comes with a orientation variable that is an interger with 3 possible values:
 
_UNSPECIFIED_, _LANDSCAPE_, _PORTRAIT_. This is exactly what I need! 

I define a simple ViewModel which would update the Configuration object with MutableLiveData. The ViewModel will be created lazily on the activity, and be shared across fragments inside that activity:

```
class OrientationViewModel: BaseViewModel() {

    private val _configData = MutableLiveData<Configuration>()
    val configData = _configData as LiveData<Configuration>

    fun changeConfiguration(configuration: Configuration?){
        _configData.value = configuration
        Timber.d("New configuration. Orientation: ${configuration?.orientation}")
    }

}
```

Override the _onConfigurationChanged_ method on my HostActivity:

```
    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        orientationViewModel.changeConfiguration(newConfig)
    }
```

Then, on my fragment that holds the Player, observe the changes:

```
orientationViewModel.configData.observe(viewLifecycleOwner, Observer { config ->
            if(config != null){
                updatePlayerView(fullScreenBtn, config.orientation)
                updateWindowMode(config.orientation)
            }
        })
```

Done! If I set a new orientation with activity?.requestOrientation or the user use the sensor to change to landscape/portrait, the activity will handle the UI changes.

As for the last problem, a simple line inside the Fragment's _onDetach_ function will leave the user in their default orientation:

```
requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
```

That’s it! You could check the full project in Github:

{% github eagskunst/VideoWorld no-readme %}

If you are interested on how to make a custom ExoPlayer controls view, how to download files using WorkManager, building recycler views with Epoxy or anything related to the project, like this article and leave a comment with what you want see next!