Foldable navigation

The foldable navigation component is a library built on top of the Android navigation component. It helps developers to implement fragment navigation for different screen modes or to adapt an existing application to Foldable Navigation pattern;

The foldable navigation component consists of three key parts:

  • Navigation Graph - An XML resource that contains all navigation-related information in one centralized location. It is the same as the one from the navigation component provided by Google.
  • FoldableNavHost - An empty container that displays destinations from your navigation graph. The implementation for the foldable navigation is FoldableNavHostFragment.
  • FoldableNavController - An object that manages app navigation within a FoldableNavHost.

Overview

Applications on dual-screen and foldable devices can be displayed on a single screen or spanned across a folding feature. When the application is first started:

  • In single-screen only one fragment (A) will be visible.
  • If the application is rendered across a folding feature, the first fragment (A) will be on the first side of the fold and the other side of the fold will be blank.

From the initial state, if you navigate to another fragment (B) then the new fragment will be opened on the end screen.

If the user navigates to a third fragment (C) it will be shown on the end screen, the previous fragment (B) will be moved on the start screen..

  • When the app is moved from being spanned to a single screen, then all fragments from the end screen are moved to the start screen and (C) will appear on top.
  • When the app is moved from a single screen to spanned across a fold or hinge, and the navigation stack has more than two fragments, then the last fragment will be moved to the end screen.

Six different dual-screen examples demonstrating how fragments A, B, and C would appear after different navigation steps

Change display area destination for actions

You can specify where a new fragment will be displayed using the launchScreen attribute in the navigation graph. Possible values for launchScreen are:

  • start - the fragment will be opened on the first screen
  • end - the fragment will be opened on the second screen
  • both - the fragment will cover the entire display area

This navigation XML example shows how to use this attribute:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/home"
    app:startDestination="@+id/titleScreen">

    <fragment
        android:id="@+id/titleScreen"
        android:name="com.microsoft.device.dualscreen.navigation.sample.homescreen.TitleFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_title">
        <action
            android:id="@+id/action_title_to_about"
            app:launchScreen="end"
            app:destination="@id/aboutScreen"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_right"
            app:popExitAnim="@anim/slide_out_left" />
    </fragment>
</navigation>

Important

This attribute can only be changed by editing the XML file directly. It cannot be modified using the Android Studio Editor.

Sample

You can download this navigation sample app to see all these behaviors.

How to import the library into your project

  1. Add the the dependency to the module-level build.gradle file:

    dependencies {
       def nav_version = "1.0.0-alpha3"
       implementation "com.microsoft.device.dualscreen:navigation-fragment-ktx:$nav_version"
       implementation "com.microsoft.device.dualscreen:navigation-ui-ktx:$nav_version"
    }
    

  1. If your project is created using Java, you will need to add a kotlin-stdlib dependency to your module-level build.gradle file. (This is because some part of the library was created using Kotlin.)

    dependencies {
       implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    }
    

These components are built on top of the Navigation component provided by Google and thus the Foldable-Navigation library contains a dependency to that.

Create a navigation graph

A navigation graph is an xml resource file with all of your app's navigation paths, using destinations and actions. The navigation graph can be created via Android Studio Navigation Editor or manually via an XML editor. You can find more info at Create a navigation graph.

Add a NavHost to an activity

The foldable navigation component is designed for apps with one main activity and multiple fragment destinations. The main activity is associated with a navigation graph and will contain a FoldableNavHostFragment that is responsible for swapping fragment destinations. If your app will have more than one activity, then each activity will have its own navigation graph.

This is an example main activity XML layout file, showing how to set the app:navGraph attribute:

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/surface_duo_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.FoldableNavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

The FoldableNavHost can also be set programmatically:

val navHostFragment = FoldableNavHostFragment.create(navGraphId)
fragmentManager.beginTransaction()
    .add(containerId, navHostFragment, fragmentTag)
    .commitNow()

You can learn more about how to add the FoldableNavHost at Add a NavHost to an activity.

This code snippet can be used to navigate fragments according to the foldable navigation rules:

class SomeFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        view.findViewById<Button>(R.id.btn_next).setOnClickListener {
            findFoldableNavController().navigate(R.id.action_next)
        }
    }
}

Update UI components with FoldableNavigationUI

The FoldableNavigationUI is a similar component like NavigationUI in the Jetpack Navigation component and contains static methods that manage navigation with the top app bar, the navigation drawer, and bottom navigation. You can find more information about

FoldableNavigationUI contains the following methods similar to those provided by NavigationUI:

// same method name, with foldable parameter
boolean onNavDestinationSelected(MenuItem item, FoldableNavController navController)
boolean navigateUp(FoldableNavController navController, Openable openableLayout)
boolean navigateUp(FoldableNavController navController, FoldableAppBarConfiguration configuration)
// method name changed to reflect foldable navigation
void setupActionBarWithFoldableNavController(AppCompatActivity activity, FoldableNavController navController)
void setupActionBarWithFoldableNavController(AppCompatActivity activity, FoldableNavController navController, Openable openableLayout)
void setupActionBarWithFoldableNavController(AppCompatActivity activity, FoldableNavController navController, FoldableAppBarConfiguration configuration)
void setupWithFoldableNavController(Toolbar toolbar, FoldableNavController navController)
void setupWithFoldableNavController(Toolbar toolbar, FoldableNavController navController, Openable openableLayout)
void setupWithFoldableNavController(Toolbar toolbar, FoldableNavController navController, FoldableAppBarConfiguration configuration)
void setupWithFoldableNavController(CollapsingToolbarLayout collapsingToolbarLayout, Toolbar toolbar, FoldableNavController navController)
void setupWithFoldableNavController(CollapsingToolbarLayout collapsingToolbarLayout, Toolbar toolbar, FoldableNavController navController, Openable openableLayout)
void setupWithFoldableNavController(CollapsingToolbarLayout collapsingToolbarLayout, Toolbar toolbar, FoldableNavController navController, FoldableAppBarConfiguration configuration)
void setupWithFoldableNavController(NavigationView navigationView, FoldableNavController navController)
void setupWithFoldableNavController(BottomNavigationView bottomNavigationView, FoldableNavController navController)

Migrate existing applications to foldable navigation

Existing applications that use the Navigation component provided by Google can add foldable functionality by following these steps:

  1. Use FoldableNavHostFragment instead of NavHostFragment in the fragment container view by changing

    <androidx.fragment.app.FragmentContainerView
         android:id="@+id/nav_host_fragment"
         android:name="androidx.navigation.NavHostFragment"
    

    to

    <androidx.fragment.app.FragmentContainerView
         android:id="@+id/nav_host_fragment"
         android:name="androidx.navigation.FoldableNavHostFragment"
    
  2. Use findFoldableNavController in order to get the instance for FoldableNavController and use it to navigate inside navigation graph by changing

    findNavController().navigate(R.id.action_next)
    

    to

    findFoldableNavController().navigate(R.id.action_next)
    
  3. Use FoldableNavigationUI instead of NavigationUI, by changing

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    val appBarConfiguration = AppBarConfiguration(navController.graph)
    setupActionBarWithNavController(navController, appBarConfiguration)
    

    to

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as FoldableNavHostFragment
    val navController = navHostFragment.navController
    val appBarConfiguration = FoldableAppBarConfiguration(navController.graph)
    setupActionBarWithFoldableNavController(navController, appBarConfiguration)