Tag Archives: Jetpack

Improving inter-activity communication with Jetpack ActivityResult

Posted by Yacine Rezgui, Developer Advocate

Whether you're requesting a permission, selecting a file from the system file manager, or expecting data from a 3rd party app, passing data between activities is a core element in inter-process communication on Android. We’ve recently released the new ActivityResult APIs to help handle these activity results.

Previously, to get results from started activities, apps needed to implement an onActivityResult() method in their activities and fragments, check which requestCode a result is referring to, verify that the requestCode is OK, and finally inspect its result data or extended data.

This leads to complicated code, and it doesn’t provide a type-safe interface for expected arguments when sending or receiving data from an activity.

What are the ActivityResult APIs?

The ActivityResult APIs were added to the Jetpack activity and fragment libraries, making it easier to get results from activities by providing type-safe contracts. These contracts define expected input and result types for common actions like taking a picture or requesting a permission, while also providing a way to create your own contracts.

The ActivityResult APIs provide components for registering for an activity result, launching a request, and handling its result once it is returned by the system. You can also receive the activity result in a separate class from where the activity is launched and still rely on the type-safe contracts.

How to use it

To demonstrate how to use the ActivityResult APIs, let’s go over an example where we’re opening a document.

First, you need to add the following dependencies to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.activity:activity:1.2.0-alpha02"
  implementation "androidx.activity:fragment:1.3.0-alpha02"
}

You need to register a callback along with the contract that defines its input and output types.

In this context, GetContent() refers to the ACTION_GET_DOCUMENT intent, and is one of the default contracts already defined in the Activity library. You can find the complete list of contracts here.

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Now we need to launch our activity using the returned launcher. As you can set a mime type filter when listing the selectable files, GetContent.launch() will accept a string as a parameter:

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you'd like to allow the user to select
        // as the input
        getContent.launch("image/*")
    }
}

Once an image has been selected and you return to your activity, your registered callback will be executed with the expected results. As you saw through the code snippets, ActivityResult brings an easier developer experience when dealing with results from activities.

Start using Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 for a type-safe way to handle your intent results with the new ActivityResult APIs.

Let us know what you think and how we can make it better by providing feedback on the issue tracker.

Improving inter-activity communication with Jetpack ActivityResult

Posted by Yacine Rezgui, Developer Advocate

Whether you're requesting a permission, selecting a file from the system file manager, or expecting data from a 3rd party app, passing data between activities is a core element in inter-process communication on Android. We’ve recently released the new ActivityResult APIs to help handle these activity results.

Previously, to get results from started activities, apps needed to implement an onActivityResult() method in their activities and fragments, check which requestCode a result is referring to, verify that the requestCode is OK, and finally inspect its result data or extended data.

This leads to complicated code, and it doesn’t provide a type-safe interface for expected arguments when sending or receiving data from an activity.

What are the ActivityResult APIs?

The ActivityResult APIs were added to the Jetpack activity and fragment libraries, making it easier to get results from activities by providing type-safe contracts. These contracts define expected input and result types for common actions like taking a picture or requesting a permission, while also providing a way to create your own contracts.

The ActivityResult APIs provide components for registering for an activity result, launching a request, and handling its result once it is returned by the system. You can also receive the activity result in a separate class from where the activity is launched and still rely on the type-safe contracts.

How to use it

To demonstrate how to use the ActivityResult APIs, let’s go over an example where we’re opening a document.

First, you need to add the following dependencies to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.activity:activity:1.2.0-alpha02"
  implementation "androidx.activity:fragment:1.3.0-alpha02"
}

You need to register a callback along with the contract that defines its input and output types.

In this context, GetContent() refers to the ACTION_GET_DOCUMENT intent, and is one of the default contracts already defined in the Activity library. You can find the complete list of contracts here.

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Now we need to launch our activity using the returned launcher. As you can set a mime type filter when listing the selectable files, GetContent.launch() will accept a string as a parameter:

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you'd like to allow the user to select
        // as the input
        getContent.launch("image/*")
    }
}

Once an image has been selected and you return to your activity, your registered callback will be executed with the expected results. As you saw through the code snippets, ActivityResult brings an easier developer experience when dealing with results from activities.

Start using Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 for a type-safe way to handle your intent results with the new ActivityResult APIs.

Let us know what you think and how we can make it better by providing feedback on the issue tracker.

Improving inter-activity communication with Jetpack ActivityResult

Posted by Yacine Rezgui, Developer Advocate

Whether you're requesting a permission, selecting a file from the system file manager, or expecting data from a 3rd party app, passing data between activities is a core element in inter-process communication on Android. We’ve recently released the new ActivityResult APIs to help handle these activity results.

Previously, to get results from started activities, apps needed to implement an onActivityResult() method in their activities and fragments, check which requestCode a result is referring to, verify that the requestCode is OK, and finally inspect its result data or extended data.

This leads to complicated code, and it doesn’t provide a type-safe interface for expected arguments when sending or receiving data from an activity.

What are the ActivityResult APIs?

The ActivityResult APIs were added to the Jetpack activity and fragment libraries, making it easier to get results from activities by providing type-safe contracts. These contracts define expected input and result types for common actions like taking a picture or requesting a permission, while also providing a way to create your own contracts.

The ActivityResult APIs provide components for registering for an activity result, launching a request, and handling its result once it is returned by the system. You can also receive the activity result in a separate class from where the activity is launched and still rely on the type-safe contracts.

How to use it

To demonstrate how to use the ActivityResult APIs, let’s go over an example where we’re opening a document.

First, you need to add the following dependencies to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.activity:activity:1.2.0-alpha02"
  implementation "androidx.activity:fragment:1.3.0-alpha02"
}

