Android bugs in React Native are different from iOS bugs. On iOS, most issues are either a JavaScript error (which shows a red screen) or a CocoaPods linking problem (which fails at build time). On Android, you get a third category: native crashes that produce a stack trace pointing to C++ code inside the React Native runtime, with no obvious connection to your JavaScript.
Over the past few years, I've developed a set of debugging patterns that I use on every project. None of these are secret knowledge. They're just the things that actually help, distilled from the things I tried that didn't.
Chrome DevTools for JavaScript
The classic approach. In the Metro terminal, press d to open the developer menu, then select "Debug with Chrome." This opens a Chrome tab connected to your app's JavaScript runtime.
The console works. Breakpoints work. The network tab doesn't work because network requests go through the native layer, not through Chrome's network stack.
For most JavaScript bugs, this is enough. Set a breakpoint, inspect variables, step through the code. The limitation is that it runs JavaScript in Chrome's V8 engine rather than the device's engine (Hermes or JSC), so timing-sensitive bugs may not reproduce.
Flipper for network inspection
Flipper is Meta's debugging tool for mobile apps. The React Native integration gives you:
- Network inspector: every HTTP request and response, including headers, bodies, and timing. This is what Chrome DevTools' network tab can't do for React Native.
- Layout inspector: a visual hierarchy of the native view tree. Useful for debugging layout issues that only appear on specific Android devices.
- React DevTools integration: component tree and props inspection.
- Database inspector: if you use SQLite or AsyncStorage, you can browse the data directly.
The Flipper plugins worth installing beyond the defaults:
- react-native-performance: shows JS frame rate, UI frame rate, and component render times. Essential for diagnosing jank.
- redux-debugger: if you use Redux, this is better than Redux DevTools because it runs without the Chrome debugger.
Setting up Flipper requires adding a few lines to the Android native code, but recent React Native versions include it by default.
Logcat with proper filtering
Android's logcat is a firehose. Every process on the device logs to it, and the default output is thousands of lines per second. The key is filtering.
adb logcat *:E
This shows only error-level messages. For React Native crashes, filter by the ReactNative tag:
adb logcat ReactNative:V ReactNativeJS:V *:S
The *:S at the end silences everything except the tags you specified. This gives you only React Native native layer messages and JavaScript console output.
For native crashes, the tag to watch is AndroidRuntime:
adb logcat AndroidRuntime:E *:S
This captures fatal exceptions with full stack traces. When you see a crash in production that you can't reproduce, this is how you find the stack trace.
In Android Studio, the logcat panel has a search bar that supports regex filtering. For persistent debugging sessions, this is more convenient than the terminal.
ADB commands for daily use
# List connected devices
adb devices
# Install an APK
adb install -r app-debug.apk
# Reverse port forwarding (needed for Metro on physical devices)
adb reverse tcp:8081 tcp:8081
# Take a screenshot
adb exec-out screencap -p > screenshot.png
# Record the screen
adb shell screenrecord /sdcard/recording.mp4
# Ctrl+C to stop, then:
adb pull /sdcard/recording.mp4
# Clear app data (fresh start without reinstalling)
adb shell pm clear com.yourapp.package
# Open deep link
adb shell am start -a android.intent.action.VIEW -d "yourapp://screen/123"
The adb reverse command is one I forget constantly. Physical Android devices need it to connect to the Metro bundler running on your development machine. Without it, the app loads but can't find the JavaScript bundle.
Common native crash patterns
"Signal 11 (SIGSEGV)" crashes
These are segmentation faults in the native layer. They usually mean:
- A native module is accessing deallocated memory
- A background thread is updating the UI (this is illegal on Android)
- A native dependency has a version mismatch
The stack trace will point to C++ code. Look for the last frame that mentions a recognizable library name. If it says libreactnativejni.so, the crash is in the React Native bridge. If it says a specific library name, the crash is in that library.
"IllegalStateException: Activity has been destroyed"
This happens when native code tries to interact with an Activity that's no longer active. Common cause: a timer or async operation that fires after the user has navigated away.
The fix is usually a cleanup in useEffect:
useEffect(() => {
const timer = setTimeout(() => {
// This might fire after the component unmounts
doSomething();
}, 5000);
return () => clearTimeout(timer);
}, []);
"java.lang.OutOfMemoryError"
Usually caused by loading too many large images without proper caching and downsizing. On Android, images are loaded at full resolution by default. A 4000x3000 photo takes about 48MB of memory uncompressed.
The fix: use a proper image library like react-native-fast-image that handles caching and resizing at the native layer, and always specify image dimensions.
Build failures after dependency updates
When a native dependency updates and the build fails, the first thing to try:
cd android && ./gradlew clean
cd ..
npx react-native start --reset-cache
If that doesn't work, check for duplicate dependencies:
cd android && ./gradlew app:dependencies
This prints the full dependency tree. Look for version conflicts where two libraries depend on different versions of the same Android library.
The debugging mindset for Android
Android debugging in React Native requires accepting that you're debugging across three layers: JavaScript, the React Native bridge, and native Android. The symptoms appear in one layer, but the cause is often in another.
A JavaScript error that only appears on Android usually points to a platform-specific native behaviour. A native crash that only happens during a specific user flow usually points to a JavaScript-triggered sequence that exercises a native code path.
The tools I have described aren't a sequence. They're a toolkit. The skill is knowing which one to reach for based on the symptom.