DEV Community

Ajmal Hasan
Ajmal Hasan

Posted on

Debugging Native Code in React Native: A Step-by-Step Guide (Android & iOS)

As React Native developers, we're comfortable with JavaScript debugging—Chrome DevTools, console logs, the usual suspects. But native code? That's where things get intimidating.

Here's what I learned about debugging Kotlin/Swift code in React Native apps, and why it's not as scary as you think.

Why Bother with Native Debugging?

  • Third-party library crashes: That cryptic native error? Step through the actual code to see what's breaking.
  • Custom native modules: Debug your bridge code properly instead of littering it with log statements.
  • Performance issues: JavaScript profiling won't show native rendering bottlenecks.
  • Platform-specific bugs: That Android 12-only crash? See exactly where and why it fails.

The Debugging Tools

Android Studio (Kotlin/Java)

Android Studio is your IDE for the Android native side. It's built on IntelliJ, so if you've used WebStorm or other JetBrains tools, the interface will feel familiar.

Key debugging shortcuts:

  • F8 - Step Over (execute current line, move to next)
  • F7 - Step Into (dive into function call)
  • Shift+F8 - Step Out (finish current function, return to caller)
  • F9 - Resume (continue until next breakpoint)
  • Cmd+F8 - Toggle breakpoint

Xcode (Swift/Objective-C)

Xcode is Apple's IDE for iOS development. It has powerful debugging tools, though the keyboard shortcuts differ from Android Studio.

Key debugging shortcuts:

  • F6 - Step Over
  • F7 - Step Into
  • F8 - Step Out
  • Control button (or Ctrl+Cmd+Y) - Continue/Resume
  • Cmd+\ - Toggle breakpoint


Setting Up: My Practice Code

To really learn debugging, I added sample code to both platforms with common patterns you'll encounter. Here's what I created:

Android (MainActivity.kt)

private fun debugBreakpointDemo() {
    val userName = "ReactNativeDev"
    val userAge = 28
    val isActive = true

    val items = listOf("Apple", "Banana", "Cherry", "Date", "Elderberry")

    for ((index, item) in items.withIndex()) {
        Log.d("BreakpointDemo", "Item $index: $item")

        if (index == 2) {
            val specialItem = item.uppercase()
            Log.d("BreakpointDemo", "Special item found: $specialItem")
        }
    }

    val result = performCalculation(10, 20)
    Log.d("BreakpointDemo", "Calculation result: $result")
}

private fun performCalculation(a: Int, b: Int): Int {
    val sum = a + b
    val multiplied = sum * 2
    return multiplied
}
Enter fullscreen mode Exit fullscreen mode

iOS (AppDelegate.swift)

func debugBreakpointDemo() {
    let userName = "ReactNativeDev"
    let userAge = 28
    let isActive = true

    let items = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]

    for (index, item) in items.enumerated() {
        print("Item \(index): \(item)")

        if index == 2 {
            let specialItem = item.uppercased()
            print("Special item found: \(specialItem)")
        }
    }

    let result = performCalculation(a: 10, b: 20)
    print("Calculation result: \(result)")
}

func performCalculation(a: Int, b: Int) -> Int {
    let sum = a + b
    let multiplied = sum * 2
    return multiplied
}
Enter fullscreen mode Exit fullscreen mode

Hands-On: Your First Native Debugging Session

Let me walk you through a practice session. This is how I learned, and it demystified native debugging completely.

Step 1: Set Your First Breakpoint

Android Studio:

  1. Open android/app/src/main/java/.../MainActivity.kt
  2. Find the line val userName = "ReactNativeDev"
  3. Click the gray gutter area to the left of the line number
  4. A red dot appears—that's your breakpoint

Xcode:

  1. Open ios/YourApp/AppDelegate.swift
  2. Find the line let userName = "ReactNativeDev"
  3. Click the gray gutter to the left
  4. A blue arrow appears—that's your breakpoint

Step 2: Run in Debug Mode

Android Studio:

  • Click the bug icon 🪲 in the toolbar (or press Shift+F9)
  • The app launches and immediately pauses at your breakpoint
  • The IDE highlights the current line in blue

Xcode:

  • Press Cmd+R or click the Play button
  • The app pauses at your breakpoint
  • The current line is highlighted with a green overlay

Step 3: Explore the Variables Panel

This is where the magic happens. Look at the bottom of your IDE.

You'll see a "Variables" panel showing:

