skip to content

In a bare React Native app, what native wiring lets incoming links reach the Linking module on iOS and on Android?

level: middleimportance: should knowfreq 40%

answer

  1. AppDelegate forwards two callbacks
  2. RCTLinkingManager open url
  3. continue userActivity for https links
  4. launchOptions into startReactNative
  5. MainActivity launchMode singleTask

basics

~10 s

On iOS the AppDelegate forwards the open-URL and continue-user-activity callbacks to RCTLinkingManager and passes launchOptions to startReactNative; on Android, MainActivity uses launchMode singleTask so running-app links arrive through onNewIntent.

solid answer

~40 s

On **iOS**, the app delegate must hand links to `RCTLinkingManager`: `application(_:open:options:)` for custom schemes and `application(_:continue:restorationHandler:)` for Universal Links, each calling the matching `RCTLinkingManager.application(...)` class method, which posts the URL as the `'url'` event. For cold starts, the app must pass its `launchOptions` into `startReactNative(withModuleName:in:launchOptions:)`, because `getInitialURL()` reads the launch URL from them. On **Android**, the manifest's `MainActivity` uses `android:launchMode="singleTask"`, so a link to a running app is delivered to the existing activity's `onNewIntent`, which React Native turns into the `'url'` event; without it Android can start a second activity instance. Cold starts need nothing extra: `getInitialURL()` reads the launching `VIEW` intent. Expo's prebuild templates already contain all of this.

code

swift · 19 lines
swift
func application(
  _ app: UIApplication,
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  return RCTLinkingManager.application(app, open: url, options: options)
}

func application(
  _ application: UIApplication,
  continue userActivity: NSUserActivity,
  restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
  return RCTLinkingManager.application(
    application,
    continue: userActivity,
    restorationHandler: restorationHandler
  )
}

go deeper

for a junior

Recall that iOS needs the AppDelegate to forward links to RCTLinkingManager and that Android's MainActivity uses launchMode singleTask.

for a middle

Explain which delegate callback carries schemes versus Universal Links, why launchOptions feed getInitialURL, and how singleTask routes a warm link through onNewIntent.

for a senior

Diagnose missing links by splitting cold and warm paths per platform, and catch regressions from rewritten AppDelegates, brownfield hosts and splash activities.

for a principal

In brownfield or multi-entry apps, decide which native component owns link intake so JavaScript receives every link exactly once.

## Why native wiring is needed at all `Linking` is a JavaScript facade over two native modules: `RCTLinkingManager` on iOS and the intent module on Android. The operating system delivers URLs to **native** entry points, the app delegate on iOS and the activity on Android. If those entry points do not pass the URL along, JavaScript never hears about it. A newly generated React Native or Expo project has the wiring already; it goes missing when an `AppDelegate` is rewritten, a native app is brownfielded, or a splash activity is added. ## iOS: the app delegate | OS entry point | When it fires | Forward to | |---|---|---| | `application(_:open:options:)` | a custom-scheme URL opens a running app | `RCTLinkingManager.application(app, open: url, options: options)` | | `application(_:continue:restorationHandler:)` | a Universal Link opens a running app | `RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)` | | `launchOptions` in `didFinishLaunchingWithOptions` | the link launches a killed app | pass into `startReactNative(withModuleName:in:launchOptions:)` | Details worth knowing: - The two class methods post a notification that the `RCTLinkingManager` module turns into the `'url'` event. For a user activity it only forwards web-browsing activities that carry a `webpageURL`. - `getInitialURL()` reads the launch options: the URL key for a custom scheme, or the user-activity dictionary for a Universal Link. An app delegate that passes `nil` launch options makes every cold-start link resolve to `null`. - In Expo SDK 57 the generated `AppDelegate` subclasses `ExpoAppDelegate`, passes `launchOptions`, and forwards both callbacks to `RCTLinkingManager` alongside `super`. ## Android: launchMode singleTask On Android the link arrives in an `Intent` with action `VIEW`: 1. **Cold start.** The intent launches `MainActivity`. `getInitialURL()` reads the current activity's intent data when the action is `VIEW` and resolves it. No extra code is needed. 2. **Warm start with `singleTask`.** Android reuses the existing `MainActivity` and calls its `onNewIntent`. `ReactActivity` passes that to the React host, which emits the intent's data as the `'url'` event to the running JavaScript. 3. **Warm start without it.** With the default `standard` launch mode, Android can create a **second** `MainActivity` instance on top of the first. The user sees the app seem to restart, the existing screen's `'url'` listener never fires, and pressing back may reveal the old copy. That is why the React Native docs and Expo's generated manifest set `android:launchMode="singleTask"` on `MainActivity`, and why Expo's scheme config plugin refuses to add schemes unless a `singleTask` activity exists. Two Android traps: - `onNewIntent` does not replace the activity's stored intent in React Native's delegate, so `getInitialURL()` keeps returning the original launch URL; the new link exists only as the event. - A separate splash activity declared as the link target must forward the intent's data to `MainActivity`, or the cold-start link is lost before React Native starts. ## How to check the wiring quickly - Kill the app, tap a link, and log `await Linking.getInitialURL()`. A `null` on iOS points at launch options; on Android, at which activity received the intent. - With the app open, tap a link and log inside the `'url'` handler. Silence on iOS points at the app delegate callbacks; a restart-like flash on Android points at the launch mode. - Test both a custom scheme and an `https` link on iOS, because they arrive through different delegate methods. ## Expo projects In an Expo project with Continuous Native Generation, prebuild writes this wiring for you. The generated `AndroidManifest.xml` gives `MainActivity` `android:launchMode="singleTask"`, and the SDK 57 `AppDelegate` passes `launchOptions` to the factory and forwards both link callbacks. The risk returns when a team edits the native folders by hand, or when a config plugin rewrites the app delegate or manifest: a plugin that replaces `MainActivity`'s attributes or the delegate's link methods can silently remove the forwarding. After adding such a plugin, rerun the checks above on a fresh prebuild. ## What is not wiring Registering schemes (`CFBundleURLTypes`, intent filters) and verifying domains decide whether the OS sends the link to your app at all. The wiring here decides whether a link the OS already delivered reaches JavaScript.

  • Links work when the React Native iOS app is killed but not when it is already open; where do you look?
    The cold path reads launch options, so it works even without delegate forwarding. The warm path needs the app delegate to call `RCTLinkingManager` from `application(_:open:options:)` for schemes and from `application(_:continue:restorationHandler:)` for Universal Links. Check which kind of link fails: a missing continue-user-activity override breaks only `https` links, while a missing open-URL override breaks only custom schemes.
  • Why can a splash activity break cold-start deep links on Android?
    `getInitialURL()` reads the intent of the activity React Native is attached to. If the link's intent filter sits on a splash activity that then starts `MainActivity` with a fresh intent, the data is dropped and `getInitialURL()` resolves to `null`. Either put the filters on `MainActivity` or copy the intent's data and action into the intent that starts it.

saying these in an interview costs you the question

  • Linking works on iOS without any AppDelegate code
  • The continue-userActivity callback is only needed for custom schemes
  • singleTask is needed for cold starts to deliver getInitialURL
  • The default standard launchMode delivers links to the running activity
  • Passing nil launchOptions only affects push notifications