You need to register a callback along with the contract that defines its input and output types.

In this context, GetContent() refers to the ACTION_GET_DOCUMENT intent, and is one of the default contracts already defined in the Activity library. You can find the complete list of contracts here.

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Now we need to launch our activity using the returned launcher. As you can set a mime type filter when listing the selectable files, GetContent.launch() will accept a string as a parameter:

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you'd like to allow the user to select
        // as the input
        getContent.launch("image/*")
    }
}

Once an image has been selected and you return to your activity, your registered callback will be executed with the expected results. As you saw through the code snippets, ActivityResult brings an easier developer experience when dealing with results from activities.

Start using Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 for a type-safe way to handle your intent results with the new ActivityResult APIs.

Let us know what you think and how we can make it better by providing feedback on the issue tracker.

Improving inter-activity communication with Jetpack ActivityResult

Posted by Yacine Rezgui, Developer Advocate

Whether you're requesting a permission, selecting a file from the system file manager, or expecting data from a 3rd party app, passing data between activities is a core element in inter-process communication on Android. We’ve recently released the new ActivityResult APIs to help handle these activity results.

Previously, to get results from started activities, apps needed to implement an onActivityResult() method in their activities and fragments, check which requestCode a result is referring to, verify that the requestCode is OK, and finally inspect its result data or extended data.

This leads to complicated code, and it doesn’t provide a type-safe interface for expected arguments when sending or receiving data from an activity.

What are the ActivityResult APIs?

The ActivityResult APIs were added to the Jetpack activity and fragment libraries, making it easier to get results from activities by providing type-safe contracts. These contracts define expected input and result types for common actions like taking a picture or requesting a permission, while also providing a way to create your own contracts.

The ActivityResult APIs provide components for registering for an activity result, launching a request, and handling its result once it is returned by the system. You can also receive the activity result in a separate class from where the activity is launched and still rely on the type-safe contracts.

How to use it

To demonstrate how to use the ActivityResult APIs, let’s go over an example where we’re opening a document.

First, you need to add the following dependencies to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.activity:activity:1.2.0-alpha02"
  implementation "androidx.activity:fragment:1.3.0-alpha02"
}

You need to register a callback along with the contract that defines its input and output types.

In this context, GetContent() refers to the ACTION_GET_DOCUMENT intent, and is one of the default contracts already defined in the Activity library. You can find the complete list of contracts here.

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

Now we need to launch our activity using the returned launcher. As you can set a mime type filter when listing the selectable files, GetContent.launch() will accept a string as a parameter:

val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you'd like to allow the user to select
        // as the input
        getContent.launch("image/*")
    }
}

Once an image has been selected and you return to your activity, your registered callback will be executed with the expected results. As you saw through the code snippets, ActivityResult brings an easier developer experience when dealing with results from activities.

Start using Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 for a type-safe way to handle your intent results with the new ActivityResult APIs.

Let us know what you think and how we can make it better by providing feedback on the issue tracker.

Decrease startup time with Jetpack App Startup

Posted by Yacine Rezgui, Developer Advocate and Rahul Ravikumar, Software Engineer

Jetpack image

Application startup time is a critical metric for any application. Users expect apps to be responsive and fast to load. When an application does not meet this expectation, it can be disappointing to users. This poor experience may cause a user to rate your app badly on the Play store, or even abandon your app altogether.

Jetpack App Startup is a library that provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use App Startup to streamline startup sequences and explicitly set the order of initialization.

Apps and libraries often rely on having components (WorkManager, ProcessLifecycleObserver, FirebaseApp etc.) initialized before Application.onCreate(). This is usually achieved by using content providers to initialize each dependency. Instead of defining separate content providers for each component that needs to be initialized, App Startup lets you define initializers that share a single content provider. This significantly improves app startup time, usually by ~2ms per content provider. App Startup also helps you further improve startup performance by making it really easy to initialize components lazily. When App Startup goes stable, we will be updating our libraries like `WorkManager` and `ProcessLifecycle` to benefit from this as well.

App Startup supports API level 14 and above.

How to use it

Gradle setup

To use App Startup in your library or app, add the following dependency to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.startup:startup-runtime:1.0.0-alpha02"
}
Define an Initializer

To be able to use App Startup in your application, you need to define an Initializer. This is where you define how to initialize and specify your dependencies. Here’s the interface you need to implement:

interface Initializer<out T: Any> {
    fun create(context: Context): T
    fun dependencies(): List<Class<out Initializer<*>>>
}

As a practical example, here’s what an Initializer that initializes WorkManager might look like:

class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val configuration = Configuration.Builder()
            .setMinimumLoggingLevel(Log.DEBUG)
            .build()

        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }
   
    // This component does not have any dependencies
    override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}

Note: This example is purely illustrative. This Initializer should actually be defined by the WorkManager library.

Lastly, we need to add an entry for WorkManagerInitializer in the AndroidManifest.xml:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- This entry makes WorkManagerInitializer discoverable. -->
    <meta-data android:name="com.example.WorkManagerInitializer"
          android:value="androidx.startup" />
</provider>

How it works

App Startup uses a single content provider called InitializationProvider. This content provider discovers initializers by introspecting the <meta-data> entries in the merged AndroidManifest.xml file. This happens before Application.onCreate().

After the discovery phase, it subsequently initializes a component after having initialized all its dependencies. Therefore, a component is only initialized after all its dependencies have been initialized.

Lazy initialization

We highly recommend using lazy initialization to further improve startup performance. To make initialization of a component lazy, you need to do the following:

