expo
expo copied to clipboard
Branch deeplinks unreliable on iOS
Minimal reproducible example
https://github.com/mersiades/expo-router-branch-mre
Which package manager are you using? (Yarn is recommended)
yarn
If the issue is web-related, please select the bundler (web.bundler
in the app.json
)
None
Summary
The expected behaviour is that when a Branch deep link (eg https://17p1j.app.link/4nUHmYKnzKb) is tapped, it is handled corrected by expo-router
. On the minimum reproducible example, the deep links should route to the "/reset-password" screen and show the correct value for the token
query param. On Android, the expected behaviour is being met; on iOS, it is not.
Steps to reproduce:
- Clone minimum reproducible example (MRE)
-
yarn install
- Set up new Branch.io project with some deep links, alter app.config.js to match new project (Or I could share the key for existing Branch project through secure means, to save you some time)
- Prebuild:
APP_VARIANT=production BRANCH_LIVE_KEY=<key> yarn prebuild
- Run release build on physical device:
yarn build:ios:local:release
The minimum reproducible example has two branches: main
and using-native-intent
.
On the main
branch, Branch is added and configured and used as-is, 'straight out of the box'. On this branch, tapping a deep link leads to the 'not found' screen, because expo-router
it treating the deep link's path (eg, /4nUHmYKnzKb) as the actual path. It's as though it's being passed through without being handled by react-native-branch
first.
On the using-native-intent
branch, I've added +native-intent.ts
file to handle Branch deep links. This uses the branch.getLatestReferringParams()
from the react-native-branch
SDK. AFAIK, this works by:
- Deep link comes in, the Branch iOS module intercepts it.
- Branch iOS module makes an HTTP request to Branch.io, which in essence turns the deep link into useful data. In our case, it returns an object with a
$deeplink_path
field with a value of `/reset-password?token=test1". Obviously, this will be asynchronous and the request time can vary. -
branch.getLatestReferringParams()
is run in the Javascript code, which asks the iOS module for the latest results returned from Branch.io.
Using branch.getLatestReferringParams()
in the +native-intent.ts
file has varying results.
- on a development build, the link is handled and we're routed to the correct screen. However, usually it's routing based on the second-most recent deep link, not the most recent.
- on a release build, we're consistently being routed to the 'not found' screen, with "/no-branch-params" as the pathname, indicating that
branch.getLatestReferringParams()
is returning an empty object. - on my actual project (not the MRE), in release build, where Sentry gives us slightly better insight into what is happening, the results are inconsistent. About 30% of the time
branch.getLatestReferringParams()
is returning the correct data and the link is being handled correctly, but for the other 70%branch.getLatestReferringParams()
returns this object, which does not have the required$deeplink_path
:
{
+clicked_branch_link: false,
+is_first_session: false,
url: https://chatloop.app.link/9iJvE8YMeJb
}
So what is going on? I don't know, but my current thoughts are:
- the inconsistency suggests a race condition, perhaps related to the time it takes the Branch iOS module to fetch the data for the most recent deep link.
- According to the Branch SDK documentation,
branch.getLatestReferringParams()
should be used within a listener (usuallybranch.subscribe()
). WouldredirectSystemPath()
within+native-intent.ts
work as a "listener" in this case?
Best practice for this method is to receive the data from the listener (to prevent a race condition).
- My actual project has been using Branch deep links for several years. They stopped working when I converted to
expo-router
fromreact-navigation
. When usingreact-navigation
, I was able to useLinking
configuration to usebranch.getLatestReferringParams()
within thebranch.subscribe()
listener (like this).
I'll also note that I'm having problems with deferred deep link and branch.getFirstReferringParams()
, which I also suspect is due to the lack of branch.subscribe()
, but I'll leave that out of this issue because it's already plenty big enough.
Appendix: Here are the deep links I've been using:
https://17p1j.test-app.link/zZjzmpLzxFb // test reset test 1
https://17p1j.test-app.link/oiTZTyCsLKb // test reset test 2
https://17p1j.app.link/4nUHmYKnzKb // live reset test 1
https://17p1j.app.link/GLDLy79tLKb // live reset test 2
Environment
expo-env-info 1.2.0 environment info: System: OS: macOS 14.1.2 Shell: 5.9 - /bin/zsh Binaries: Node: 18.19.1 - ~/.nvm/versions/node/v18.19.1/bin/node Yarn: 1.22.17 - ~/.yarn/bin/yarn npm: 10.2.4 - ~/.nvm/versions/node/v18.19.1/bin/npm Watchman: 2024.06.10.00 - /opt/homebrew/bin/watchman Managers: CocoaPods: 1.15.2 - /Users/michael/.rvm/rubies/ruby-3.2.2/bin/pod SDKs: iOS SDK: Platforms: DriverKit 23.4, iOS 17.4, macOS 14.4, tvOS 17.4, visionOS 1.1, watchOS 10.4 Android SDK: API Levels: 28, 29, 30, 31, 32, 33, 34 Build Tools: 29.0.2, 30.0.3, 32.0.0, 32.1.0, 33.0.0, 33.0.1, 34.0.0 System Images: android-30 | Google APIs ARM 64 v8a, android-31 | Google APIs ARM 64 v8a, android-32 | Google APIs ARM 64 v8a, android-VanillaIceCream | Google Play ARM 64 v8a IDEs: Android Studio: 2023.2 AI-232.10300.40.2321.11567975 Xcode: 15.3/15E204a - /usr/bin/xcodebuild npmPackages: expo: ~51.0.14 => 51.0.14 expo-router: ~3.5.16 => 3.5.16 react: 18.2.0 => 18.2.0 react-dom: 18.2.0 => 18.2.0 react-native: 0.74.2 => 0.74.2 react-native-web: ~0.19.10 => 0.19.12 npmGlobalPackages: eas-cli: 10.0.2 Expo Workflow: bare // Not sure why this is saying 'bare'. Maybe because I've been doing local release builds?