We will be discussing how we can build an endless react-native process that runs even if the app is killed, suspended, active in the foreground, or when the phone is restarted.
Having a background process can be great when you want some data to be processed or sent to the server periodically. It wasn’t possible earlier to have a background process in React Native using JavaScript code but with their recent HeadlessJS (only supported for Android platforms, for now), we now have a JS runtime available in the background for us to execute the JS code based on our needs. However, it would need to be linked to the Android code natively (using Java).
Let’s get started!
1. Bridging
Our first step is to set up a communication channel between React Native and the native code. Let’s create a module in the native code and link it with the React Native layer. Go to android/app/src/main/java/com/package_name/ and create a new file as ExampleModule.java which will extend ReactContextBaseJavaModule class.
package com.background_process;
import android.content.Intent;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import android.util.Log;
import javax.annotation.Nonnull;
public class ExampleModule extends ReactContextBaseJavaModule {
public static final String REACT_CLASS = "Example";
private static ReactApplicationContext reactContext;
public ExampleModule(@Nonnull ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@Nonnull
@Override
public String getName() {
return REACT_CLASS;
}
@ReactMethod
public void startService() {
this.reactContext.startService(new Intent(this.reactContext, ExampleService.class));
}
@ReactMethod
public void stopService() {
this.reactContext.stopService(new Intent(this.reactContext, ExampleService.class));
}
}
Make sure you import your dependencies right. Methods defined with @ReactMethod are going to be exposed to the React Native layer and the module reference can invoke these functions. We have defined two methods to start and stop a service. A public static variable, REACT_CLASS variable is the name of the module and with which we will be accessing it in our React Native code.
Create a new file in the root directory of React Native as Example.js
You will also need to create a package to make it available inside our JS code. It is done in order for a user to add as many packages as needed.
ExamplePackage.java
package com.background_process;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ExamplePackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(
new ExampleModule(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
2. Create an ExampleService.java to Handle the Task
Our service will be responsible to process the tasks when it is started. We will be making using of an interval and calling a function on those intervals. A Handler instance can be used to process an object and events periodically based on the interval given to it. In order for the React Native layer to know about this, RCTDeviceEventEmitter is used to transfer the events to the React Native layer.
NOTE: In order to have an endless service running, we would need to have that included in the status bar, otherwise the Android OS would clean up the process if memory runs low or if it is consuming too many resources. Keeping it in the status bar would make it as a foreground instance and wouldn’t kill it if resources were constrained. It may take up your battery though if you use/call your functions too frequently.
We would now need to define an entry inside the AndroidManifest.xml
Some of the things are already there which will be covered as we go forward.
Final code for the ExampleService.java (android/app/src/main/java/com/package_name/) is below
package com.background_process;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
import android.app.NotificationManager;
import android.app.NotificationChannel;
import android.os.Build;
import com.facebook.react.HeadlessJsTaskService;
public class ExampleService extends Service {
private static final int SERVICE_NOTIFICATION_ID = 100001;
private static final String CHANNEL_ID = "EXAMPLE";
private Handler handler = new Handler();
private Runnable runnableCode = new Runnable() {
@Override
public void run() {
Context context = getApplicationContext();
Intent myIntent = new Intent(context, ExampleEventService.class);
context.startService(myIntent);
HeadlessJsTaskService.acquireWakeLockNow(context);
handler.postDelayed(this, 300000); // 5 Min
}
};
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
this.handler.removeCallbacks(this.runnableCode);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
this.handler.post(this.runnableCode);
// The following code will turn it into a Foreground background process (Status bar notification)
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentIntent(contentIntent)
.setOngoing(true)
.build();
startForeground(SERVICE_NOTIFICATION_ID, notification);
return START_STICKY_COMPATIBILITY;
}
}
3. Create BootReceiver.java to handle the device restarts
There are tons of events emitted by Android OS and we will be focusing on the event BOOT_COMPLETED which will happen when a device is turned on or restarted. In the case of this event, we would fire up our app background process. BroadcastReceiver will help us to accomplish this. It will look for the specific device reboot event and start our service.
The code:
package com.background_process;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
context.startService(new Intent(context, ExampleService.class));
}
}
We have already defined it inside our AndroidManifest.xml file along with the necessary permissions.
4. Integration
We are now almost done with the integration. Now you will be able to start and stop the background service through JavaScript as shown in the code snippet below:
After clicking on the ‘Start’ button our service will start and you can monitor that in the adb console as:
adb logcat
5. HeadlessJS integration
Till now, we have all the stuff required for the background process integration. Now we need a JS function to run in the background with this background event. For that, we would need to create a class natively in Android which would extend the HeadlessJsTaskService. The code would look something like the following:
It’s all done now. You can execute your code inside this ExampleTask function which will run in the HeadlessJS background runtime periodically at an interval of 5 minutes and you can do anything you want with it, just make sure your interval is not too short which can cause a phone’s battery to drain pretty quickly.
HeadlessJS is currently only supported in Android and is still under development for iOS. Make some noise and let’s get it ported to iOS too!
Application
QED42 team has integrated this into a real-world application which offers real-time GPS tracking and map-based suggestions to help you keep away from infections in your locality.
This application tracks your GPS location and renders it on the map along with users and keeps you informed about their health and symptoms so that you take an informed decision.
Check state wise live health statistics and see how other states are doing.
Feel free to install and use Hibernate as the app doesn’t require any login or signup and we also don’t monitor or store any critical data.
In addition, a user can always turn off your location service from app settings.
Make sure to comment in case of any issues.The React Native core team has been doing some really great things for people to build an app easily. With that, it’s our responsibility to take it forward by contributing to it as much as possible and move this a step ahead. This blog will revolve around how to implement a React Native: endless background process.