Add a tools:node="remove" attribute to the <meta-data> entry for the Initializer. This disables eager initialization.

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- disables eager initialization -->
    <meta-data android:name="com.example.WorkManagerInitializer"
              tools:node="remove" />
</provider>

To lazily initialize WorkManagerInitializer you can then use:

// This returns an instance of WorkManager
AppInitializer.getInstance(context)
    .initializeComponent(WorkManagerInitializer.class);

Your app now initializes the component lazily. For more information, please read our detailed documentation here.

Final thoughts

App Startup is currently in alpha-02. Find out more about how to use it from our documentation. Once you try it out, help us make it better by giving us feedback on the issue tracker.

Decrease startup time with Jetpack App Startup

Posted by Yacine Rezgui, Developer Advocate and Rahul Ravikumar, Software Engineer

Jetpack image

Application startup time is a critical metric for any application. Users expect apps to be responsive and fast to load. When an application does not meet this expectation, it can be disappointing to users. This poor experience may cause a user to rate your app badly on the Play store, or even abandon your app altogether.

Jetpack App Startup is a library that provides a straightforward, performant way to initialize components at application startup. Both library developers and app developers can use App Startup to streamline startup sequences and explicitly set the order of initialization.

Apps and libraries often rely on having components (WorkManager, ProcessLifecycleObserver, FirebaseApp etc.) initialized before Application.onCreate(). This is usually achieved by using content providers to initialize each dependency. Instead of defining separate content providers for each component that needs to be initialized, App Startup lets you define initializers that share a single content provider. This significantly improves app startup time, usually by ~2ms per content provider. App Startup also helps you further improve startup performance by making it really easy to initialize components lazily. When App Startup goes stable, we will be updating our libraries like `WorkManager` and `ProcessLifecycle` to benefit from this as well.

App Startup supports API level 14 and above.

How to use it

Gradle setup

To use App Startup in your library or app, add the following dependency to your gradle file:

repositories {
    google()
    maven()
}

dependencies {
  implementation "androidx.startup:startup-runtime:1.0.0-alpha02"
}
Define an Initializer

To be able to use App Startup in your application, you need to define an Initializer. This is where you define how to initialize and specify your dependencies. Here’s the interface you need to implement:

interface Initializer<out T: Any> {
    fun create(context: Context): T
    fun dependencies(): List<Class<out Initializer<*>>>
}

As a practical example, here’s what an Initializer that initializes WorkManager might look like:

class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        val configuration = Configuration.Builder()
            .setMinimumLoggingLevel(Log.DEBUG)
            .build()

        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }
   
    // This component does not have any dependencies
    override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}

Note: This example is purely illustrative. This Initializer should actually be defined by the WorkManager library.

Lastly, we need to add an entry for WorkManagerInitializer in the AndroidManifest.xml:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- This entry makes WorkManagerInitializer discoverable. -->
    <meta-data android:name="com.example.WorkManagerInitializer"
          android:value="androidx.startup" />
</provider>

How it works

App Startup uses a single content provider called InitializationProvider. This content provider discovers initializers by introspecting the <meta-data> entries in the merged AndroidManifest.xml file. This happens before Application.onCreate().

After the discovery phase, it subsequently initializes a component after having initialized all its dependencies. Therefore, a component is only initialized after all its dependencies have been initialized.

Lazy initialization

We highly recommend using lazy initialization to further improve startup performance. To make initialization of a component lazy, you need to do the following:

Add a tools:node="remove" attribute to the <meta-data> entry for the Initializer. This disables eager initialization.

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- disables eager initialization -->
    <meta-data android:name="com.example.WorkManagerInitializer"
              tools:node="remove" />
</provider>

To lazily initialize WorkManagerInitializer you can then use:

// This returns an instance of WorkManager
AppInitializer.getInstance(context)
    .initializeComponent(WorkManagerInitializer.class);

Your app now initializes the component lazily. For more information, please read our detailed documentation here.

Final thoughts

App Startup is currently in alpha-02. Find out more about how to use it from our documentation. Once you try it out, help us make it better by giving us feedback on the issue tracker.

What’s New in Navigation 2020

Posted by Jeremy Woods, Software Engineer, Android UI Toolkit

Navigation image

The latest versions of the Jetpack Navigation library (2.2.0 and 2.3.0) added a lot of requested features and functionality, including dynamic navigation, navigation back stack entries, a library for navigation testing, additional features for deep linking, and more. Let’s go over the most important changes, see what problems they solve, and learn how to use them!

Dynamic Navigation

We’ve updated Navigation to simplify adding dynamic feature modules for your application.

Previously, implementing navigation between destinations defined in dynamic feature modules required a lot of work. Before you could navigate to the first dynamic destination, you needed to add the Play Core library and the Split Install API to your app. You also needed to check for and download the dynamic module. Once downloaded, you could then finally navigate to the destination. On top of this, if you wanted to have an on-screen progress bar for the module being downloaded, you needed to implement a SplitInstallManager listener.

To address this complexity, we created the Dynamic Navigator library. This library extends the functionality of the Jetpack Navigation library to provide seamless installation of on-demand dynamic feature modules when navigating. The library handles all Play Store interaction for you, and it even includes a progress screen that provides the download status of your dynamic module.

The default UI for showing a progress bar when a user navigates to a dynamic feature for the first time.

The default UI for showing a progress bar when a user navigates to a dynamic feature for the first time. The app displays this screen as the corresponding module downloads

To use dynamic navigation, all you need to do is:

  1. Change instances of NavHostFragment to DynamicNavHostFragment
  2. Add an app:moduleName attribute to the destinations associated with a DynamicNavHostFragment

