1. Universal Links & App Links
Unlike Week 5's custom scheme, a real https://myapp.example.com/post/42
link opening your app instead of a browser requires each platform to verify
you actually own that domain — done by hosting a small JSON file at a fixed path.
{
"applinks": {
"apps": [],
"details": [
{ "appID": "TEAMID.com.example.myapp", "paths": ["/post/*"] }
]
}
}
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": ["YOUR:APP:SIGNING:CERT:FINGERPRINT"]
}
}]
These files must be served with Content-Type: application/json, over
plain HTTPS, with no redirects — both platforms fetch and cache them at install
time, so a change after the app is already installed can take a while to propagate.
2. Wiring It Into Expo & React Navigation
{
"expo": {
"ios": {
"associatedDomains": ["applinks:myapp.example.com"]
},
"android": {
"intentFilters": [{
"action": "VIEW",
"autoVerify": true,
"data": [{ "scheme": "https", "host": "myapp.example.com" }],
"category": ["BROWSABLE", "DEFAULT"]
}]
}
}
}
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.
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
Finish the deep linking story
Extend Week 5's app with real Universal/App Links and a scheduled local notification.
Requirements:
- Host (or simulate hosting, if you don't have a domain yet) the
apple-app-site-associationandassetlinks.jsonfiles with the correct content and content type. - Add
associatedDomains(iOS) and an intent filter (Android) toapp.json, then produce a fresh native build. - Extend the
linkingconfig from Week 5 with the realhttps://prefix, without changing thescreensmapping. - Add a "Remind me later" button on the
PostDetailscreen that schedules a local notification carrying that post's ID. - Confirm tapping the scheduled local notification navigates to the correct post, using the same response listener from Week 14.
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?
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?
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?
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?
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.