Skip to content

Navigation: Deeplinks

Viacheslav Ivanovichev edited this page Oct 20, 2023 · 9 revisions

Deeplinks are just plain black-box config parameters. To make the app handle them, define top level-config for the App entry point.

@Immutable
data class AppConfig(val deeplink: String? = null)

And pass the config to the AppCoordinator via AppScope.

@Composable
fun App(appDependencies: AppDependencies, config: AppConfig) {
    val appScope = rememberScope { AppScope(appDependencies, config) }
    //...
}

In the coordinator’s init method call navigator with corresponding deeplink param.

class AppCoordinator(
    //..
    private val config: AppConfig
) : Coordinator() {

    init {
        if (config.deeplink != null) {
            navigator.navigateTo(config.deeplink)
        }
        //..
    }
}

Now the navigation logic is ready to open any deeplink coming as a String parameter. The last step is to create an unique Route in the form of deeplink that will refer to a particular navigation destination.

 sealed interface Ticketing : AppRoutes {
        data object Flow : Ticketing {
            const val RoutePattern: String = "movie://app/ticketing/{movieName}"

            fun routeWithParam(movieName: String) = "movie://app/ticketing/${movieName}"
        }
    }

Then assign the Route to the corresponding scene:

@Composable
fun App(appDependencies: AppDependencies, config: AppConfig) {
    //...
    NavigationFlow(/* .. */ ) {
            //..

            dialog(route = AppRoutes.Ticketing.Flow.RoutePattern) { entry ->
                // Scene @Composable content goes here
                appScope.ticketingFactory.CreateTicketingFlow(
                    modifier = Modifier.fillMaxSize(),
                    io = appScope.ticketingFlowIO,
                    config = TicketingConfig(
                        // retrieve {movieName} parameter from a deeplink
                        movieName = requireNotNull(entry.path<String>("movieName"))
                    )
                )
            }

            //..
        }
}

That’s it, now the common code is ready to perform a scene by a deeplink.

Android

To make it work on Android. Specify the corresponding intent filter into AndroidManifest.xml

<intent-filter
   android:autoVerify="true"
   android:label="Deeplink">
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data
         android:host="app"
         android:scheme="movie" />
</intent-filter>

In MainActivity.kt, pass the intent param to the AppConfig()

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

        //...

        // You can add additional checks to filter out the deeplink data
        val action: String? = intent?.action
        val deeplinkUri = intent?.data?.toString()

        setContent {
            CreateMovieApp(AppConfig(deeplink = deeplinkUri))
        }
    }
}

Test the deeplink via terminal command:

adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "movie://app/ticketing/Barbie"

iOS

Define corresponding scheme in Target’s Info, e.g movie://

Screenshot 2023-10-09 at 12.57.32.png

In your AppSceneDelegate retrieve urlContext and pass the deeplink in app controller like so:

// Will be called when app is not running	
override fun scene(
        scene: UIScene,
        willConnectToSession: UISceneSession,
        options: UISceneConnectionOptions
    ) {
	//...
	val urlContexts = options.URLContexts as? Set<UIOpenURLContext>
        val deeplink = urlContexts?.firstOrNull()?.URL?.absoluteString

        val appController = createMovieAppController(
            nsUserActivity = activity,
            config = AppConfig(deeplink = deeplink)
        )

	//..
}

// Will be called when app is in runtime
override fun scene(scene: UIScene, openURLContexts: Set<*>) {
        val urlContexts = openURLContexts as? Set<UIOpenURLContext>
        val deeplink = urlContexts?.firstOrNull()?.URL?.absoluteString

        window?.rootViewController = createMovieAppController(
            nsUserActivity = null,
            config = AppConfig(deeplink = deeplink)
        )
    }

Test the deeplink via terminal command:

xcrun simctl openurl booted "movie://app/ticketing/Barbie"