DEV Community

Cover image for ๐Ÿ”Œ Build a Native Module in React Native (Battery Level Example for Android & iOS)
Lav Pranjale
Lav Pranjale

Posted on

๐Ÿ”Œ Build a Native Module in React Native (Battery Level Example for Android & iOS)

Ever wonder how libraries like react-native-camera or react-native-device-info access native features?
In this guide, youโ€™ll learn how to create your own native module in React Native โ€” using Kotlin for Android and Swift for iOS โ€” to fetch the battery level of the device.
You'll understand how the React Native bridge works and how JavaScript can talk directly to platform-native code.

๐Ÿš€ What Youโ€™ll Build

We'll build a lightweight but powerful native module called BatteryStatus that retrieves the current battery level of the device.

Itโ€™s a perfect hands-on project to understand how the React Native bridge acts as a translator between your JavaScript and native code โ€” giving you the best of both worlds: cross-platform development and low-level access to device features.

โœ… Pre-requisites Checklist

Make sure the following tools are installed and working:

  • โœ… Node.js & npm
  • โœ… React Native CLI (npx react-native init)
  • โœ… Android Studio (for Android)
  • โœ… Xcode (for iOS on macOS)

โš ๏ธ This tutorial is for React Native CLI projects (not Expo). Expo doesnโ€™t support custom native modules out of the box unless you eject or use Expo Modules API.

๐Ÿ—๏ธ Step 1: Create a New Project

Start by initializing a fresh React Native app:

npx react-native init BatteryModuleApp
cd BatteryModuleApp
Enter fullscreen mode Exit fullscreen mode

Weโ€™ll first create the native module for Android, then move on to iOS.

๐Ÿค– Android: Build the Native Module in Kotlin

๐Ÿงฑ Step 2: Create the BatteryModule.kt Class

๐Ÿ“„ android/app/src/main/java/com/batterymoduleapp/BatteryModule.kt

package com.batterymoduleapp

import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import com.facebook.react.bridge.*

class BatteryModule(reactContext: ReactApplicationContext) :
  ReactContextBaseJavaModule(reactContext) {

  // Module name used to access from JavaScript
  override fun getName(): String = "BatteryStatus"

  @ReactMethod
  fun getBatteryLevel(promise: Promise) {
    try {
      // Get battery info from system intent
      val batteryIntent = reactApplicationContext.registerReceiver(
        null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)
      )
      val level = batteryIntent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
      val scale = batteryIntent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
      val batteryPct = level * 100 / scale

      promise.resolve(batteryPct) // Send value back to JS
    } catch (e: Exception) {
      promise.reject("BATTERY_ERROR", "Failed to get battery level", e)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ฆ Step 3: Register the Module with BatteryPackage.kt

๐Ÿ“„ android/app/src/main/java/com/batterymoduleapp/BatteryPackage.kt

package com.batterymoduleapp

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class BatteryPackage : ReactPackage {
  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
    return listOf(BatteryModule(reactContext))
  }

  override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
    return emptyList()
  }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿงฉ Step 4: Register the Package in MainApplication.kt

๐Ÿ“„android/app/src/main/java/com/batterymoduleapp/MainApplication.kt

override fun getPackages(): List<ReactPackage> {
  return listOf(
    MainReactPackage(),
    BatteryPackage() // ๐Ÿ‘ˆ Register your package here
  )
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ iOS: Build the Native Module in Swift

๐Ÿงฑ Step 5: Create BatteryStatus.swift

๐Ÿ“„ ios/BatteryModuleApp/BatteryStatus.swift

import Foundation
import UIKit

@objc(BatteryStatus)
class BatteryStatus: NSObject {

  @objc
  func getBatteryLevel(_ resolve: @escaping RCTPromiseResolveBlock,
                       rejecter reject: @escaping RCTPromiseRejectBlock) {
    UIDevice.current.isBatteryMonitoringEnabled = true
    let level = UIDevice.current.batteryLevel

    if level < 0 {
      reject("BATTERY_ERROR", "Battery level is unavailable", nil)
    } else {
      resolve(Int(level * 100))
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿง  Whatโ€™s RCTPromiseResolveBlock?

Itโ€™s a bridge-friendly way of returning asynchronous values from native Swift code back to JavaScript.

๐Ÿงฉ Step 6: Bridge Swift to React Native

Create an Objective-C bridging file:

๐Ÿ“„ ios/BatteryModuleApp/BatteryStatus.m

#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(BatteryStatus, NSObject)
RCT_EXTERN_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
@end
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“„ If not already created, add a bridging header file:
BatteryModuleApp-Bridging-Header.h

#import <React/RCTBridgeModule.h>
Enter fullscreen mode Exit fullscreen mode

โš›๏ธ Step 7: Call the Module from JavaScript

๐Ÿ“„ App.js

import React from 'react';
import {NativeModules, Button, Alert, View} from 'react-native';

const {BatteryStatus} = NativeModules;

export default function App() {
  const getBattery = async () => {
    try {
      const level = await BatteryStatus.getBatteryLevel();
      Alert.alert(`Battery Level: ${level}%`);
    } catch (e) {
      Alert.alert('Error fetching battery level');
    }
  };

  return (
    <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
      <Button title="Get Battery Level" onPress={getBattery} />
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿง  This works because React Nativeโ€™s bridge exposes our native BatteryStatus class to JavaScript via NativeModules.

๐Ÿ› ๏ธ Troubleshooting Tips

โ€œNative module cannot be foundโ€ error?

  • Android: Check if BatteryPackage is added to MainApplication.kt
  • iOS: Confirm RCT_EXTERN_MODULE is in place and bridging header is set
  • Try cleaning: cd android && ./gradlew clean or npx react-native clean

iOS build fails on Swift?

  • Make sure youโ€™ve created and linked the Bridging-Header.h correctly
  • Check your targetโ€™s build settings

๐Ÿง  Final Thoughts

Custom native modules unlock the full capabilities of React Native. You can:

  • ๐Ÿ”“ Access low-level features (camera, Bluetooth, sensors)
  • ๐Ÿš€ Improve performance by offloading heavy tasks to native code
  • ๐Ÿ”Œ Integrate native SDKs (payments, health data, media)

And best of all โ€” you gain true cross-platform control while keeping the power of React Nativeโ€™s declarative UI.

Follow me on LinkedIn

Got questions or ideas for improvements? Drop them in the comments! ๐Ÿ’ฌ

Top comments (0)