[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-7297":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":10,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":21,"hasPages":21,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":27,"readmeContent":28,"aiSummary":29,"trendingCount":16,"starSnapshotCount":16,"syncStatus":17,"lastSyncTime":30,"discoverSource":31},7297,"DeepLinkDispatch","airbnb\u002FDeepLinkDispatch","airbnb","A simple, annotation-based library for making deep link handling better on Android","http:\u002F\u002Fnerds.airbnb.com\u002Fdeeplinkdispatch\u002F",null,"Kotlin",4413,412,114,50,0,2,4,1,58.25,false,"master",[24,25,26],"android","annotation-processor","deep-links","2026-06-12 04:00:33","# DeepLinkDispatch\n\n[![Deploy snapshot](https:\u002F\u002Fgithub.com\u002Fairbnb\u002FDeepLinkDispatch\u002Factions\u002Fworkflows\u002Fdeploy_snapshot.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fairbnb\u002FDeepLinkDispatch\u002Factions\u002Fworkflows\u002Fdeploy_snapshot.yml)\n\nDeepLinkDispatch provides a declarative, annotation-based API to define application deep links.\n\nYou can register an `Activity` to handle specific deep links by annotating it with `@DeepLink` and a URI.\nDeepLinkDispatch will parse the URI and dispatch the deep link to the appropriate `Activity`, along\nwith any parameters specified in the URI.\n\n### Example\n\nHere's an example where we register `SampleActivity` to pull out an ID from a deep link like\n`example:\u002F\u002Fexample.com\u002FdeepLink\u002F123`. We annotated with `@DeepLink` and specify there will be a\nparameter that we'll identify with `id`.\n\n```java\n@DeepLink(\"example:\u002F\u002Fexample.com\u002FdeepLink\u002F{id}\")\npublic class SampleActivity extends Activity {\n  @Override protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    Intent intent = getIntent();\n    if (intent.getBooleanExtra(DeepLink.IS_DEEP_LINK, false)) {\n      Bundle parameters = intent.getExtras();\n      String idString = parameters.getString(\"id\");\n      \u002F\u002F Do something with idString\n    }\n  }\n}\n```\n\n**Note:** Only the scheme, host and path elements of the URI are used for matching the incoming deeplink to the code. Query parameters or any other part of URI are not used for matching. Query parameters are handled and available in the deep link meta data, e.g. for [method call backs](README.md#query-parameters) or when [using handlers](https:\u002F\u002Fgithub.com\u002Fairbnb\u002FDeepLinkDispatch\u002Fedit\u002Fmaster\u002FREADME.md#deeplinkhandler-annotations).\n\n### Multiple Deep Links\n\nSometimes you'll have an Activity that handles several kinds of deep links:\n\n```java\n@DeepLink({\"foo:\u002F\u002Fexample.com\u002FdeepLink\u002F{id}\", \"foo:\u002F\u002Fexample.com\u002FanotherDeepLink\"})\npublic class MainActivity extends Activity {\n  @Override protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    Intent intent = getIntent();\n    if (intent.getBooleanExtra(DeepLink.IS_DEEP_LINK, false)) {\n      Bundle parameters = intent.getExtras();\n      String idString = parameters.getString(\"id\");\n      \u002F\u002F Do something with idString\n    }\n  }\n}\n```\n\n### Manifest generation\n\nIf you have any deep links and multiple and possibly complicated (multiple versions of allowed host and path) URLs it can get \ncomplicated to keep the `AndroidManifest.xml` entries up to date. DeepLinkDispatch allows you to (when using `ksp` and the\ndeeplink is in a library module) automatically generate `AndroidManifest.xml` entries and keep them up to date.\n\n```kotlin\n@DeepLink(\"http{scheme(|s)}:\u002F\u002Fexample.{domain(com|de|ro)}\u002FdeepLink\u002F{id}\", \"{scheme(foo|bar)}:\u002F\u002F{host(example|another-example)}.{domain(com|de|ro)}\u002FanotherDeepLink\", activityClassFqn = \"com.example.MainActivity\")\nclass MainActivity : Activity {\n  @Override fun onCreate(savedInstanceState: Bundle) {\n    super.onCreate(savedInstanceState)\n    val intent : Intent = getIntent()\n    if (intent.getBooleanExtra(DeepLink.IS_DEEP_LINK, false)) {\n      val parameters : Bundle = intent.getExtras()\n      val idString : String = parameters.getString(\"id\")\n      \u002F\u002F Do something with idString\n    }\n  }\n}\n```\nThe requirements for using manifest generation are:\n\n1. Must use `ksp`\n2. Deep links must be used in a module that is not an application module (does not apply the `com.android.application` gradle plugin)\n3. Must specify `activityClassFqn` in the `@DeepLink` annotation or in the `@DeepLinkSpec` annotation when defining a custom deeplink. This will reference the Activity that the `intent-filter` should be added.\n\nYou can see an example of this in `sample-ksb-library`.\n\nIf left to default `intent-filter` entries will be generated with:\n\n```xml\n\u003Caction android:name=\"android.intent.action.VIEW\" \u002F>\n```\nand\n```xml\n\u003Ccategory android:name=\"android.intent.category.DEFAULT\" \u002F>\n\u003Ccategory android:name=\"android.intent.category.BROWSABLE\" \u002F>\n```\n\nbut you can override that default by providing different values for `actions` and\u002For `categories` in the `@DeepLink` annotation or in the `@DeepLinkSpec` annotation (for special annotation definition).\n\nThe integration of the generated `AndroidManfiest.xml` file into the Android build system is done by a gradle plugin that needs to be applied to every module that is using manifest generation.\n\nTo do that you need to first make it known to your projects root gradle file:\n\n```groovy\nbuildscript {\n\n    ...\n    \n    dependencies {\n        ...\n        classpath \"com.airbnb:deeplinkdispatch-gradle-plugin:$VERSION\"\n    }\n}\n```\n\nand then apply it in your module:\n\n```groovy\napply plugin: 'com.airbnb.deeplinkdispatch.manifest-generation'\n```\n\n**Note**: It must be applied *after* the kotlin and android plugins and cannot be applied to an android application module.\n\n### DeepLinkHandler Annotations\n\nYou can annotate a Kotlin `object` that is extending `com.airbnb.deeplinkdispatch.handler.DeepLinkHandler`\nwith an `@DeepLink` annotation.\n\n```kotlin\n@DeepLink(\"foo:\u002F\u002Fexample.com\u002FhandlerDeepLink\u002F{param1}?query1={queryParameter}\")\nobject ProjectDeepLinkHandler : DeepLinkHandler\u003CProjectDeepLinkHandlerArgs>() {\n    override fun handleDeepLink(parameters: ProjectDeepLinkHandlerArgs) {\n        \u002F**\n         * From here any internal\u002F3rd party navigation framework can be called the provided args.\n         *\u002F\n    }\n}\n\ndata class ProjectDeepLinkHandlerArgs(\n    @DeeplinkParam(\"param1\", DeepLinkParamType.Path) val number: Int,\n    @DeeplinkParam(\"query1\", DeepLinkParamType.Query) val flag: Boolean?,\n)\n```\n\nDeepLinkDispatch will then call the `handleDeepLink` function in your handler with the path placeholders\nand queryParameters converted into an instance of the specified type class.\n\nQuery parameter conversion is supported for nullable and non nullable versions of `Boolean`,`Int`,\n`Long`,`Short`,`Byte`,`Double`,`Float` and `String` as well as the same types in Java. For other\ntypes see: [Type conversion](#type-conversion)\n\nThis will give compile time safety, as all placeholders and query parameters specified in the template\ninside the `@DeepLink` annotation must be present in the arguments class for the processor to pass.\nThis is also true the other way around as all fields in the arguments class must be annotated and must\nbe present in the template inside the annotation.\n\nFrom this function you can now call into any internal or 3rd party navigation system\nwithout any Intent being fired at all and with type safety for your arguments.\n\n*Note:* Even though they must be listed in the template and annotation, argument values annotated\nwith `DeepLinkParamType.Query` can be null as they are allowed to not be present in the matched url.\n\n#### Type conversion\n\nIf you want to support the automatic conversions of types other than `Boolean`,`Int`,`Long`,`Short`,`Byte`,\n`Double`,`Float` and `String` in deep link argument classes you can add support by adding your own type\nconverters in the `DeepLinkDelegate` class that you are instantiating.\n\nType conversion is handled via a lambda that you can set in the `DeepLinkDelegate` constructor.\n\nAll type converters you want to add get added to an instance of `TypeConverters` which then in turn\ngets returned by the lambda. This way you can add type converters on the fly while the app is running\n(e.g. if you just downloaded a dynamic feature which supports additional types).\n\nThere is an example of this in the `sample` app for this. Here is a brief overview:\n\n```java\nTypeConverters typeConverters = new TypeConverters();\ntypeConverters.put(ColorDrawable.class, value -> {\n  switch (value.toLowerCase()) {\n    case \"red\":\n      return new ColorDrawable(0xff0000ff);\n  }\n});\n\nFunction0\u003CTypeConverters> typeConvertersLambda = () -> typeConverters;\n\nDeepLinkDelegate deepLinkDelegate = new DeepLinkDelegate(\n  ...\n  typeConvertersLambda,\n  ...);\n```\n\n#### Type conversion errors\n\nIf a url parameter cannot be converted to the specified type, the system will -- by default -- set the\nvalue to `0` or `null`, depending on if the type is nullable. However this behavior can be overwritten\nby implementing a lambda `Function3\u003CDeepLinkUri, Type, String, Integer>` and setting it to\n`typeConversionErrorNullable` and\u002For `typeConversionErrorNonNullable` via the `DeepLinkDelegate`\nconstructor. When called, the lambda will let you know about the matching `DeepLinkUri` template, the\ntype and the value that was tried to type convert so you can also log these events.\n\n### Method Annotations\n\nYou can also annotate any `public static` method with `@DeepLink`. DeepLinkDispatch will call that\nmethod to create the `Intent` and will use it when starting your `Activity` via that registered deep link:\n\n```java\n@DeepLink(\"foo:\u002F\u002Fexample.com\u002FmethodDeepLink\u002F{param1}\")\npublic static Intent intentForDeepLinkMethod(Context context) {\n  return new Intent(context, MainActivity.class)\n      .setAction(ACTION_DEEP_LINK_METHOD);\n}\n```\n\nIf you need access to the `Intent` extras, just add a `Bundle` parameter to your method, for example:\n\n```java\n@DeepLink(\"foo:\u002F\u002Fexample.com\u002FmethodDeepLink\u002F{param1}\")\npublic static Intent intentForDeepLinkMethod(Context context, Bundle extras) {\n  Uri.Builder uri = Uri.parse(extras.getString(DeepLink.URI)).buildUpon();\n  return new Intent(context, MainActivity.class)\n      .setData(uri.appendQueryParameter(\"bar\", \"baz\").build())\n      .setAction(ACTION_DEEP_LINK_METHOD);\n}\n```\n\nIf you're using Kotlin, make sure you also annotate your method with `@JvmStatic`. `companion objects` will *not work*, so you can use an `object declaration` instead:\n\n```kotlin\nobject DeeplinkIntents {\n  @JvmStatic \n  @DeepLink(\"https:\u002F\u002Fexample.com\")\n  fun defaultIntent(context: Context, extras: Bundle): Intent {\n    return Intent(context, MyActivity::class.java)\n  }\n}\n```\n\nIf you need to customize your `Activity` backstack, you can return a `TaskStackBuilder` instead of an `Intent`. DeepLinkDispatch will call that method to create the `Intent` from the `TaskStackBuilder` last `Intent` and use it when starting your `Activity` via that registered deep link:\n\n```java\n@DeepLink(\"http:\u002F\u002Fexample.com\u002FdeepLink\u002F{id}\u002F{name}\")\npublic static TaskStackBuilder intentForTaskStackBuilderMethods(Context context) {\n  Intent detailsIntent =  new Intent(context, SecondActivity.class).setAction(ACTION_DEEP_LINK_COMPLEX);\n  Intent parentIntent =  new Intent(context, MainActivity.class).setAction(ACTION_DEEP_LINK_COMPLEX);\n  TaskStackBuilder  taskStackBuilder = TaskStackBuilder.create(context);\n  taskStackBuilder.addNextIntent(parentIntent);\n  taskStackBuilder.addNextIntent(detailsIntent);\n  return taskStackBuilder;\n}\n```\n\nIf, depending on app state or parameter values you have to either just start an `Intent` or a\n`TaskStackBuilder`, you can return an instance of `DeepLinkMethodResult`, which can have any.\nThe system will pick whichever value is not null but will prefer the `TaskStackBuilder` if both\nare not null.\n\n```java\n@DeepLink(\"dld:\u002F\u002Fhost\u002FmethodResult\u002Fintent\")\npublic static DeepLinkMethodResult intentOrTaskStackBuilderViaDeeplinkMethodResult(Context context) {\n  TaskStackBuilder taskStackBuilder = null;\n  Intent intent = null;\n  if (someState) {\n    Intent detailsIntent = new Intent(context, SecondActivity.class);\n    Intent parentIntent = new Intent(context, MainActivity.class);\n    taskStackBuilder = TaskStackBuilder.create(context);\n    taskStackBuilder.addNextIntent(parentIntent);\n    taskStackBuilder.addNextIntent(detailsIntent);\n  } else {\n    intent = new Intent(context, MainActivity.class);\n  }\n  return new DeepLinkMethodResult(intent, taskStackBuilder);\n}\n```\n\n### Query Parameters\n\nQuery parameters are parsed and passed along automatically, and are retrievable like any other parameter. For example, we could retrieve the query parameter passed along in the URI `foo:\u002F\u002Fexample.com\u002FdeepLink?qp=123`:\n\n```java\n@DeepLink(\"foo:\u002F\u002Fexample.com\u002FdeepLink\")\npublic class MainActivity extends Activity {\n  @Override protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    Intent intent = getIntent();\n    if (intent.getBooleanExtra(DeepLink.IS_DEEP_LINK, false)) {\n      Bundle parameters = intent.getExtras();\n      if (parameters != null && parameters.getString(\"qp\") != null) {\n        String queryParameter = parameters.getString(\"qp\");\n        \u002F\u002F Do something with the query parameter...\n      }\n    }\n  }\n}\n```\n\n### Configurable path segment placeholders\n\nConfigurable path segment placeholders allow your to change configured elements of the URL path at runtime without changing the source of the library where the deeplink is defined. That way a library can be used in multiple apps that are still uniquely addressable via deeplinks. They are defined by encapsulating an id like this `\u003Csome_id>` and are only allowed as a path segment (between two slashes. `\u002F`:\n\n```java\n@DeepLink(\"foo:\u002F\u002Fcereal.com\u002F\u003Ctype_of_cereal>\u002Fnutritional_info\")\npublic static Intent intentForNutritionalDeepLinkMethod(Context context) {\n  return new Intent(context, MainActivity.class)\n      .setAction(ACTION_DEEP_LINK_METHOD);\n}\n```\n\nIf you do this you do have to provide a mapping (at runtime) for which values are allowed for creating a match. This is done when you new the `DeeplinkDelegate` class like:\n\n```java\n@DeepLinkHandler({ AppDeepLinkModule.class, LibraryDeepLinkModule.class })\npublic class DeepLinkActivity extends Activity {\n  @Override protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    \u002F\u002F Configure a map for configurable placeholders if you are using any. If you do a mapping\n    \u002F\u002F has to be provided for that are used\n    Map configurablePlaceholdersMap = new HashMap();\n    configurablePlaceholdersMap.put(\"type_of_cereal\", \"\u002Fobamaos\");\n    \u002F\u002F DeepLinkDelegate, LibraryDeepLinkModuleRegistry and AppDeepLinkModuleRegistry\n    \u002F\u002F are generated at compile-time.\n    DeepLinkDelegate deepLinkDelegate = \n        new DeepLinkDelegate(new AppDeepLinkModuleRegistry(), new LibraryDeepLinkModuleRegistry(), configurablePlaceholdersMap);\n    \u002F\u002F Delegate the deep link handling to DeepLinkDispatch. \n    \u002F\u002F It will start the correct Activity based on the incoming Intent URI\n    deepLinkDelegate.dispatchFrom(this);\n    \u002F\u002F Finish this Activity since the correct one has been just started\n    finish();\n  }\n}\n```\n\nThis app will now match the Url `foo:\u002F\u002Fcereal.com\u002Fobamaos\u002Fnutritional_info` to the `intentForNutritionalDeepLinkMethod` method for that app.\nIf you build another app and set `type_of_cereal` to `captnmaccains` that apps version of the `intentForNutritionalDeepLinkMethod` would be called when when opening `foo:\u002F\u002Fcereal.com\u002Fcaptnmaccains\u002Fnutritional_info`\n\nIf you are using configurable path segment placeholders, a mapping has to be provided for every placeholder used. If you are missing one the app will crash at runtime.\n\nNote: Because `\"\"` is a valid value for a mapping you need to provide the leading `\u002F` in the mapping as well if it is not empty (as the whole path segment will get removed if the mapping is empty).\ne.g. in this example `type_of_cereal` set to `\"\"` will match `foo:\u002F\u002Fcereal.com\u002Fnutritional_info`\n\n#### Empty configurable path segment placeholders mapping\n\nA mapping can be to an empty string, in that case the element is just ignored. In the above example if `configurablePlaceholdersMap.put(\"type_of_cereal\", \"\");` is defined `foo:\u002F\u002Fcereal.com\u002Fnutritional_info` would map to calling the `intentForNutritionalDeepLinkMethod` method. An empty configurable path segment placeholder is not allowed as the last path element in an URL!\n\n### Callbacks\n\nYou can optionally register a `BroadcastReceiver` to be called on any incoming deep link into your\napp. DeepLinkDispatch will use `LocalBroadcastManager` to broadcast an `Intent` with any success\nor failure when deep linking. The intent will be populated with these extras:\n\n* `DeepLinkHandler.EXTRA_URI`: The URI of the deep link.\n* `DeepLinkHandler.EXTRA_SUCCESSFUL`: Whether the deep link was fired successfully.\n* `DeepLinkHandler.EXTRA_ERROR_MESSAGE`: If there was an error, the appropriate error message.\n\nYou can register a receiver to receive this intent. An example of such a use is below:\n\n```java\npublic class DeepLinkReceiver extends BroadcastReceiver {\n  private static final String TAG = \"DeepLinkReceiver\";\n\n  @Override public void onReceive(Context context, Intent intent) {\n    String deepLinkUri = intent.getStringExtra(DeepLinkHandler.EXTRA_URI);\n    if (intent.getBooleanExtra(DeepLinkHandler.EXTRA_SUCCESSFUL, false)) {\n      Log.i(TAG, \"Success deep linking: \" + deepLinkUri);\n    } else {\n      String errorMessage = intent.getStringExtra(DeepLinkHandler.EXTRA_ERROR_MESSAGE);\n      Log.e(TAG, \"Error deep linking: \" + deepLinkUri + \" with error message +\" + errorMessage);\n    }\n  }\n}\n\npublic class YourApplication extends Application {\n  @Override public void onCreate() {\n    super.onCreate();\n    IntentFilter intentFilter = new IntentFilter(DeepLinkHandler.ACTION);\n    LocalBroadcastManager.getInstance(this).registerReceiver(new DeepLinkReceiver(), intentFilter);\n  }\n}\n```\n\n### Custom Annotations\n\nYou can reduce the repetition in your deep links by creating custom annotations that provide\ncommon prefixes that are automatically applied to every class or method annotated with your custom\nannotation. A popular use case for this is with web versus app deep links:\n\n```java\n\u002F\u002F Prefix all app deep link URIs with \"app:\u002F\u002Fairbnb\"\n@DeepLinkSpec(prefix = { \"app:\u002F\u002Fairbnb\" })\n\u002F\u002F When using tools like Dexguard we require these annotations to still be inside the .dex files\n\u002F\u002F produced by D8 but because of this bug https:\u002F\u002Fissuetracker.google.com\u002Fissues\u002F168524920 they\n\u002F\u002F are not so we need to mark them as RetentionPolicy.RUNTIME.\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface AppDeepLink {\n  String[] value();\n}\n```\n\nYou can use placeholders (like in paths) in the scheme and host section of prefixes listed in the\n`DeepLinkSpec`. e.g. if you want to match both http and https you can define it like this:\n\n```java\n\u002F\u002F Match all deeplinks which a scheme staring with \"http\".\n@DeepLinkSpec(prefix = { \"http{url_scheme_suffix}:\u002F\u002Fairbnb.com\")\n@Retention(RetentionPolicy.CLASS)\npublic @interface WebDeepLink {\n  String[] value();\n}\n```\n\nYou will get the value of `url_scheme_suffix` which -- in this case would be \"\" for http and \"s\"\nwhen https is used -- in the extras bundle of your annotated method. If you want to limit which\nvalues are accepted, you can do that directly within the placeholder by defining it with allowed\nvalues like this: `http{url_scheme_suffix(|s)}:\u002F\u002Fairbnb.com`. In this case valid values would be\n`\"\"` and `\"s\"` (`http` and `https`). Values are pipe(`|`) separated, there can only be one `(...)`\nsection per placeholder and it has to be at the end of the placeholder.\n\n```java\n\u002F\u002F Match all deeplinks which a scheme staring with \"http\".\n@DeepLinkSpec(prefix = { \"http{url_scheme_suffix(|s)}:\u002F\u002F{prefix(|www.)}airbnb.{domain(com|de)}\")\n@Retention(RetentionPolicy.CLASS)\npublic @interface WebDeepLink {\n  String[] value();\n}\n```\n\nThe above code would match URLs that start with `http` or `https`, are for `airbnb.com` or\n`airbnb.de` or `www.airbnb.com` and `www.airbnb.de`. They would e.g. not match `airbnb.ro`.\n\n```java\n\u002F\u002F This activity is gonna handle the following deep links:\n\u002F\u002F \"app:\u002F\u002Fairbnb\u002Fview_users\"\n\u002F\u002F \"http:\u002F\u002Fairbnb.com\u002Fusers\"\n\u002F\u002F \"http:\u002F\u002Fairbnb.com\u002Fuser\u002F{id}\"\n\u002F\u002F \"https:\u002F\u002Fairbnb.com\u002Fusers\"\n\u002F\u002F \"https:\u002F\u002Fairbnb.com\u002Fuser\u002F{id}\"\n@AppDeepLink({ \"\u002Fview_users\" })\n@WebDeepLink({ \"\u002Fusers\", \"\u002Fuser\u002F{id}\" })\npublic class CustomPrefixesActivity extends AppCompatActivity {\n    \u002F\u002F...\n}\n```\n\nThis can be very useful if you want to use it with country prefxes in hostnames e.g.\n\n```java\n\u002F\u002F Match all deeplinks that have a scheme starting with http and also match any deeplink that\n\u002F\u002F starts with .airbnb.com as well as ones that are only airbnb.com\n@DeepLinkSpec(prefix = { \"http{url_scheme_suffix}:\u002F\u002F{country_prefix}.airbnb.com\",\n \"http{url_scheme_suffix}:\u002F\u002Fairbnb.com\")\n@Retention(RetentionPolicy.CLASS)\npublic @interface WebDeepLink {\n  String[] value();\n}\n```\n\nwhich saves you from defining a lot prefixes.\n\n**Note:** if using incremental annotation preocessing all custom deeplinks **must** be configured in gradle. Otherwise they do not work! See: [Incremental annotation processing](https:\u002F\u002Fgithub.com\u002Fairbnb\u002FDeepLinkDispatch#incremental-annotation-processing)\n\n## Usage\n\nAdd to your project `build.gradle` file (Latest version is [![DeeplinkDispatch version](https:\u002F\u002Fbadge.fury.io\u002Fgh\u002Fairbnb%2FDeepLinkDispatch.svg)](https:\u002F\u002Fbadge.fury.io\u002Fgh\u002Fairbnb%2FDeepLinkDispatch)\n): \n```groovy\ndependencies {\n  implementation 'com.airbnb:deeplinkdispatch:x.x.x'\n}\n```\n\nDeeplinkDispatch supports three ways to run the annotation processor dependin on which one you choose\nthe setup is slightly different.\n\n### KSP\n\nWhen using Kotlin we strongly suggest to use KSP as it can bring major speed improvements.\n\nTo run the processor via KSP you first have to apply the KSP plugin. Add the dependency to the\n`build.gradle` file of your main project:\n\n```groovy\nbuildscript {\n\n    apply from: rootProject.file(\"dependencies.gradle\")\n\n    repositories {\n        google()\n        gradlePluginPortal()\n    }\n    dependencies {\n        classpath \"com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:\u003Cksp-version>\"\n    }\n}\n```\n\nApply the plugin in the `build.gradle` file of the project you want to use it:\n\n```groovy\nplugins {\n  id(\"com.google.devtools.ksp\")\n}\n```\n\nand don't forget the dependency to the annotation procesor and DeepLinkDispatch itself:\n\n```groovy\ndependencies {\n  implementation 'com.airbnb:deeplinkdispatch:x.x.x'\n  ksp 'com.airbnb:deeplinkdispatch-processor:x.x.x'\n}\n```\n\n**Note:** When using KSP (you have `ksp 'com.airbnb:deeplinkdispatch-processor:x.x.x'` in your dependencies) at least one Kotlin source file *must* be present in the project or no output will be generated!\n\nAs an example the main `sample` app is set up using KSP.\n\n### Kapt\n\nIf your project is already setup for Kotlin the only thing you have to add is the plugin:\n\n```groovy\nplugins {\n  id(\"kotlin-kapt\")\n}\n```\n\nand don't forget the dependency to the annotation procesor and DeepLinkDispatch itself:\n\n```groovy\ndependencies {\n  implementation 'com.airbnb:deeplinkdispatch:x.x.x'\n  kapt 'com.airbnb:deeplinkdispatch-processor:x.x.x'\n}\n```\n\nAs an example the `sample-kapt-library` is set up using Kapt\n\n### Java annotation processor\n\nJust add the dependency to DeepLinkDispatch and to the annotation processor:\n\n```groovy\ndependencies {\n  implementation 'com.airbnb:deeplinkdispatch:x.x.x'\n  annotationProcessor 'com.airbnb:deeplinkdispatch-processor:x.x.x'\n}\n```\n\nAs an example the `sample-library` is set up using the Java annotation processor\n\nWhen this is done, create your deep link module(s) (**new on DeepLinkDispatch v3**). For every class you annotate with `@DeepLinkModule`, DeepLinkDispatch will generate a \"Registry\" class, which contains a registry of all your `@DeepLink` annotations.\n\n```java\n\u002F** This will generate a AppDeepLinkModuleRegistry class *\u002F\n@DeepLinkModule\npublic class AppDeepLinkModule {\n}\n```\n\n**Optional**: If your Android application contains multiple modules (eg. separated Android library projects), you'll want to add one `@DeepLinkModule` class for every module in your application, so that DeepLinkDispatch can collect all your annotations in one \"Registry\" class per module:\n\n```java\n\u002F** This will generate a LibraryDeepLinkModuleRegistry class *\u002F\n@DeepLinkModule\npublic class LibraryDeepLinkModule {\n}\n```\n\n\nCreate any `Activity` (eg. `DeepLinkActivity`) and add it to the `AndroidManifest.xml` (when using ksp and [manifest generation](https:\u002F\u002Fgithub.com\u002Fairbnb\u002FDeepLinkDispatch?tab=readme-ov-file#manifest-generation})):\n\n```xml\n\u003Cactivity\n    android:name=\"com.example.DeepLinkActivity\"\n    android:theme=\"@android:style\u002FTheme.NoDisplay\">\n\u003C\u002Factivity>\n```\n\nin this case the intent filters will be added automatically during build time.\n\nor when not using manifest generation also add the intent filter for your deeplinks\n\n```xml\n\u003Cactivity\n    android:name=\"com.example.DeepLinkActivity\"\n    android:theme=\"@android:style\u002FTheme.NoDisplay\">\n    \u003Cintent-filter>\n        \u003Caction android:name=\"android.intent.action.VIEW\" \u002F>\n        \u003Ccategory android:name=\"android.intent.category.DEFAULT\" \u002F>\n        \u003Ccategory android:name=\"android.intent.category.BROWSABLE\" \u002F>\n        \u003Cdata android:scheme=\"foo\" \u002F>\n    \u003C\u002Fintent-filter>\n\u003C\u002Factivity>\n```\n\nAnnotate your `DeepLinkActivity` with `@DeepLinkHandler` and provide it a list of `@DeepLinkModule` annotated class(es):\n\n```java\n@DeepLinkHandler({ AppDeepLinkModule.class, LibraryDeepLinkModule.class })\npublic class DeepLinkActivity extends Activity {\n  @Override protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    \u002F\u002F DeepLinkDelegate, LibraryDeepLinkModuleRegistry and AppDeepLinkModuleRegistry\n    \u002F\u002F are generated at compile-time.\n    DeepLinkDelegate deepLinkDelegate = \n        new DeepLinkDelegate(new AppDeepLinkModuleRegistry(), new LibraryDeepLinkModuleRegistry());\n    \u002F\u002F Delegate the deep link handling to DeepLinkDispatch. \n    \u002F\u002F It will start the correct Activity based on the incoming Intent URI\n    deepLinkDelegate.dispatchFrom(this);\n    \u002F\u002F Finish this Activity since the correct one has been just started\n    finish();\n  }\n}\n```\n\nor\n\n```java\n@DeepLinkHandler({ AppDeepLinkModule.class, LibraryDeepLinkModule.class })\npublic class DeepLinkActivity extends Activity {\n  @Override protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    \u002F\u002F Configure a map for configurable placeholders if you are using any. If you do a mapping\n    \u002F\u002F has to be provided for that are used\n    Map configurablePlaceholdersMap = new HashMap();\n    configurablePlaceholdersMap.put(\"your_values\", \"what should match\");\n    \u002F\u002F DeepLinkDelegate, LibraryDeepLinkModuleRegistry and AppDeepLinkModuleRegistry\n    \u002F\u002F are generated at compile-time.\n    DeepLinkDelegate deepLinkDelegate = \n        new DeepLinkDelegate(new AppDeepLinkModuleRegistry(), new LibraryDeepLinkModuleRegistry(), configurablePlaceholdersMap);\n    \u002F\u002F Delegate the deep link handling to DeepLinkDispatch. \n    \u002F\u002F It will start the correct Activity based on the incoming Intent URI\n    deepLinkDelegate.dispatchFrom(this);\n    \u002F\u002F Finish this Activity since the correct one has been just started\n    finish();\n  }\n}\n```\n\nif you use configurable path segments\n\n### Upgrading\n\nWhen upgrading to 5.x+ you may experience some breaking API changes. Read about them [here](UPGRADING.md).\n\n### Incremental annotation processing\n\nYou must update your build.gradle to opt into incremental annotation processing. When enabled, all custom deep link annotations must be registered in the build.gradle (pipe (`|`) separated), otherwise they will be silently ignored.\n\nExamples of this configuration are as follows: \n\n**Standard**\n```groovy\njavaCompileOptions {\n  annotationProcessorOptions {\n    arguments = [\n      'deepLink.incremental': 'true',\n      'deepLink.customAnnotations': 'com.airbnb.AppDeepLink|com.airbnb.WebDeepLink'\n    ]\n  }\n}\n```\n\n**Kotlin kapt**\n```groovy\nkapt {\n  arguments {\n    arg(\"deepLink.incremental\", \"true\")\n    arg(\"deepLink.customAnnotations\", \"com.airbnb.AppDeepLink|com.airbnb.WebDeepLink\")\n  }\n}\n```\n\n**KSP**\n\nKSP is always incremental and you always have to provide the list of `deepLink.customAnnotation` if\nyou have any or they will not be processed.\n\n```groovy\nksp {\n  arg(\"deepLink.incremental\", \"true\")\n  arg(\"deepLink.customAnnotations\", \"com.airbnb.AppDeepLink|com.airbnb.WebDeepLink\")\n}\n```\n\n### Performance\n\nStarting with v5 DeeplinkDispatch is designed to be very fast in resolving deep links even if there are a lot of them. To ensure we do not regress from this benchmark tests using [`androidx.benchmark`](https:\u002F\u002Fdeveloper.android.com\u002Fstudio\u002Fprofile\u002Fbenchmark#top_of_page) were added.\n\nIt is testing the `ScaleTestActivity` in the `sample-benchmarkable-library` which has 2000 deep links. For those on a Pixel 2 running Android 11 we expect the following results:\n\n```text\nStarted running tests\n\nbenchmark:        11,716 ns DeeplinkBenchmarks.match1\nbenchmark:       139,375 ns DeeplinkBenchmarks.match500\nbenchmark:     2,163,907 ns DeeplinkBenchmarks.newRegistry\nbenchmark:        23,035 ns DeeplinkBenchmarks.match1000\nbenchmark:       152,969 ns DeeplinkBenchmarks.match1500\nbenchmark:       278,906 ns DeeplinkBenchmarks.match2000\nbenchmark:       162,604 ns DeeplinkBenchmarks.createResultDeeplink1\nbenchmark:        11,774 ns DeeplinkBenchmarks.parseDeeplinkUrl\nTests ran to completion.\n```\n\nAs you can see it takes us about 2ms to create the registry with 2000 entries. A lookup can be done in sub 1ms usually and `createResult`, which includes the lookup for the `match1` case plus actually calling the method that was annotated, can be done in under 0.2ms.\n\nThe performance tests can be run from Android Studio or via gradle by running `.\u002Fgradlew sample-benchmark:connectedCheck` (with a device connected). The outoput can be found in `sample-benchmark\u002Fbuild\u002Foutputs\u002Fconnected_android_test_additional_output\u002F`.\n\n### Notes\n\n* Starting with DeepLinkDispatch v3, you have to **always** provide your own `Activity` class and annotate it with `@DeepLinkHandler`. It's no longer automatically generated by the annotation processor.\n* Intent filters may only contain a single data element for a URI pattern. Create separate intent filters to capture additional URI patterns.\n* Please refer to the [sample app](https:\u002F\u002Fgithub.com\u002Fairbnb\u002FDeepLinkDispatch\u002Fblob\u002Fmaster\u002Fsample\u002Fsrc\u002Fmain\u002Fjava\u002Fcom\u002Fairbnb\u002Fdeeplinkdispatch\u002Fsample\u002FDeepLinkActivity.java) for an example of how to use the library.\n\nSnapshots of the development version are available in\n[Sonatype's `snapshots` repository](https:\u002F\u002Foss.sonatype.org\u002Fcontent\u002Frepositories\u002Fsnapshots\u002F).\n\nTo access the snapshots add this to your `build.gradle` file:\n```groovy\nrepositories {\n    maven {\n        url \"https:\u002F\u002Foss.sonatype.org\u002Fcontent\u002Frepositories\u002Fsnapshots\u002F\"\n    }\n}\n```\n\n\n### Generated deep links Documentation\n\nYou can tell DeepLinkDispatch to generate text a document with all your deep link annotations, which you can use for further processing and\u002For reference.\nNote: Passing a fully qualified file path string as an argument to any compilation task will cause the cache key to be non-relocateable from one machine to another.\nIn order to do that, add to your `build.gradle` file:\n```groovy\ntasks.withType(JavaCompile) {\n  options.compilerArgs \u003C\u003C \"-AdeepLinkDoc.output=${buildDir}\u002Fdoc\u002Fdeeplinks.txt\"\n}\n```\n\nWhen using Kotlin Kapt\n```groovy\nkapt {\n  arguments {\n    arg(\"deepLinkDoc.output\", \"${buildDir}\u002Fdoc\u002Fdeeplinks.txt\")\n  }\n}\n```\n\nand if you are using KSP\n```groovy\nksp {\n  arg(\"deepLinkDoc.output\", \"${buildDir}\u002Fdoc\u002Fdeeplinks.txt\")\n}\n```\n\nThe documentation will be generated in the following format:\n```\n* {DeepLink1}\\n|#|\\n[Description part of javadoc]\\n|#|\\n{ClassName}#[MethodName]\\n|##|\\n\n* {DeepLink2}\\n|#|\\n[Description part of javadoc]\\n|#|\\n{ClassName}#[MethodName]\\n|##|\\n\n```\n\nYou can also generate the output in a much more readable Markdown format by naming the output file `*.md` (e.g. `deeplinks.md`). Make sure that your Markdown viewer understands tables.\n\n### Matching and edge cases\n\nDeeplink Dispatchs matching algo is designed to match non ambiguous structured URI style data very fast but because of the supported featureset it comes with some edge cases.\n\nWe organize the URI data (of all URIs that are in your app) in a tree structure that is created per module. The URI is dissolved into that tree structure and inserted into that graph at build time. We do not allow duplicates inside the tree at built time and having them will fail the build. However this is currently only guaranteed for each module not across modules.)\n\n![Example of a DeeplinkDispagch match graph](images\u002Fdld_graph.png)\n\nAt runtime we traverse the graph for each module to find the correct action to undertake. The algo just walks the input URI until the last element and *never* backtracks inside the graph. The children of each element are checked for matches in alphabetic order:  \n\n`elements without any variable element -> elements containing placeholders -> elements that are a configurable path segment`\n\n#### Edge cases\n\n* Duplicates can exist between modules. Only the first one found will be reported as a match. Modules are processed in the order the module registries are listed in your `DeepLinkDelegate` creation.\n* Placeholders can lead to duplications at runtime e.g. `dld:\u002F\u002Fairbnb\u002Fdontdupeme` will match both `@Deeplink('dld:\u002F\u002Fairbnb\u002F{qualifier}dupeme')` and `@Deeplink('dld:\u002F\u002Fairbnb\u002Fdontdupeme')`. They can both be defined in the same module as they are not identical. If they are defined in the same module the algo will match `@Deeplink('dld:\u002F\u002Fairbnb\u002Fdontdupeme')` as it would check litereal matches before looking at elements containing placeholders. If they are not defined in the same module the one defined in the registry listed first in `DeeplinkDelegate` will be matched.\n* Configurable path segments can lead to duplicates. e.g. `dld:\u002F\u002Fairbnb\u002Fobamaos\u002Fcereal` will match both  `@Deeplink('dld:\u002F\u002Fairbnb\u002Fobamaos\u002Fcereal\u002F')` and `@Deeplink('dld:\u002F\u002Fairbnb\u002F\u003Cbrand>\u002Fcereal')` if `\u003Cbrand>` is configured to be `obamaos`. The same match rules as mentioned before apply here.\n* Configurable path segments can have empty values e.g. `\u003Cbrand>` can be set to `\"\"` in the previous example. Which would then match `dld:\u002F\u002Fairbnb\u002Fcereal`. If a deeplink like that is defined already somewhere else the same match rules as mentioned before apply to which match actually gets found.\n* Because of limitations of the algo the last path element (the item behind the last slash) cannot  be a configurable path segment with it's value set to `\"\"`. Currently the system will allow you to do this but will not correctly match in that case.\n\n## Proguard\u002FR8 Rules\n\nThe Proguard\u002FR8 rules mandatory for the lib are defined in the [proguard-rules.pro](deeplinkdispatch\u002Fproguard-rules.pro) in `deeplinkdispatch`. However\nthey are already included via `consumerProguardFiles` so there is nothing you have to do to include them.\n\n## Testing the sample app\n\nUse adb to launch deep links (in the terminal type: `adb shell`).\n\nThis fires a standard deep link. Source annotation `@DeepLink(\"dld:\u002F\u002Fexample.com\u002FdeepLink\")`\n\n`am start -W -a android.intent.action.VIEW -d \"dld:\u002F\u002Fexample.com\u002FdeepLink\" com.airbnb.deeplinkdispatch.sample`\n\nThis fires a deep link associated with a method, and also passes along a path parameter. Source annotation `@DeepLink(\"dld:\u002F\u002FmethodDeepLink\u002F{param1}\")`\n\n`am start -W -a android.intent.action.VIEW -d \"dld:\u002F\u002FmethodDeepLink\u002Fabc123\" com.airbnb.deeplinkdispatch.sample`\n\nYou can include multiple path parameters (also you don't have to include the sample app's package name). Source annotation `@DeepLink(\"http:\u002F\u002Fexample.com\u002FdeepLink\u002F{id}\u002F{name}\")`\n\n`am start -W -a android.intent.action.VIEW -d \"http:\u002F\u002Fexample.com\u002FdeepLink\u002F123abc\u002Fmyname\"`\n\n\nNote it is possible to call directly `adb shell am ...` however this seems to miss the URI sometimes so better to call from within shell.\n\n## License\n\n```\nCopyright 2015-2020 Airbnb, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http:\u002F\u002Fwww.apache.org\u002Flicenses\u002FLICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n","DeepLinkDispatch 是一个用于优化Android应用深度链接处理的注解库。它通过简单的注解方式，让开发者能够轻松地为特定的`Activity`注册并处理深度链接。其核心功能包括基于注解的声明式API定义、URI参数自动解析以及与目标`Activity`的智能匹配。此外，DeepLinkDispatch还支持自动生成和维护`AndroidManifest.xml`中的相关条目，简化了多版本URL配置管理。该工具适用于需要提升用户体验的应用场景，尤其是在那些依赖于复杂或多样化的深度链接来启动特定页面或执行特定操作的Android应用程序中。","2026-06-11 03:11:37","top_language"]