React Native Push Notifications on Android: JPush Integration & Setup

React Native Push Notifications on Android: JPush Integration & Setup
COMMENTS ()
Tweet

How we built a production-grade, geo-aware push notification system using Firebase globally and JPush for mainland China

Ever wondered why push notifications sometimes just don’t reach users in China? That’s exactly the problem I ran into. I initially used Firebase Cloud Messaging (FCM) for all push notifications. Globally, it worked perfectly, but Android devices in mainland China were never receiving pushes because Google Mobile Services (GMS) are restricted. On iOS, Firebase relies on Apple Push Notification Service (APNs), which isn’t restricted, so notifications worked seamlessly.

To solve this, we implemented a smart, location aware solution: the app geofences the user at runtime. JPush serves as the alternate (viable) solution for push notifications in China, while Firebase continues to handle all other regions. This dynamic approach ensures that no user misses important notifications, while retaining the existing Firebase setup for iOS.

Integrating JPush presented various problems because its React Native wrapper is only partially maintained, missing critical lifecycle hooks. Relying on auto-linking alone caused silent initialization failures, which meant I had to implement manual native integration on Android to make it reliable.

1. Android JPush Setup

Follow the official JPush React Native GitHub guide for the initial setup.

Problem: On React Native 0.77 (and 0.72+), which uses Kotlin MainApplication.ktInstead of Java, the default auto-linking for JPush often fails to initialize the SDK on Android, causing silent push notification issues. This is especially problematic because JPush’s React Native wrapper is partially maintained and misses critical lifecycle hooks.

Solution: Manually initialize JPush MainApplication.kt to ensure a reliable setup:

try {
    val jpushInterface = Class.forName("cn.jpush.android.api.JPushInterface")

    jpushInterface.getMethod("setDebugMode", Boolean::class.java)
        .invoke(null, true)

    // Init JPush at startup
    jpushInterface.getMethod("init", android.content.Context::class.java)
        .invoke(null, this)

    // Fix: badge count stuck at 5 → allow up to 50 notifications
    jpushInterface.getMethod(
        "setLatestNotificationNumber",
        android.content.Context::class.java,
        Integer.TYPE
    ).invoke(null, this, 50)

} catch (e: Exception) {
    println("JPush init failed: ${e.message}")
}

Outcome: With this manual hook, JPush initializes reliably on Android, ensuring push notifications are delivered consistently and badges behave correctly.

2. Preventing Background Kill: Battery Optimization

Problem: After successfully initializing JPush, I noticed push notifications would still fail intermittently on certain Android devices, especially from Chinese OEMs like Huawei, Xiaomi, Oppo, and Vivo. These manufacturers aggressively kill background services to save battery, and the React Native JPush wrapper does not request REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, meaning the system could silently stop push delivery.

Solution: I implemented a small native module that prompts the user to whitelist the app, preventing aggressive background termination:

Native module (Java):

public class BatteryOptimizationModule extends ReactContextBaseJavaModule {
    public BatteryOptimizationModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @NonNull
    @Override
    public String getName() {
        return "BatteryOptimization";
    }

    @ReactMethod
    public void requestIgnoreBatteryOptimizations() {
        Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
        intent.setData(Uri.parse("package:" + getReactApplicationContext().getPackageName()));
        getReactApplicationContext().startActivity(intent);
    }
}

Register the package in MainApplication.kt:

override fun getPackages(): List<ReactPackage> {
return mutableListOf(
MainReactPackage(),
BatteryOptimizationPackage() // added my custom module
)
}

Call it from JS:

import { NativeModules } from 'react-native';
const { BatteryOptimization } = NativeModules;

BatteryOptimization.requestIgnoreBatteryOptimizations();

Outcome: With this native module, JPush stays alive even under aggressive OEM power policies.

Users must manually accept the prompt; there’s no guaranteed silent bypass.

 

3. Safe Linking & Registration ID Retry

Problems:

  1. After setting up JPush, I noticed that getRegistrationID sometimes returned null, especially for users outside mainland China. JPush registration depends on servers located in China, and if the initial handshake cannot be completed (due to network latency or connection timeouts), the app would fail to retrieve a valid registration ID.
  2. Additionally, React Native auto-linking could inadvertently link iOS pods for JPush even though this project targets Android only, introducing unnecessary dependencies and potential sensitive location permission issues on iOS.

Respective solutions:

Retry with exponential backoff: I implemented a function that retries fetching the registration ID multiple times, waiting a few seconds between attempts. This ensures the internal PushService has enough time to establish a connection

export const getJPushRegistrationID = async (
retries = 3,
initialDelay = 1000
): Promise<string | null> => {
for (let i = 0; i <= retries; i++) {
try {
const id = await new Promise<string | null>((resolve) => {
JPush.getRegistrationID((r) => resolve(r?.registerID?.trim() || null));
});
if (id) return id;
} catch (err) {
console.warn(`Attempt ${i + 1} failed: ${err}`);
}

// Exponential backoff: wait for initialDelay * 2^i milliseconds
if (i < retries) {
const delay = initialDelay * Math.pow(2, i);
await new Promise((res) => setTimeout(res, delay));
}
}

return null;
};

Disable iOS auto-linking: To prevent unnecessary JPush pods (and location permissions) from being included in iOS builds, I created a react-native.config.js at the project root:

module.exports = {
assets: ['./src/assets/fonts/'],
dependencies: {
'jpush-react-native': {
platforms: { ios: null }, // prevent iOS from linking JPush
},
'jcore-react-native': {
platforms: { ios: null }, // prevent iOS from linking JCore
},
},
};

Outcome: These steps ensured reliable registration IDs for all users, regardless of location, and prevented Android-only push logic from impacting iOS builds.

4. Google Play Policy Fix (Location Permissions)

Problem: The bundled JPush AAR (node_modules/jpush-react-native/android/libs/jpush-android-3.2.0.aar) automatically declares several location permissions ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATIONand ACCESS_BACKGROUND_LOCATIONeven if we never use JLocation. Gradle’s manifest merger imports these into the final APK, which caused our app to be flagged and rejected by Google Play for sensitive location access.

Solution: To comply with Google Play policies, I stripped the unwanted permissions at build time by using the tools:node="remove" override. I also added the tools namespace in the manifest.

Implementation:

In android/app/src/main/AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" tools:node="remove"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:node="remove"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" tools:node="remove"/>
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" tools:node="remove"/>
</manifest>

Outcome: By explicitly removing these permissions, the app passed Google Play review without needing to change any push notification functionality, while maintaining full compliance.

I followed this guide to strip the unwanted permissions at build time.

 

Conclusion: Reliable Global Push Delivery

By combining Firebase for international users with JPush for mainland China, we achieved a unified push notification strategy that delivers consistently. The integration remains robust, policy compliant, and production-ready. This approach ensures that notifications reach every user regardless of geography or device constraints while preserving the existing Firebase infrastructure and maintaining a clean, maintainable React Native codebase.

Software Engineer

CALL

USA408 365 4638

VISIT

1301 Shoreway Road, Suite 160,

Belmont, CA 94002

Contact us

Whether you are a large enterprise looking to augment your teams with experts resources or an SME looking to scale your business or a startup looking to build something.
We are your digital growth partner.

Tel: +1 408 365 4638
Support: +1 (408) 512 1812