userName = "ReactNativeDev"
userAge = <not initialized yet>
isActive = <not initialized yet>
Enter fullscreen mode Exit fullscreen mode

Why aren't the others initialized? Because breakpoints pause before executing the line. The line is about to run but hasn't yet.

Step 4: Step Over (F8 or F6)

Press F8 (Android Studio) or F6 (Xcode) a few times. Watch as:

  1. userName appears in variables
  2. Next step: userAge appears
  3. Next step: isActive appears
  4. The list items gets created

You're executing line by line. This is stepping over—you execute the current line and move to the next one without diving into function internals.

Step 5: Step Into a Function (F7)

Keep stepping until you reach the line:

val result = performCalculation(10, 20)
Enter fullscreen mode Exit fullscreen mode

Now instead of pressing F8/F6, press F7.

Boom! You're now inside the performCalculation function. The IDE jumped you to its definition. This is stepping into—diving into the implementation of a function call.

Step over a couple of times inside this function. Watch sum and multiplied appear in the variables panel.

Step 6: Step Out (Shift+F8 or F8)

You've seen enough inside performCalculation. Press Shift+F8 (Android Studio) or F8 (Xcode).

The function completes, and you're back where you called it from. The result variable now contains the return value (60). This is stepping out—finishing the current function and returning to the caller.

Step 7: Resume Execution (F9 or Continue Button)

Press F9 (Android Studio) or click the Continue button (Xcode) to let the app run normally until it hits another breakpoint (or finishes).


Advanced Techniques I Use Daily

Conditional Breakpoints

Right-click a breakpoint and add a condition. For example, only break when index == 2 in a loop. This saved me hours when debugging issues that only occurred on the 100th iteration.

Android Studio: Right-click breakpoint → More → Condition
Xcode: Right-click breakpoint → Edit Breakpoint → Condition

Expression Evaluation

While paused, you can evaluate any expression on the fly.

Android Studio:

  • Select an expression in code
  • Right-click → Evaluate Expression (or Alt+F8)
  • Try: items.filter { it.startsWith("A") }

Xcode:

  • In the debug console, type: po items.filter { $0.hasPrefix("A") }
  • po means "print object"

Call Stack Navigation

The call stack shows how you got to the current line. Click any frame to jump to that context and see its variables. Invaluable when you're deep in a chain of function calls.

Watch Variables

Add variables to your watch list to track them across function calls. They persist even when out of scope.

Android Studio: Right-click variable → Add to Watches
Xcode: Right-click variable → Watch "variableName"


Real-World Example: Debugging a Crash

Last week, my app crashed when users tapped a button. JavaScript logs showed nothing. Here's how native debugging saved me:

  1. Android Studio: Set breakpoint in onClick handler
  2. Run in debug mode, tap the button
  3. Step through the native code
  4. Found the crash: accessing an ArrayList index that didn't exist
  5. The issue: JavaScript sent wrong array length to native

Without stepping through with breakpoints, I'd have spent hours adding log statements and rebuilding.


Tips from the Trenches

Start simple: Don't try to debug react-native core libraries on day one. Practice with your own simple native code first.

Use both IDEs: I have both Android Studio and Xcode open when working on RN. Debug both platforms regularly—they have different bugs.

Combine with JS debugging: Use both Chrome DevTools for JS and native debuggers. Some bugs span both layers.

Read the call stack: When you hit a breakpoint, spend time reading the call stack. Understanding how you got there is as important as where you are.

Don't fear native code: It's just code. Kotlin and Swift are actually quite pleasant languages. The debugging process is identical to JavaScript debugging, just different tools.


Practice Challenge

Here's what I recommend: spend 30 minutes today just stepping through the sample code I showed above. Don't try to fix anything, just explore:

  1. Set breakpoints in loops
  2. Step into and out of functions
  3. Watch variables change
  4. Evaluate expressions
  5. Add conditional breakpoints

This low-pressure practice builds the muscle memory. When you hit a real bug, you'll know exactly what to do.


Resources That Helped Me


Conclusion

Native debugging isn't scary—it's empowering. As React Native developers, we shouldn't fear diving into the native layer. These tools are powerful, learnable, and absolutely essential for professional RN development.

The difference between a good RN developer and a great one? Great ones are comfortable in all three worlds: JavaScript, Android native, and iOS native.

Start small, practice deliberately, and soon you'll wonder how you ever developed without native debugging.

Happy debugging! 🐛🔍


What native debugging techniques have you found helpful? Share your experiences in the comments below!

Top comments (0)