For more information on dynamic navigation, see Navigate with dynamic feature modules and check out the samples.

NavBackStackEntry: Unlocked

When you navigate from one destination to the next, the previous destination and its latest state is placed on the Navigation back stack. If you return to the previous destination by using navController.popBackBack(), the top back stack entry is removed from the back stack with its state still intact and the NavDestination is restored. The Navigation back stack contains all of the previous destinations that were needed to arrive at the current NavDestination.

We manage the destinations on the Navigation back stack by encapsulating them into the NavBackStackEntry class. NavBackStackEntry is now public. This means that users can go a level deeper than just NavDestinations and gain access to navigation-specific ViewModels, Lifecycles, and SavedStateRegistries. You can now properly scope data sharing or ensure it is destroyed at the appropriate time.

See Navigation and the back stack for more information.

NavGraph ViewModels

Since a NavBackStackEntry is a ViewModelProvider, you can create a ViewModel to share data between destinations at the NavGraph level. Each parent navigation graph of all NavDestinations are on the back stack, so your view model can be scoped appropriately:

val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph)

For more information on navGraph scoped view models, see Share UI-related data between destinations with ViewModel

Returning a Result from a destination

By combining ViewModel and Lifecycle, you can share data between two specific destinations. To do this, NavBackStackEntry provides a SavedStateHandle, a key-value map that can be used to store and retrieve data, even across configuration changes. By using the given SavedStateHandle, you can access and pass data between destinations. For example to pass data from destination A to destination B:

In destination A:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val navController = findNavController();
    // We use a String here, but any type that can be put in a Bundle is supported
    navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(
        viewLifecycleOwner) { result ->
        // Do something with the result.
    }
}

And in destination B:

navController.previousBackStackEntry?.savedStateHandle?.set("key", result)

See Returning a result to the previous Destination for more details.

Testing your Navigation Flow

Previously, the recommended testing solution for Navigation was Mockito. You would create a mock NavController and verify that navigate() was called at the appropriate time with the correct parameters. Unfortunately, this solution was not enough to test certain areas of Navigation flow, such as ViewModel interaction or the Navigation back stack. The Navigation Library now offers a well-integrated solution for these areas with the Navigation Testing library.

The Navigation Testing library adds TestNavHostController, which gives access to the Navigation back stack in a test environment. This means that you can now verify the state of the entire back stack. When using the TestNavHostController, you can set your own LifecycleOwner, ViewModelStoreOwner, and OnBackPressDispatcher by using the APIs given by NavHostController. By setting these components, you can test them in the context of navigation.

For example, here's how to test a destination that uses a nav graph-scoped ViewModel:

val navController = TestNavHostController(ApplicationProvider.getApplicationContext())

// This allows fragments to use by navGraphViewModels()
navController.setViewModelStore(ViewModelStore())
navController.setGraph(R.navigation.main_nav)

The TestNavHostController also lets you set the current destination. You can move the test directly to the use case being tested without the need to set it up using navigate() calls. This is extremely convenient for writing tests for different navigation scenarios.

When setting the current destination, you might do something like the following:

val navController = TestNavHostController(ApplicationProvider.getApplicationContext())

navController.setGraph(R.navigation.main_nav)
navController.setCurrentDestination(R.id.destination_1)

Remember that when setting the current destination, that destination must be part of your nav graph.

For more information about TestNavHostController, see the Test Navigation docs.

Nav Deep Linking

Deep linking allows you to navigate directly to any destination no matter where you currently are in the NavGraph. This can be very useful for launching your app to a specific destination or jumping between destinations that would otherwise be inaccessible to one another.

When navigating using a deep link, you can now provide deep link query parameters in any order and even leave them out altogether if they have been given a default value or have been made nullable. This means that if you have provided default values for all of the query parameters on a deep link, the deep link can match a URL pattern without including any query parameters.

For example, www.example.com?arg1={arg1}&arg2={arg2} will now match with www.example.com as long as arg1 and arg2 have default values and/or are nullable.

Deep links can also be matched using intent actions and MIME types. Instead of requiring destinations to match by URI, you can provide the deep link with an action or MIME type and match with that instead. You can specify multiple match types for a single deep link, but note that URI argument matching is prioritized first, followed by action, and then mimeType.

You create a deep link by adding it to a destination in XML, using the Kotlin DSL, or by using the Navigation Editor in Android Studio.

Here's how to add a deep link to a destination using XML:

<fragment android:id="@+id/a"
          android:name="com.example.myapplication.FragmentA"
          tools:layout="@layout/a">
        <deeplink app:url="www.example.com"
                app:action="android.intent.action.MY_ACTION"
                app:mimeType="type/subtype"/>
    </fragment>

Here's how to add the same deep link using the Kotlin DSL:

val baseUri = "http://www.example.com/"

fragment<MyFragment>(nav_graph.dest.a) {
   deepLink(navDeepLink {
    uriPattern = "${baseUri}"
    action = "android.intent.action.MY_ACTION"
    mimeType = "type/subtype"
   })
}

You can also add the same deep link using the Navigation Editor in Android Studio versions 4.1 and higher. Note that you must also be using the Navigation 2.3.0-alpha06 dependency or later.

An open dialog in the Navigation Editor for adding a deep link to a destination. There are options to add an URI, a MIME type, and an action, along with a checkBox to Auto Verify

Adding a deep link to a destination in the Navigation Editor

To navigate to a destination using a deep link, you must first build a NavDeepLinkRequest and then pass that deep link request into the Navigation controller's call to navigate():

val deepLinkRequest = NavDeepLinkRequest.Builder
        .fromUri(Uri.parse("http://www.example.com"))
        .setAction("android.intent.action.MY_ACTION")
        .setMimeType("type/subtype")
        .build()
