DEV Community

loading...
Cover image for Re-gaining orientation #2

Re-gaining orientation #2

tkuenneth profile image Thomas Kuenneth ・3 min read

Welcome to the second part of my series about screen orientation in Jetpack Compose. Please recall that at the end of the first installment I presented the following code snippet to you:

override fun onBackPressed() {
  if (isTwoColumnMode)
    super.onBackPressed()
  else {
    if (module.value == null)
      super.onBackPressed()
    else
      module.value = null
  }
}
Enter fullscreen mode Exit fullscreen mode

Its purpose is to do whatever the framework wishes to do if the app is in two-column mode, but to just remove a currently visible composable if the app is not. Two-column mode in my example meant:

  • show a list of modules on the left hand side of the screen
  • show a module (a composable) in the bigger remaining part of the screen to the right of the list

If we only have one column, the app either shows

  • a list of modules
  • a module

Once you click on a list item, the list (composable) is replaced by the module (composable). If the user is done working with the module, the list needs to re-appear.

Now, although the app works as intended, I wrote:

But this looks weird. This looks hacky.

It does.

Since the first Android version the back stack nicely represented a flow of activities. With Honeycomb the framework learned about fragments. And they were nicely integrated in the back stack, too (through fragment transactions).

With Jetpack Compose we are encouraged to no longer use fragments. This makes our lives easier, because fragments have a lifecycle similar to activities (which may at times feel complex and hard to grasp). On the other hand, because fragments have a lifecycle, you can react to Hey fragment, you have been attached to an activity. or Hey fragment, you will be removed shortly, so get your stuff done. This comes in handy for showing or hiding parts of the user interface, or, navigation.

Composables are just ui building blocks. They should be kept as simple as possible. The less logic they have the better. So they certainly follow no lifecycle similar to activities or fragments. If a composable is to be shown on screen is declared by the developer. This works well in many cases, but what about navigation-like changes?

Jetpack Navigation contains the artifact navigation-compose which is at the time of writing 1.0.0-alpha09. To use it in your app just add

implementation "androidx.navigation:navigation-compose:1.0.0-alpha09"
Enter fullscreen mode Exit fullscreen mode

to your build.gradle file.

You can then navigate between composables while taking advantage of the Navigation component’s infrastructure and features. The docs say that NavController...

is the central API for the Navigation component. It is
stateful and keeps track of the back stack of composables
that make up the screens in your app and the state of each
screen.

To create one, invoke rememberNavController().

val navController = rememberNavController()
Enter fullscreen mode Exit fullscreen mode

The docs continue:

You should create the NavController in the place in your
composable hierarchy where all composables that need to
reference it have access to it.

So, what does this mean? In a typical two column situation there is no need to navigate. Please recall that my example from the first installment needed the hack regarding onBackPressed() only in portrait mode. Consequently I use Compose Navigation only there. In fact, we just need to change one composable:

@Composable
fun Portrait(module: MutableState<Module?>) {
  val navController = rememberNavController()
  NavHost(navController, startDestination = "moduleSelection") {
    composable("moduleSelection") {
      ModuleSelectionList(
        modules,
        callback = {
          module.value = it
          navController.navigate("module")
        }
      )
    }
    composable("module") {
      module.value?.let {
        Module(it)
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Each NavController must be associated with a single
NavHost composable. The NavHost links the
NavController with a navigation graph that specifies the
composable destinations that you should be able to navigate
between. As you navigate between composables, the content
of the NavHost is automatically recomposed.

As you can see, I need just two routes, moduleSelection (which is also the initial destination) and module. The module to be shown is passed to my Portrait() composable as a MutableState<Module?>, so it can be easily modified. All other parts, including the distinction between portrait and landscape, remain unchanged. Just remember how simple this is:

isTwoColumnMode = (LocalConfiguration.current.orientation
    == Configuration.ORIENTATION_LANDSCAPE)
if (isTwoColumnMode)
  Landscape(module)
else
  Portrait(module)
Enter fullscreen mode Exit fullscreen mode

That's all for today. Please stay tuned for the third installment where I will be focussing an optimizing the screen layout for foldables.


Source

Discussion (0)

pic
Editor guide