Skip to content

Deep linking

Users following links on devices have one goal in mind: to get to the content they want to see. Decompose provides ability to override initial destinations and back stack. A typical approach is to parse deep links on the platform side and then pass the initial destination data to the root component and then down the tree to all the required components.

Parsing deep links on the platform side is beyond this documentation. This information should be available in the platform's specific documentation. For example here is the related documentation for Android.

Given the basic example from the Child Stack Overview page, we can easily handle deep links. Let's say we have a link like http://myitems.com?itemId=3. When the user clicks on it, we want to open the details screen of the item with the provided id. When the user closes the details screen, they should be navigated back to the list screen. The idea is to pass parsed data from the deep link to a component responsible for navigation, in our case it is the Root component.

import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import kotlinx.serialization.Serializable

class DefaultRootComponent(
    componentContext: ComponentContext,
    initialItemId: Long? = null, // It can be any other type, e.g. a sealed class with all possible destinations
) : RootComponent, ComponentContext by componentContext {

    private val navigation = StackNavigation<Config>()

    private val stack =
        childStack(
            source = navigation,
            initialStack = {
                listOfNotNull(
                    Config.List,
                    if (initialItemId != null) Config.Details(itemId = initialItemId) else null,
                )
            },
            handleBackButton = true,
            childFactory = ::createChild,
        )

    // Omitted code

    @Serializable
    private sealed class Config {
        @Serializable
        data object List : Config()

        @Serializable
        data class Details(val itemId: Long) : Config()
    }
}

Now, if the initialItemId is supplied, the initial screen will be the ItemDetails component. The ItemList component will be in the back stack, so the user will be able to go back.

The first step is to configure your Android app so that it can handle deep links. Please follow the official documentation.

Warning

It is strongly recommended to always use the standard (default) launchMode for Activity when handling deep links.

Once the app is configured, the deeplink Intent will be available to use in Activity#onCreate method. The handleDeepLink extension function can be used to automatically extract the deep link Uri from the Intent, it also takes care of restarting the Activity task and finishing the current Activity if needed (similarly to how it works with the official Jetpack navigation).

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import com.arkivanov.decompose.defaultComponentContext
import com.arkivanov.decompose.handleDeepLink

class MainActivity : AppCompatActivity() {

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

        val root = 
            handleDeepLink { uri ->
                val initialItemId = uri?.extractInitialItemId()
                DefaultRootComponent(
                    componentContext = defaultComponentContext(
                        discardSavedState = initialItemId != null, // Discard any saved state if there is a deep link
                    ),
                    initialItemId = initialItemId,
                )
            } ?: return

        setContent {
            // Omitted code
        }
    }

    private fun Uri.extractInitialItemId(): Long = TODO("Extract the initial item id from the deep link")
}

Alternative way

The alternative way can be used if for some reason you don't want to use the standard (default) launchMode for the Activity, and instead of restarting the root component you prefer handling the new deep link manually in Activity#onNewIntent.

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.defaultComponentContext

class MainActivity : AppCompatActivity() {

    private lateinit var root: RootComponent

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

        val initialItemId = intent.data?.extractInitialItemId()

        root =
            DefaultRootComponent(
                componentContext = defaultComponentContext(),
                initialItemId = initialItemId,
            )

        setContent {
            // Omitted code
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)

        val initialItemId = intent.data?.extractInitialItemId() ?: return
        root.onDeepLink(initialItemId)
    }

    private fun Uri.extractInitialItemId(): Long? =
        TODO("Extract the initial item id from the deep link")
}

class DefaultRootComponent(
    componentContext: ComponentContext,
    initialItemId: Long? = null,
) : RootComponent, ComponentContext by componentContext {

    // Omitted code

    fun onDeepLink(initialItemId: Long) {
        navigation.replaceAll(Config.List, Config.Details(itemId = initialItemId))
    }

    // Omitted code
}