navController.navigate(deeplinkRequest)

For more information on deep links, visit Create a deep link for a destination, as well as the deep linking sections in Navigate to a destination and Kotlin DSL.

Navigation Editor

Android Studio 4.0 includes new features for the Navigation Editor. You can now edit your destinations using a split pane view. This means you can edit the XML or design and see the changes in real time.

The Navigation Editor opened in split pane mode with the navigation.xml file on the left and the corresponding nav graph on the right. The nav graph has 6 destination, and a nested graph

Viewing a navigation.xml file in split view mode

In Android Studio 4.1, the Navigation Editor introduced the component tree. This allows you to traverse the entire nav graph, freely going in and out of nested graphs.

An open component tree of a nav graph in the Navigation Editor. It starts viewing the entire graph, then moves to the title screen before going into the nested profiles graph. After cycling through the destinations in the profiles graph, it goes back to fragments in the original graph

Navigating through a graph in the Navigation Editor

Additional Changes

NavigationUI can now use any layout that uses the Openable interface. This means that it is no longer limited to DrawerLayout and allows for customization of the AppBarConfiguration. You can provide your Openable and use it as the layout instead.

Navigation also provides support for Kotlin DSL. Kotlin DSL can be used to create different destinations, actions, or deep links. For more information see the documentation for Kotlin DSL.

Wrap up

Navigation added lots of useful features over the past year. You can simplify your dynamic feature modules by taking advantage of the Dynamic Navigator library, use a NavBackStackEntry to help correctly scope your data, easily test your navigation flow using the TestNavHostController, or even match your deep link using intent actions and/or MIME types.

For more information about the Jetpack Navigation library, check out the documentation at https://developer.android.com/guide/navigation

Please provide feedback (or file bugs) using the Navigation issuetracker component.

What’s New in Navigation 2020

Posted by Jeremy Woods, Software Engineer, Android UI Toolkit

Navigation image

The latest versions of the Jetpack Navigation library (2.2.0 and 2.3.0) added a lot of requested features and functionality, including dynamic navigation, navigation back stack entries, a library for navigation testing, additional features for deep linking, and more. Let’s go over the most important changes, see what problems they solve, and learn how to use them!

Dynamic Navigation

We’ve updated Navigation to simplify adding dynamic feature modules for your application.

Previously, implementing navigation between destinations defined in dynamic feature modules required a lot of work. Before you could navigate to the first dynamic destination, you needed to add the Play Core library and the Split Install API to your app. You also needed to check for and download the dynamic module. Once downloaded, you could then finally navigate to the destination. On top of this, if you wanted to have an on-screen progress bar for the module being downloaded, you needed to implement a SplitInstallManager listener.

To address this complexity, we created the Dynamic Navigator library. This library extends the functionality of the Jetpack Navigation library to provide seamless installation of on-demand dynamic feature modules when navigating. The library handles all Play Store interaction for you, and it even includes a progress screen that provides the download status of your dynamic module.

The default UI for showing a progress bar when a user navigates to a dynamic feature for the first time.

The default UI for showing a progress bar when a user navigates to a dynamic feature for the first time. The app displays this screen as the corresponding module downloads

To use dynamic navigation, all you need to do is:

  1. Change instances of NavHostFragment to DynamicNavHostFragment
  2. Add an app:moduleName attribute to the destinations associated with a DynamicNavHostFragment

For more information on dynamic navigation, see Navigate with dynamic feature modules and check out the samples.

NavBackStackEntry: Unlocked

When you navigate from one destination to the next, the previous destination and its latest state is placed on the Navigation back stack. If you return to the previous destination by using navController.popBackBack(), the top back stack entry is removed from the back stack with its state still intact and the NavDestination is restored. The Navigation back stack contains all of the previous destinations that were needed to arrive at the current NavDestination.

We manage the destinations on the Navigation back stack by encapsulating them into the NavBackStackEntry class. NavBackStackEntry is now public. This means that users can go a level deeper than just NavDestinations and gain access to navigation-specific ViewModels, Lifecycles, and SavedStateRegistries. You can now properly scope data sharing or ensure it is destroyed at the appropriate time.

See Navigation and the back stack for more information.

NavGraph ViewModels

Since a NavBackStackEntry is a ViewModelProvider, you can create a ViewModel to share data between destinations at the NavGraph level. Each parent navigation graph of all NavDestinations are on the back stack, so your view model can be scoped appropriately:

val viewModel: MyViewModel by navGraphViewModels(R.id.my_graph)

For more information on navGraph scoped view models, see Share UI-related data between destinations with ViewModel

Returning a Result from a destination

By combining ViewModel and Lifecycle, you can share data between two specific destinations. To do this, NavBackStackEntry provides a SavedStateHandle, a key-value map that can be used to store and retrieve data, even across configuration changes. By using the given SavedStateHandle, you can access and pass data between destinations. For example to pass data from destination A to destination B:

In destination A:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val navController = findNavController();
    // We use a String here, but any type that can be put in a Bundle is supported
    navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")?.observe(
        viewLifecycleOwner) { result ->
        // Do something with the result.
    }
}

And in destination B:

navController.previousBackStackEntry?.savedStateHandle?.set("key", result)

See Returning a result to the previous Destination for more details.

Testing your Navigation Flow

Previously, the recommended testing solution for Navigation was Mockito. You would create a mock NavController and verify that navigate() was called at the appropriate time with the correct parameters. Unfortunately, this solution was not enough to test certain areas of Navigation flow, such as ViewModel interaction or the Navigation back stack. The Navigation Library now offers a well-integrated solution for these areas with the Navigation Testing library.

