An easy way to use deep link in Navigation Component on a Big Project

I will demonstrate how to use deep link in Navigation Component on a big project and show some problems you might found during the implementation.

Designing
Navigation on Android is now easier than ever before with the Jetpack Navigation Component. This library allows you to implement navigation without the headache of complex navigation, which makes it easier to scale your navigation implementation in large projects.

Navigation Component Benefits
1. Automates fragment transactions, this can make our code cleaner.
2. Handles back stack
2. Simplified deep linking
3. Safe Args allows you to pass data between screens easily and type-safe
4. Handles transition animations
5. We can handle Drawer and Bottom Navigation easier
6. Centralizes and visualizes navigation

In this article, I will show you my experience and some problems you might found when you use Jetpack Navigation Component in a big project.

Don't reuse fragments in many navigation graphs.
In a big project, you can't avoid reusing a fragment in many Navigation graphs, but you might end up with this error:

Reuse fragments in many navigation graph error.
The error happens when you try to release the project with pro-guards because SafeArgs Plugin will generate classes ****Args.kt and ***Directions.kt for all navigation graphs, but pro-guards won't allow you to have duplicate classes.

The solution, Nested Graph!
When you need to use a fragment in many flows you can create a nested graph and reuse it anywhere.

Deep-link is the hero!
Y
ou can't predict the future requirements of the business, but you can prepare for future change, deep-link in navigation component can make a complicated flow much simpler.

At the beginning of the project, you may have a significantly complicated flow, However, one day the requirements change, and now the app needs to use just a small flow from the significant flow. We could easily copy some part of the flow into the new flow, but we would have duplicate code.

The solution is to make the flow smaller using the nested graph as I mentioned before and use the deep link for navigations.

Global Action
Global action is one way to access your navigation graphs from everywhere even from a nested graph just like a deep link, however they can't be accessed from another application.

Creating deep links
Here is a shopping cart project which has a product listing, shopping cart, payment, history, and more, and the project is also modularized by feature and uses Single Activity Architecture. I am assuming you already know how to set up and create basic navigation graphs. If you don't, you can learn by following Google's code lab.

Shopping Cart Project's Architecture
When you create a navigation graph, I recommend creating one deep link for one flow where you can access the flow from anywhere in the project. Then, use the deep link for navigating between pages in your app, as you might need deep links in the future.

Create a Listing graph
<?xml version="1.0" encoding="utf-8"?>
<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/navigation_listing"
    app:startDestination="@id/listingFragment">

    <fragment
        android:id="@+id/listingFragment"
        android:name="com.him.oozoublog.navcom.listing.ui.listing.ListingFragment"
        android:label="fragment_listing"
        tools:layout="@layout/fragment_listing">
        <deepLink
            android:id="@+id/deepLink"
            app:uri="myapp://listing" />
    </fragment>
    ......
</navigation>

Create a Shopping Cart navigation graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/navigation_shopping_cart"
    app:startDestination="@id/shoppingCartFragment">

    <fragment
        android:id="@+id/shoppingCartFragment"
        android:name="com.him.oozoublog.navcom.shoppingcart.ui.ShoppingCartFragment"
        android:label="ShoppingCartFragment">
        <deepLink
            android:id="@+id/deepLink"
            app:uri="myapp://shopping-cart" />
    </fragment>
</navigation>

Create a History navigation graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/history_navigation"
    app:startDestination="@id/historyFragment">

    <fragment
        android:id="@+id/historyFragment"
        android:name="com.him.oozoublog.navcom.history.ui.HistoryFragment"
        android:label="HistoryFragment">
        <deepLink
            android:id="@+id/deepLink"
            app:uri="myapp://history" />
    </fragment>
</navigation>

Create a Payment navigation graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/navigation_payment"
    app:startDestination="@id/paymentFragment">

    <fragment
        android:id="@+id/paymentFragment"
        android:name="com.him.oozoublog.navcom.payment.ui.PaymentFragment"
        android:label="PaymentFragment">
        <deepLink
            android:id="@+id/deepLink"
            app:uri="myapp://payment" />
    </fragment>
</navigation>

Create the main navigation graph
The main navigation graph is literally nothing but includes all graphs that need to be integrated into the project.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_navigation"
    app:startDestination="@+id/navigation_listing">

    <include app:graph="@navigation/navigation_listing" />

    <include app:graph="@navigation/navigation_history" />

    <include app:graph="@navigation/navigation_shopping_cart" />

    <include app:graph="@navigation/navigation_payment" />

</navigation>

Deep-link handler 
We need a class that contains all the supported deep links and can be accessed anywhere in the project. We also can have custom functions as needed.
object InternalDeepLink {
    const val DOMAIN = "myapp://"

    const val LISTING = "${DOMAIN}listing"
    const val PAYMENT = "${DOMAIN}payment"
    const val SHOPPING_CART = "${DOMAIN}shopping-cart"
    const val HISTORY = "${DOMAIN}history"

    fun makeCustomDeepLink(id: String): String {
        return "${DOMAIN}customDeepLink?id=${id}"
    }
}

How to use the deep links
Using the deep links is very easy. You can navigate to all screens that support deep links like this:
listingViewModel._navigateToShoppingCart.observe(viewLifecycleOwner, {
    val deepLink = InternalDeepLink.SHOPPING_CART.toUri()
    findNavController().navigate(deepLink)
})

We can use enums in the deep links
Navigation library allows you to pass your custom enum class between destinations, you can view supported argument types.

For example, the listing page can search by car, watch, and both.
enum class SearchType {
    ALL,
    CAR,
    WATCH
}
Add an argument into the listing graph
<?xml version="1.0" encoding="utf-8"?>
<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/navigation_listing"
    app:startDestination="@id/listingFragment">

    <fragment
        android:id="@+id/listingFragment"
        android:name="com.him.oozoublog.navcom.listing.ui.listing.ListingFragment"
        android:label="fragment_listing"
        tools:layout="@layout/fragment_listing">
        <argument
            android:name="searchType"
            android:defaultValue="ALL"
            app:argType="com.him.oozoublog.navcom.listing.ui.listing.SearchType" />
        <deepLink
            android:id="@+id/deepLink"
            app:uri="myapp://listing?searchType={searchType}" />
    </fragment>
</navigation>

How to get the data from deep links.
When we navigate to another page with some data SafeArgs Plugin will automatically handle the data. When you rebuild the project you will see ListingFragmentArgs.kt. This plugin also converts the data to your custom class if there is any.
ListingFragmentArgs.kt
You can use navArgs() delegate for passing data like this:
class ListingFragment : Fragment() { 
    ....
    private val args: ListingFragmentArgs by navArgs()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.i("search type", args.searchType.name)
    }
    ....
}
Like 11 likes
Him Sama
Share:

Join the conversation

This will be shown public
All comments are moderated

Comments

OLEG
April 12th, 2021
How to open a deep-link WITHOUT backstack.
1. Open fragment A with deep-link (press back -> close app)

2. Open fragment B with deep-link (press back -> close app)

Mark
June 25th, 2021
Thanks for the content, could you please share the use-cases in the article in the project GitHub repo, thanks.

Get our stories delivered

From us to your inbox weekly.