My remittance comparison app is built with React Native and Expo. I built it alone, with no professional coding background, and recently released it on Google Play.
I opened the production version expecting to celebrate.
Instead:
Tap icon → splash screen → app closes.
Every single time.
Naturally, I assumed the worst part of my codebase was responsible. My App.js is around 9,000 lines. Yes, I know. I am a non-developer who kept adding features until one file became the entire company.
So I started investigating.
First, I suspected the AdMob banner initialization. I moved the initialization into a useEffect, wrapped it in .catch(), and tried to make the JavaScript side as defensive as possible.
The app still crashed.
Then I found CRYPTO_CODES being referenced around 546 lines before its declaration. For a moment, I thought I had found a classic JavaScript temporal dead zone error:
ReferenceError: Cannot access 'CRYPTO_CODES' before initialization
But the reference was inside a function. The function only ran after the constant had already been initialized.
False alarm.
At that point, I went far beyond what I expected to do as a non-coder.
I created a Node test harness to simulate my module loading in production mode with:
__DEV__ = false
I stubbed React Native, Expo, Supabase, native modules, and even every image loaded with require().
I wanted to answer one question:
Does the top level of my App.js actually crash when the production bundle loads?
It did not.
The module loaded successfully.
The first render also completed successfully.
My ugly 9,000-line JavaScript file was, at least mechanically, innocent.
The real clue came from the Android crash log:
Invalid application ID MobileAdsInitProvider.attachInfo
The failure was happening inside the Google Mobile Ads native initialization process.
That meant the app was crashing before my JavaScript had a chance to run.
The problem was not my React component, my proxy code, my exchange APIs, or the giant collection of arrays and functions inside App.js.
It was the native AdMob configuration generated from app.json.
The production build did not have a valid AdMob application ID correctly reflected in the Android manifest.
That also explained why this JavaScript code could not save the app:
MobileAds() .initialize() .catch((error) => { console.log('MobileAds initialization failed:', error); });
A JavaScript .catch() can only handle an error after the JavaScript runtime is running.
This crash happened earlier, while Android was initializing the native Ads SDK.
You cannot catch a native startup crash from the JavaScript layer.
I corrected the AdMob plugin configuration in app.json, verified that the Android application ID used the proper AdMob App ID format, and also added the Android advertising ID permission explicitly while cleaning up the configuration:
{ "plugins": [ [ "react-native-google-mobile-ads", { "androidAppId": "ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy" } ] ], "android": { "permissions": [ "com.google.android.gms.permission.AD_ID" ] } }
The important distinction is:
The AdMob App ID uses ~
The banner Ad Unit ID uses /
For example:
App ID: ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy Banner ID: ca-app-pub-xxxxxxxxxxxxxxxx/zzzzzzzzzz
Mixing those up, or failing to generate the App ID correctly in the Android manifest, can cause the Ads SDK to fail before the app reaches JavaScript.
The main lessons I learned:
An instant crash on launch may be native, not JavaScript.
When the app dies before the first screen appears, do not immediately spend hours reading React components.
adb logcat is your best friend.
One native stack trace can be more useful than reading thousands of lines of JavaScript.
A successful build does not mean the native configuration is valid.
An AAB can build successfully, upload successfully, and still crash immediately in production.
JavaScript error handling cannot catch every failure.
If Android crashes while loading a native provider, your try/catch and Promise .catch() blocks never get a chance.
The smallest configuration mistake can hide behind the largest file.
I was convinced my messy codebase was responsible. The real issue was a native configuration entry outside App.js.
I am rebuilding and releasing the corrected version now.
Hopefully, the next screenshot is the app actually opening instead of another crash log.
The app, for anyone curious about a non-coder building an FX, remittance, and crypto fee comparison product:
https://play.google.com/store/apps/details?id=com.jkim1285.TransferIQMobile
Has anyone else spent hours debugging the completely wrong file?