The Navigation Testing library adds TestNavHostController, which gives access to the Navigation back stack in a test environment. This means that you can now verify the state of the entire back stack. When using the TestNavHostController, you can set your own LifecycleOwner, ViewModelStoreOwner, and OnBackPressDispatcher by using the APIs given by NavHostController. By setting these components, you can test them in the context of navigation.

For example, here's how to test a destination that uses a nav graph-scoped ViewModel:

val navController = TestNavHostController(ApplicationProvider.getApplicationContext())

// This allows fragments to use by navGraphViewModels()
navController.setViewModelStore(ViewModelStore())
navController.setGraph(R.navigation.main_nav)

The TestNavHostController also lets you set the current destination. You can move the test directly to the use case being tested without the need to set it up using navigate() calls. This is extremely convenient for writing tests for different navigation scenarios.

When setting the current destination, you might do something like the following:

val navController = TestNavHostController(ApplicationProvider.getApplicationContext())

navController.setGraph(R.navigation.main_nav)
navController.setCurrentDestination(R.id.destination_1)

Remember that when setting the current destination, that destination must be part of your nav graph.

For more information about TestNavHostController, see the Test Navigation docs.

Nav Deep Linking

Deep linking allows you to navigate directly to any destination no matter where you currently are in the NavGraph. This can be very useful for launching your app to a specific destination or jumping between destinations that would otherwise be inaccessible to one another.

When navigating using a deep link, you can now provide deep link query parameters in any order and even leave them out altogether if they have been given a default value or have been made nullable. This means that if you have provided default values for all of the query parameters on a deep link, the deep link can match a URL pattern without including any query parameters.

For example, www.example.com?arg1={arg1}&arg2={arg2} will now match with www.example.com as long as arg1 and arg2 have default values and/or are nullable.

Deep links can also be matched using intent actions and MIME types. Instead of requiring destinations to match by URI, you can provide the deep link with an action or MIME type and match with that instead. You can specify multiple match types for a single deep link, but note that URI argument matching is prioritized first, followed by action, and then mimeType.

You create a deep link by adding it to a destination in XML, using the Kotlin DSL, or by using the Navigation Editor in Android Studio.

Here's how to add a deep link to a destination using XML:

<fragment android:id="@+id/a"
          android:name="com.example.myapplication.FragmentA"
          tools:layout="@layout/a">
        <deeplink app:url="www.example.com"
                app:action="android.intent.action.MY_ACTION"
                app:mimeType="type/subtype"/>
    </fragment>

Here's how to add the same deep link using the Kotlin DSL:

val baseUri = "http://www.example.com/"

fragment<MyFragment>(nav_graph.dest.a) {
   deepLink(navDeepLink {
    uriPattern = "${baseUri}"
    action = "android.intent.action.MY_ACTION"
    mimeType = "type/subtype"
   })
}

You can also add the same deep link using the Navigation Editor in Android Studio versions 4.1 and higher. Note that you must also be using the Navigation 2.3.0-alpha06 dependency or later.

An open dialog in the Navigation Editor for adding a deep link to a destination. There are options to add an URI, a MIME type, and an action, along with a checkBox to Auto Verify

Adding a deep link to a destination in the Navigation Editor

To navigate to a destination using a deep link, you must first build a NavDeepLinkRequest and then pass that deep link request into the Navigation controller's call to navigate():

val deepLinkRequest = NavDeepLinkRequest.Builder
        .fromUri(Uri.parse("http://www.example.com"))
        .setAction("android.intent.action.MY_ACTION")
        .setMimeType("type/subtype")
        .build()
navController.navigate(deeplinkRequest)

For more information on deep links, visit Create a deep link for a destination, as well as the deep linking sections in Navigate to a destination and Kotlin DSL.

Navigation Editor

Android Studio 4.0 includes new features for the Navigation Editor. You can now edit your destinations using a split pane view. This means you can edit the XML or design and see the changes in real time.

The Navigation Editor opened in split pane mode with the navigation.xml file on the left and the corresponding nav graph on the right. The nav graph has 6 destination, and a nested graph

Viewing a navigation.xml file in split view mode

In Android Studio 4.1, the Navigation Editor introduced the component tree. This allows you to traverse the entire nav graph, freely going in and out of nested graphs.

An open component tree of a nav graph in the Navigation Editor. It starts viewing the entire graph, then moves to the title screen before going into the nested profiles graph. After cycling through the destinations in the profiles graph, it goes back to fragments in the original graph

Navigating through a graph in the Navigation Editor

Additional Changes

NavigationUI can now use any layout that uses the Openable interface. This means that it is no longer limited to DrawerLayout and allows for customization of the AppBarConfiguration. You can provide your Openable and use it as the layout instead.

Navigation also provides support for Kotlin DSL. Kotlin DSL can be used to create different destinations, actions, or deep links. For more information see the documentation for Kotlin DSL.

Wrap up

Navigation added lots of useful features over the past year. You can simplify your dynamic feature modules by taking advantage of the Dynamic Navigator library, use a NavBackStackEntry to help correctly scope your data, easily test your navigation flow using the TestNavHostController, or even match your deep link using intent actions and/or MIME types.

For more information about the Jetpack Navigation library, check out the documentation at https://developer.android.com/guide/navigation

Please provide feedback (or file bugs) using the Navigation issuetracker component.

Getting on the same page with Paging 3

Posted by Florina Muntenescu, Android Developer Advocate

Android graphic

Getting on the same page with Paging 3

