Recently weâve encountered a new challenge: add a custom screen for a push notification in an app written in React Native to receive VoIP calls like Facebook Messenger, Whatsapp and Skype are doing.
8/7/2017
Have you ever used React Native? Itâs a framework developed by Facebook that allows you to build native apps with Javascript!
React Native lets you build mobile apps using only JavaScript. It uses the same design as React, letting you compose a rich mobile UI from declarative components.
The first step of the call is a notification. Weâre using OneSignal to send push notifications to the device, and there is a npm package for React Native (https://github.com/geektimecoil/react-native-onesignal) to manage that. Our goal was to build an app to make VoIP calls.
Users are used to receive the call even if the device is locked and the app process is not running. This is simple on iOS. You can use a very powerful system API called PushKit, which basically does all the work for you. In this case an open source library is saving us once again (https://github.com/ianlin/react-native-callkit)!
Thatâs great! But what about Android? Well.. Here comes the trouble! đą
Android doesnât have a system API to handle VoIP call notification, so how do we implement this? We, unfortunately, need to write some Java from scratch. Why did I say unfortunately? Because our app is written in Javascript: all our business logic, networking, screens and other stuffs are already handled by React-native, so we donât want to duplicate the code. We just want to popup a screen that will send an event to Native saying âI have accepted/declined the callâ.
Receive push notifications when the app is not alive
The first step is writing a service which receives push notifications even if the app is not alive. For the sake of simplicity weâll use WakefulBroadcastReceiver in this example, but we suggest using a JobScheduler to detect notifications in a better way.
â
public class PusherReceiver extends WakefulBroadcastReceiver {
public void onReceive(final Context context, Intent intent) {
if (!isAppOnForeground((context))) {
String custom = intent.getStringExtra("custom");
try {
JSONObject notificationData = new JSONObject(custom);
// This is the Intent to deliver to our service.
Intent service = new Intent(context, BackgroundService.class);
// Put here your data from the json as extra in in the intent
// Start the service, keeping the device awake while it is launching.
startWakefulService(context, service);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
The onReceive event will be triggered every time we receive a push from the GCM (remember to add the service to your AndroidManifest.xml). From here weâll start a BackgroundService which basically just starts your React Native activity with an Intent.
public class BackgroundService extends IntentService {
public BackgroundService() {
super("BackgroundService");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Intent i = new Intent(getBaseContext(), MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
if (intent != null) {
startActivity(i);
PusherReceiver.completeWakefulIntent(intent);
}
}
}
Awesome! Now our React Native activity is up and running!
Now we need to show our activity over the lock screen and send the user choice (receive or decline the call) to the app.
The following steps are assuming that you are using some package to handle navigation properly inside your app. We used https://github.com/wix/react-native-navigation which is one of the most supported libs.
Based on this package, we need to extend the NavigationApplication class. This way, we get access to the lifecycle methods of our activities where we can set some flags and start our UnlockScreenActivity.
public class MainApplication extends NavigationApplication {
@Override
public boolean isDebug() {
// Make sure you are using BuildConfig from your own application
return BuildConfig.DEBUG;
}
protected List getPackages() {
// Add additional packages you require here
// No need to add RnnPackage and MainReactPackage
return Arrays.asList(
//new MainReactPackage()
//your react package here
);
}
@Override
public List createAdditionalReactPackages() {
return getPackages();
}
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
if (activity instanceof NavigationActivity) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
}
@Override
public void onActivityResumed(Activity activity) {
if (activity instanceof NavigationActivity && NavigationApplication.instance.getReactGateway().isInitialized()) {
Intent i = new Intent(activity, ScreenUnlockActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(i);
}
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
}
Itâs really important to add the flag on the NavigationActivity, which is the activity generated by react-native-navigation that will be the container of our app. We need to put it over the lock screen, otherwise if you put it only on your âUnlockScreenActivityâ, youâll see the custom screen and after that the phone will turn the screen off again đ
N.B. you need to set these flags before calling setContentView inside the onCreate method. Keep it in mind!
Starting from Android SDK 23, we have access to ConnectionService which is similar to what PushKit does on iOS. Probably you can get a better solution using it.
The activities lifecycles in this article is quite generic. Pay attention where you add the flags on the window in order to be sure to obtain the desired result.
Weâve followed this implementation because we didnât want to duplicate business login and networking code between JS and Java, but maybe there is better way to handle this directly inside React Native đ
â
No items found.
8/7/2017
Un nuovo articolo ogni mese
Non perdere il prossimo! Resta aggiornato sul mondo della progettazione e dello sviluppo dei prodotti digitali.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Iscrivendoti accetti di ricevere la nostra newsletter periodica. Useremo il tuo indirizzo e-mail unicamente per inviarti aggiornamenti sulle nostre attivitĂ . Leggi lâinformativa completa.