Deep links
Deep linking is a navigation mechanism that allows the operating system to handle custom links by taking the user to a specific destination in the corresponding app.
Deep links are a more general case of app links (as they are called on Android) or universal links (the iOS term): these are verified connections of the app with a specific web address. To learn about them specifically, see documentation on Android App Links and iOS universal links.
Deep links can also be useful for getting outside input into the app, for example, in the case of OAuth authorization: you can parse the deep link and get the OAuth token without necessarily visually navigating the users.
To implement a deep link in Compose Multiplatform:
Setup
To use deep links with Compose Multiplatform, set up the dependencies as follows.
List these versions, libraries, and plugins in your Gradle catalog:
Add the additional dependencies to the shared module's build.gradle.kts
:
Register deep links schemas in the operating system
Each operating system has its own way of handling deep links. It's more reliable to refer to the documentation for your particular targets:
For Android apps, deep link schemes are declared as intent filters in the
AndroidManifest.xml
file. See Android documentation to learn how to properly set up intent filters.For iOS and macOS apps, deep link schemes are declared in
Info.plist
files, in the CFBundleURLTypes key.For Windows apps, deep link schemes can be declared by adding keys with necessary information to the Windows registry (for Windows 8 and earlier) or by specifying the extension in the package manifest (for Windows 10 and 11). This can be done with an installation script or a third-party distribution package generator like Hydraulic Conveyor. Compose Multiplatform does not support configuring this within the project itself.
For Linux, deep link schemes can be registered in a
.desktop
file included in the distribution.
Assign deep links to destinations
A destination declared as a part of a navigation graph has an optional deepLinks
parameter which can hold the list of corresponding NavDeepLink
objects. Each NavDeeplink
describes a URI pattern that should match a destination – you can define multiple URI patterns that should lead to the same screen.
There is no limit to the number of deep links you can define for a route.
General URI patterns for deep links
A general URI pattern should match the entire URI. You can use placeholders for parameters to extract them from the received URI within the destination.
Rules for general URI patterns:
URIs without a scheme are assumed to start with
http://
orhttps://
. SouriPattern = "example.com"
matcheshttp://example.com
andhttps://example.com
.{placeholder}
matches one or more characters (example.com/name={name}
matcheshttps://example.com/name=Bob
). To match zero or more characters, use the.*
wildcard (example.com/name={.*}
matcheshttps://example.com/name=
as well as any value ofname
).Parameters for path placeholders are required while matching query placeholders is optional. For example, the pattern
example.com/users/{id}?arg1={arg1}&arg2={arg2}
:Doesn't match
http://www.example.com/users?arg1=one&arg2=two
because the required part of the path (id
) is missing.Matches both
http://www.example.com/users/4?arg2=two
andhttp://www.example.com/users/4?arg1=one
.Also matches
http://www.example.com/users/4?other=random
because extraneous query parameters don't affect matching.
If several composables have a
navDeepLink
that matches the received URI, behavior is indeterminate. Make sure that your deep link patterns don't intersect. If you need multiple composables to handle the same deep link pattern, consider adding path or query parameters, or use an intermediate destination to route the user predictably.
Generated URI pattern for a route type
You can avoid writing out the URI pattern fully: the Navigation library can automatically generate a URI pattern based on the parameters of a route.
To use this approach, define a deep link like this:
Here PlantDetail
is the route type you're using for the destination, and the "plant" in basePath
is the serial name of the PlantDetail
data class.
The rest of the URI pattern will be generated as follows:
Required parameters are appended as path parameters (example:
/{id}
)Parameters with a default value (optional parameters) are appended as query parameters (example:
?name={name}
)Collections are appended as query parameters (example:
?items={value1}&items={value2}
)The order of parameters matches the order of the fields in the route definition.
So, for example, this route type:
has the following generated URI pattern generated by the library:
Example of adding deep links to a destination
In this example we assign several deep links to a destination and then extract parameter values from the received URIs:
For web, deep links work a bit differently: since Compose Multiplatform for Web makes single-page apps, you need to put all parameters of the deep link URI pattern in a URL fragment (after the #
character), and make sure that all parameters are URL-encoded.
You can still use the backStackEntry.toRoute()
method to parse the parameters if the URL fragment conforms to the URI pattern rules. For details on accessing and parsing a URL in a web app, as well as particulars on navigation in the browser, see Support for browser navigation in web apps.
Handle received deep links
On Android, the deep link URIs sent to the app are available as a part of the Intent
that triggered the deep link. A cross-platform implementation needs a universal way to listen for deep links.
Let's create a bare-bones implementation:
Declare a singleton in common code for storing and caching the URIs with a listener for external URIs.
Where necessary, implement platform-specific calls sending URIs received from the operating system.
Set up the listener for new deep links in the main composable.
Declare a singleton with a URI listener
In commonMain
, declare the singleton object at the top level:
Implement platform-specific calls to the singleton
Both for desktop JVM and for iOS you need to explicitly pass the URI received from the system.
In jvmMain/.../main.kt
, parse the command-line arguments for every necessary operating system and pass the received URI on to the singleton:
For iOS, in Swift code add an application()
variant that handles incoming URIs:
Set up the listener
You can use a DisposableEffect(Unit)
to set up the listener and clean it up after the composable is no longer active. For example:
Result
Now you can see the full workflow: when the user opens a demo://
URI, the operating system matches it with the registered scheme. Then:
If the app handling the deep link is closed, the singleton receives the URI and caches it. When the main composable function starts, it calls the singleton and navigates to the deep link matching the cached URI.
If the app handling the deep link is open, the listener is already set up, so when the singleton receives the URI the app immediately navigates to it.