The Paging library enables you to load large sets of data gradually and gracefully, reducing network usage and system resources. You told us that the Paging 2.0 API was not enough - that you wanted easier error handling, more flexibility to implement list transformations like map or filter, and support for list separators, headers, and footers. So we launched Paging 3.0 (now in alpha02), a complete rewrite of the library using Kotlin coroutines (still supporting Java users) and offering the features you asked for.

Paging 3 highlights

The Paging 3 API provides support for common functionality that you would otherwise need to implement yourself when loading data in pages:

  • Keeps track of the keys to be used for retrieving the next and previous page.
  • Automatically requests the correct next page when the user scrolls to the end of the loaded data.
  • Ensures that multiple requests aren’t triggered at the same time.
  • Tracks loading state and allows you to display it in a RecyclerView list item or elsewhere in your UI, and provides easy retry functionality for failed loads.
  • Enables common operations like map or filter on the list to be displayed, independently of whether you’re using Flow, LiveData, or RxJava Flowable or Observable.
  • Provides an easy way of implementing list separators.
  • Simplifies data caching, ensuring that you’re not executing data transformations at every configuration change.

We also made several Paging 3 components backwards compatible with Paging 2.0; so if you already use Paging in your app, you can migrate incrementally.

Adopting Paging 3 in your app

Let’s say that we’re implementing an app that displays all the good doggos. We get the doggos from a GoodDoggos API that supports index-based pagination. Let’s go over the Paging components we need to implement and how they fit into your app architecture. The following examples will be in Kotlin, using coroutines. For examples in the Java programming language using LiveData/RxJava, check out the documentation.

The Paging library integrates directly into the recommended Android app architecture in each layer of your app:

Paging components

Paging components and their integration in the app architecture"

Defining the data source

Depending on where you’re loading data from, implement only a PagingSource or a PagingSource and a RemoteMediator:

  • If you’re loading data from a single source, like network, local database, a file, etc, implement the PagingSource (if you’re using Room, it implements the PagingSource for you starting in Room 2.3.0-alpha).
  • If you’re loading data from a layered source, like a network data source with a local database cache, implement the RemoteMediator to merge the two sources and a PagingSource for the local database cache.

PagingSource

A PagingSource defines the source of paging data and how to retrieve data from that single source. The PagingSource should be part of the repository layer. Implement load() to retrieve paged data from your data source and return the loaded data together with information about next and previous keys. This is a suspend function, so you can call other suspend functions here, such as the network call:

class DoggosRemotePagingSource(
    val backend: GoodDoggosService
) : PagingSource<Int, Dog>() {
  override suspend fun load(
    params: LoadParams<Int>
  ): LoadResult<Int, Dog> {
    try {
      // Load page 1 if undefined.
      val nextPageNumber = params.key ?: 1
      val response = backend.getDoggos(nextPageNumber)
      return LoadResult.Page(
        data = response.doggos,
        prevKey = null, // Only paging forward.
        nextKey = response.nextPageNumber + 1
      )
    } catch (e: Exception) {
        // Handle errors in this block
        return LoadResult.Error(exception)
    }
  }
}

PagingData and Pager

The container for paginated data is called PagingData. A new instance of PagingData is created every time your data is refreshed. To build a stream of PagingData create a Pager instance, using a PagingConfig configuration object and a function that tells the Pager how to get an instance of your PagingSource implementation.

In your ViewModel you construct the Pager object and expose a Flow<PagingData> to the UI. Flow<PagingData> has a handy cachedIn() method that makes the data stream shareable and allows you to cache the content of a Flow<PagingData> in a CoroutineScope. That way if you implement any transformations on the data stream, they will not be triggered again each time you collect the flow after Activity recreation. The caching should be done as close to the UI layer as possible, but not in the UI layer, as we want to make sure it persists beyond configuration change. The best place for this would be in a ViewModel, using the viewModelScope:

val doggosPagingFlow = Pager(PagingConfig(pageSize = 10)) {
  DogRemotePagingSource(goodDoggosService)
}.flow.cachedIn(viewModelScope)

PagingDataAdapter

To connect a RecyclerView to the PagingData, implement a PagingDataAdapter:

class DogAdapter(diffCallback: DiffUtil.ItemCallback<Dog>) :
  PagingDataAdapter<Dog, DogViewHolder>(diffCallback) {
  override fun onCreateViewHolder(
    parent: ViewGroup,
    viewType: Int
  ): DogViewHolder {
    return DogViewHolder(parent)
  }

  override fun onBindViewHolder(holder: DogViewHolder, position: Int) {
    val item = getItem(position)
    if(item == null) {
      holder.bindPlaceholder()
    } else {
      holder.bind(item)
    }
  }
}

Then, in your Activity/Fragment you’ll have to collect the Flow<PagingData> and submit it to the PagingDataAdapter. This is what the implementation would look like in an Activity onCreate():

val viewModel by viewModels<DoggosViewModel>()

val pagingAdapter = DogAdapter(DogComparator)
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = pagingAdapter

lifecycleScope.launch {
  viewModel.doggosPagingFlow.collectLatest { pagingData ->
    pagingAdapter.submitData(pagingData)
  }
}

Paged data transformations

A filtered list

Displaying a filtered list

Transforming PagingData streams is very similar to the way you would any other data stream. For example, if we only want to display playful doggos from our Flow<PagingData<Dog>> we would need to map the Flow object and then filter the PagingData:

doggosPagingFlow.map { pagingData ->
        pagingData.filter { dog -> dog.isPlayful }
    }
list with separators

List with separators

Adding list separators is also a paged data transformation where we transform the PagingData to insert separator objects into the list. For example, we can insert letter separators for our doggos’ names. When using separators, you will need to implement your own UI model class that supports the new separator items. To modify your PagingData to add separators, you will use the insertSeparators transformation:

