fix(react-native-mdocs-holder-tutorial): repair Android debug build - #341
Merged
Merged
Conversation
Remove expo-dev-client, whose kotlinx-datetime 0.7.x requirement is binary incompatible with the 0.6.0 the Holder AAR is built against, sync the stale lockfile, and derive the app scheme from config in withAndroidHolderSDK so renaming the package no longer breaks OAuth.
…starter The starter ships the same withAndroidHolderSDK.js and the same expo-dev-client dependency, so a reader following the tutorial hits the identical kotlinx-datetime conflict and package rename defects once they register the plugin. Its app.config.ts is left untouched, as the differences there are deliberate tutorial scaffolding.
al-abbas-nz
approved these changes
Sep 14, 2026
dylan-paul
approved these changes
Sep 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The completed React Native mDocs Holder tutorial app could not run on Android. A debug build installed successfully and then crashed on startup before the first screen rendered. This fixes that, plus two related defects found while tracing it.
Remove
expo-dev-clientThis is the cause of the startup crash. The app died during React Native TurboModule initialization with:
The native Holder AAR (
global.mattr.mobilecredential:holder:7.1.0, pinned by RN Holder SDK 10.0.0) requestskotlinx-datetime:0.6.0and its bytecode contains a hard reference tokotlinx.datetime.Clock$System.expo-dev-launcher, pulled in transitively byexpo-dev-client, declareskotlinx-datetime:0.7.1as adebugOnlydependency. Gradle resolves version conflicts to the highest version, so 0.7.1 wins.That upgrade is binary incompatible. In 0.7.x,
kotlinx.datetime.Clockwas demoted to a deprecated typealias forkotlin.time.Clock, and a typealias has no runtime class. The 0.6.0 jar shipskotlinx/datetime/Clock.classandClock$System.class; the 0.7.1 jar ships neither. The AAR's reference therefore resolves to nothing.The two versions cannot coexist. Only one can be on an Android runtime classpath, and forcing 0.6.0 instead breaks
:expo-dev-launcher:compileDebugKotlin, because itsDateFormat.ktcalls the format builder methodday(), which only exists in 0.7.x (0.6.0 hasdayOfMonth()). There is also no newer Holder SDK to move to: 10.0.0 is the latest published and it pins native Android 7.1.0.Since
expo-dev-clientis the only package pulling inexpo-dev-launcher, and nothing in the app source imports it, removing it is the one option that leaves both libraries at a version they were compiled against.expo run:androidstill produces a development build with Metro and fast refresh. Only the dev launcher UI for switching dev servers is lost, which a tutorial reader running a single project does not need.Because the conflict is scoped
debugOnly, release builds were always unaffected. This only ever broke the debug builds that anyone following the tutorial would produce.Derive the app scheme from config in
withAndroidHolderSDK.jsThe plugin hardcoded
io.mattrlabs.sample.reactnativemobilecredentialholdertutorialappin two places rather than readingconfig.android.package. The tutorial instructs readers to change the bundle identifier, and doing so silently broke two things:WebCallbackActivitystayed registered on the original scheme, so OAuth redirects arrived on a scheme nothing was listening for and the credential offer flow failed with no error pointing at the cause.Both now read the package from config, so a rename stays consistent across the generated manifest.
Register the development launch deep link
With
expo-dev-clientremoved,expo run:androidbuilt and installed the APK and then failed at the final step:Expo CLI builds its launch URL from the longest scheme in the manifest (
resolveExpoOrLongestScheme), which is the Android package. That scheme was only registered withandroid:host="accept", so the CLI'sexpo-development-clientintent matched nothing.expo-dev-client's own config plugin used to register a host-less filter that absorbed this, which is why the gap only became visible once it was removed.The plugin now registers that single host on MainActivity. Scoping it to
expo-development-clientspecifically, rather than restoring a host-less catch-all, keepsexpo run:androidworking without letting MainActivity intercept the OAuth callback URLs thatWebCallbackActivityneeds.Sync
yarn.lockpackage.jsonalready pinned@mattrglobal/mobile-credential-holder-react-native@^10.0.0, but the committed lockfile still resolved 8.1.2. Anyone installing from the lockfile got a Holder SDK two majors behind the manifest. The lockfile now matches.The same fixes in the starter app
The starter ships a byte-identical
withAndroidHolderSDK.jsand the sameexpo-dev-clientdependency and stale lockfile, so the three changes above apply to it unchanged. Itsapp.config.tsis deliberately left alone, because the differences there are tutorial scaffolding rather than defects.The starter does behave differently in its shipped state, which affects when each problem surfaces rather than whether it exists:
withAndroidHolderSDK.jsis present but not yet listed inplugins; the reader registers it partway through the tutorial. Until then none of its mods run, and Android cannot resolve the Holder AAR at all, since the Maven repository injection is what makes it resolvable.schemeis still the default and there are nointentFilters, so the longest scheme in the generated manifest is host-less and the CLI's launch deep link resolves. The unresolved intent error only appears once the reader updates the scheme and adds the intent filters, at which point the app matches the completed sample exactly.In other words, a reader who follows the tutorial to the end lands in precisely the state that was broken, so leaving the starter unfixed would just defer the same three failures.
No other sample app in this repository combines
expo-dev-clientwith a MATTR mDocs SDK, so the scope ends here.Testing
Verified on a physical Android device (Samsung SM-A057F, Android 15):
kotlinx-datetimeresolves to 0.6.0 ondebugRuntimeClasspath, with no conflict to resolve and noforceoverrideNoClassDefFoundErrorand no reference tokotlinx.datetimeormobilecredentialaccept,credentials, andexpo-development-clienthosts, under both the default package name and a renamed oneexpo run:androidcompletes without the unresolved intent error🤖 Generated with Claude Code