Week 15: Deep Linking & Local Notifications

Week 5 built deep linking with a custom URL scheme, tested from the terminal. This week finishes the job: real https:// links that open the app directly instead of a browser, and local notifications the app schedules itself rather than receiving from a server.

Phase 7 of 8 Week 15 of 22 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Configure Universal Links (iOS) and App Links (Android) on a real domain
  • Extend Week 5's linking config to handle both the custom scheme and real https:// links
  • Schedule a local, time-based notification without any backend involved
  • Test both a universal link and a scheduled local notification end-to-end

2. Wiring It Into Expo & React Navigation

app.json — declaring the associated domain
{
  "expo": {
    "ios": {
      "associatedDomains": ["applinks:myapp.example.com"]
    },
    "android": {
      "intentFilters": [{
        "action": "VIEW",
        "autoVerify": true,
        "data": [{ "scheme": "https", "host": "myapp.example.com" }],
        "category": ["BROWSABLE", "DEFAULT"]
      }]
    }
  }
}
navigation/linking.ts — extending Week 5's config
export const linking: LinkingOptions<RootParamList> = {
  prefixes: ['myapp://', 'https://myapp.example.com'], // both schemes, same config
  config: {
    screens: {
      FeedTab: { screens: { Feed: 'feed', PostDetail: 'post/:postId' } },
      ProfileTab: 'profile',
    },
  },
};

The screens mapping doesn't change at all from Week 5 — adding the real https:// prefix alongside the custom scheme is enough for both to resolve to the same routes. A new native build is required after editing app.json's native config, the same as any native-config change.

3. Local & Scheduled Notifications

A local notification is scheduled entirely on-device — no push token, no backend, no FCM/APNs round trip — good for reminders, timers, and anything the app itself can predict in advance.

scheduling a notification 1 hour from now
import * as Notifications from 'expo-notifications';

async function scheduleReminder(postId: string, title: string) {
  await Notifications.scheduleNotificationAsync({
    content: {
      title: 'Still thinking about this?',
      body: title,
      data: { postId }, // read by the same response listener as Week 14
    },
    trigger: { seconds: 60 * 60 },
  });
}

Because it carries the same data shape as a push notification, the tap handler built in Week 14 (addNotificationResponseReceivedListener) handles a tapped local notification with no additional code — it can't tell, and doesn't need to, which kind of notification it was.

4. Hands-on Exercise

Hands-on

Finish the deep linking story

Extend Week 5's app with real Universal/App Links and a scheduled local notification.

Requirements:

  1. Host (or simulate hosting, if you don't have a domain yet) the apple-app-site-association and assetlinks.json files with the correct content and content type.
  2. Add associatedDomains (iOS) and an intent filter (Android) to app.json, then produce a fresh native build.
  3. Extend the linking config from Week 5 with the real https:// prefix, without changing the screens mapping.
  4. Add a "Remind me later" button on the PostDetail screen that schedules a local notification carrying that post's ID.
  5. Confirm tapping the scheduled local notification navigates to the correct post, using the same response listener from Week 14.
Hint

If you don't have a real domain to test Universal Links against, it's completely reasonable to build and verify everything except the live domain verification step — get the app.json config, the linking table, and the local-notification scheduling all working, and treat hosting the two verification files as a deployment-time step rather than a blocker to finishing the rest.

5. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

What do apple-app-site-association and assetlinks.json actually prove to each platform?

They prove domain ownership — that whoever controls the app also controls the web server at that domain — which is what each platform requires before it will trust the app to intercept https:// links to that domain instead of opening them in a browser. Without this verification, a real https link always falls through to the browser.

Q2

Why doesn't the React Navigation screens mapping need to change between the custom-scheme link from Week 5 and the real https:// link this week?

The screens config maps a path shape (like post/:postId) to a screen — it's independent of which URL prefix led there. Adding https://myapp.example.com alongside myapp:// in prefixes is enough; both resolve through the same route table.

Q3

What makes a local notification different from the push notification built in Week 14?

A local notification is scheduled entirely on-device by the app itself — no push token, no backend, no FCM/APNs delivery — and fires based on a time trigger the app set. A push notification requires a backend to send it, through Expo's push service or FCM/APNs, to a specific token.

Q4

Why does the notification-tap handler from Week 14 work unmodified for a tapped local notification?

addNotificationResponseReceivedListener fires for any tapped notification with the same response shape, and a local notification's data payload is structured identically to a push notification's — the handler reads data.postId the same way either time, with no way (and no need) to distinguish which kind of notification triggered it.