pager.flow.map { pagingData: PagingData<Dog> ->
  pagingData.map { doggo ->
    // Convert items in stream to UiModel.DogModel.
    UiModel.DogModel(doggo)
  }
  .insertSeparators<UiModel.DogModel, UiModel> { before: Dog, after: Dog ->
      return if(after == null) {
          // we're at the end of the list
          null
      }  
      if (before == null || before.breed != after.breed) {
          // breed above and below different, show separator
          UiModel.SeparatorItem(after.breed)
       } else {
           // no separator
           null
       }
    }
  }
}.cachedIn(viewModelScope)

Just as before, we're using cachedIn right before the UI layer. This ensures that loaded data and the results of any transformations can be cached and reused after a configuration change.

Advanced Paging work with RemoteMediator

If you’re paging data from a layered source, you should implement a RemoteMediator. For example, in the implementation of this class you need to request data from the network and save it in the database. The load() method will be triggered whenever there is no more data in the database to be displayed. Based on the PagingState and the LoadType we can construct the next page request.

It’s your responsibility to define how the previous and next remote page keys are constructed and retained as the Paging library doesn’t know what your API looks like. For example, you can associate remote keys to every item you receive from the network and save them in the database.

override suspend fun load(loadType: LoadType, state: PagingState<Int, Dog>): MediatorResult {

   val page = ... // computed based on loadType and state

   try {
       val doggos = backend.getDoggos(page)
       doggosDatabase.doggosDao().insertAll(doggos)
       
       val endOfPaginationReached = emails.isEmpty()
       return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
   } catch (exception: Exception) {
       return MediatorResult.Error(exception)
   } 
}

When you’re loading data from the network and saving it to the database, the database is the source of truth for the data displayed on the screen. This means that the UI will be displaying data from your database, so you’ll have to implement a PagingSource for your database. If you’re using Room, you’ll just need to add a new query in your DAO that returns a PagingSource:

@Query("SELECT * FROM doggos")
fun getDoggos(): PagingSource<Int, Dog>

The Pager implementation changes slightly in this case, as you need to pass the RemoteMediator instance as well:

val pagingSourceFactory = { database.doggosDao().getDoggos() }

return Pager(
     config = PagingConfig(pageSize = NETWORK_PAGE_SIZE),
     remoteMediator = DoggosRemoteMediator(service, database),
     pagingSourceFactory = pagingSourceFactory
).flow

Check out the docs to find out more about working with RemoteMediator. For a complete implementation of RemoteMediator in an app, check out step 15 of the Paging codelab and the accompanying code.



We’ve designed the Paging 3 library to help you accommodate both simple and complex uses of Paging. It makes it easier to work with large sets of data whether it’s being loaded from the network, a database, in-memory cache, or some combination of these sources. The library is built on coroutines and Flow, making it easy to call suspend functions and work with streams of data.

As Paging 3 is still in alpha, we need your help to make it better! To get started, find out more about Paging in our documentation and try it out by taking our codelab or checking out the sample. Then, let us know how we can improve the library by creating issues on the Issue Tracker.

Unifying Background Task Scheduling on Android

Posted by Caren Chang, Developer Programs Engineer

Android users care a lot about the battery life on their phones. In particular, how your app schedules deferrable background tasks play an important role in battery life. To help you build more battery-friendly apps, we introduced WorkManager as the unified solution for all deferrable background processing needs.

Starting November 1, 2020, we are unifying deferrable background tasks on Android around WorkManager, and GCMNetworkManager will be deprecated and no longer supported.

Why WorkManager

The WorkManager API incorporates the features of Firebase Job Dispatcher (FJD) and GcmNetworkManager solutions, providing a consistent job scheduling service back to API level 14 while being conscious of battery life. For example, if your app needs to send log files up to the server, it would be more efficient to wait until the device is both charging and connected to WiFi. In this case, WorkManager will ensure that the sync will execute when the given constraints (charging and connected to WiFi) are met. Additionally, it does not require Google Play Services, unlike FJD and GcmNetworkManager.

Some of the other key features of WorkManager include:

  • Persist scheduled work across app updates and device restarts
  • Schedule one-off or periodic tasks
  • Monitor and manage tasks
  • Chain tasks together

What it means for developers

Now that the WorkManager library has reached maturity, we have decided to deprecate alternative solutions to simplify the developer story and focus on WorkManager stability and features.

  • We announced the deprecation of the FirebaseJobDispatcher library in April 2019. In April 2020 the library will be archived and we will no longer provide support for issues filed on the library.
  • In addition, we are now announcing the deprecation of GCMNetworkManager. The library is no longer receiving any new features and starting in November 2020, we will no longer provide support for issues relating to the library.
  • Furthermore, once your app updates the target API level (targetSdkVersion) to above Android 10 (API level 29), FirebaseJobDispatcher and GcmNetworkManager API calls will no longer work on devices running Android Marshmallow (6.0) and above.

Migrating to WorkManager

Now is the time to migrate your apps to WorkManager if you haven't already done so! You can start by reading the official documentation for WorkManager.

If your app is still using FirebaseJobDispatcher, you can migrate your app to WorkManager by following the migration guide. A similar migration guide from GCMNetworkManager to WorkManager is also available.

YouTube recently moved over to WorkManager for their background scheduling needs and has reported improvements in app startup time as well as an 8% drop in crash rates.

Going forward

The team is dedicated to improving and continuing feature development on WorkManager. If you encounter issues using the library, have proposals for features you would like to see, or have any feedback about the library, please file an issue.