react native voip push notification app doesn't launch after accepting a call

487 views Asked by At

So I'm currently working on an app that implements VoIP calls on iOS, to do this I used RNCallKeep and RNVoipPushNotification. I manage to handle all the states including the app in foreground and background but the thing is that when the app is in Killed state or background state and a VoIP call comes to the device after I tap the answer call is accepted in the background. but app doesn't launch

AppDelegate fill

#import "AppDelegate.h"
#import <Firebase.h>
#import "RNNotifications.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import "RNCallKeep.h"
#import <PushKit/PushKit.h>                 
#import "RNVoipPushNotificationManager.h"

#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>

static void InitializeFlipper(UIApplication *application) {
  FlipperClient *client = [FlipperClient sharedClient];
  SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
  [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
  [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
  [client addPlugin:[FlipperKitReactPlugin new]];
  [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
  [client start];
}
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
 if ([FIRApp defaultApp] == nil) {
      [FIRApp configure];
    }
#ifdef FB_SONARKIT_ENABLED
  InitializeFlipper(application);
#endif

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];

  [RNCallKeep setup:@{
    @"appName": @"Med360Doctors",
    @"maximumCallGroups": @3,
    @"maximumCallsPerCallGroup": @1,
    @"supportsVideo": @YES,
  }];

  [RNVoipPushNotificationManager voipRegistration];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"Med360Doctors"
                                            initialProperties:nil];

  if (@available(iOS 13.0, *)) {
      rootView.backgroundColor = [UIColor systemBackgroundColor];
  } else {
      rootView.backgroundColor = [UIColor whiteColor];
  }

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  [RNNotifications startMonitorNotifications];
  return YES;
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  [RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  [RNNotifications didFailToRegisterForRemoteNotificationsWithError:error];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
  [RNNotifications didReceiveBackgroundNotification:userInfo withCompletionHandler:completionHandler];
}


- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];

#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

 - (BOOL)application:(UIApplication *)application
 continueUserActivity:(NSUserActivity *)userActivity
   restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler
 {
   return [RNCallKeep application:application
            continueUserActivity:userActivity
              restorationHandler:restorationHandler];
 }

 - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
  // Register VoIP push token (a property of PKPushCredentials) with server
  [RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type];
}

- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type
{
  // --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token.
}

- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {

  NSString *uuid = payload.dictionaryPayload[@"uuid"];
  NSString *displayName = [NSString stringWithFormat:@"%@ Calling from Med360", payload.dictionaryPayload[@"displayName"]];
  NSString *handle = payload.dictionaryPayload[@"handle"];
  // --- this is optional, only required if you want to call `completion()` on the js side
  [RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion];

  // --- Process the received push
  [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];
//  NSDictionary *extra = [payload.dictionaryPayload valueForKeyPath:@"custom.path.to.data"];

  [RNCallKeep reportNewIncomingCall: uuid
                               handle: handle
                           handleType: @"generic"
                             hasVideo: YES
                  localizedCallerName: displayName
                      supportsHolding: YES
                         supportsDTMF: YES
                     supportsGrouping: YES
                   supportsUngrouping: YES
                          fromPushKit: YES
                              payload: nil
                withCompletionHandler: completion];
  
  // --- You don't need to call it if you stored `completion()` and will call it on the js side.
  completion();
}
@end`

RNVoipPushNotification Controller file

import React, { useEffect } from 'react';
    import { useNavigation } from '@react-navigation/native';
    import Incomingvideocall from './components/incomingCallUi/Incomingvideocall';      import VoipPushNotification from "react-native-voip-push-notification";
    

    const NotificationController = () => {
      const navigation = useNavigation();
      useEffect(() => {
        VoipPushNotification.registerVoipToken();
        VoipPushNotification.addEventListener("notification", (notification) => {
         
          const { type } = notification;
          if (type === "Video") {
            const incomingCallAnswer = ({ callUUID }) => {
              navigation.navigate('McoVideoCall', {
                appointment: Object.assign({}, notification),
              });
              Incomingvideocall.endIncomingcallAnswer(callUUID);
              clearTimeout(timer);
              Incomingvideocall.endAllCall();
            };
            const endIncomingCall = () => {
              Incomingvideocall.endAllCall();
            };
            Incomingvideocall.configure(incomingCallAnswer, endIncomingCall);
          } else if (type === "DISCONNECTED") {
            Incomingvideocall.endAllCall();
          }
          VoipPushNotification.onVoipNotificationCompleted(notification.uuid);
        });
    
        VoipPushNotification.addEventListener("didLoadWithEvents", (events) => {
          const  type  =  events.length != 0 && events[1]?.data?.type
          if (type === "Video") {
           
            Incomingvideocall.endAllCall();
            navigation.navigate('VideoScreen', {
              appointment: Object.assign({}, events.length != 0 ? events[1]?.data : {}),
            });
    
            const incomingCallAnswer = ({ callUUID }) => {
              navigation.navigate('VideoScreen', {
                appointment: Object.assign({}, events),
              });
              Incomingvideocall.endAllCall();
              Incomingvideocall.endIncomingcallAnswer(callUUID);
            
            };
    
            const endIncomingCall = () => {
              Incomingvideocall.endAllCall();
            };
    
            Incomingvideocall.configure(incomingCallAnswer, endIncomingCall);
          } else if (type === "DISCONNECTED") {
            Incomingvideocall.endAllCall();
          }
    
        });
    
        return () => {
          VoipPushNotification.removeEventListener("didLoadWithEvents");
          VoipPushNotification.removeEventListener("register");
          VoipPushNotification.removeEventListener("notification");
        };
      }, []);
      return null;
    };
    export default NotificationController;

package.json file

 "dependencies": {
    "@react-native-firebase/app": "^16.4.6",
    "@react-native-firebase/messaging": "^16.4.6",
    "react": "18.2.0",
    "react-native": "0.71.3",
    "react-native-callkeep": "^4.3.12",
    "react-native-voip-push-notification": "^3.3.2",
    "react-native-webrtc": "^1.94.0"
  },

Note:

  • Working in Android fine
  • Working in iPhone8 or iPhone6 and iPhone SE
  • Not Working in iPhone12

Working Call is coming in every state killed, background or foreground but app doesn't launch after accepting a call

2

There are 2 answers

0
Shivo'ham On BEST ANSWER

My problem is solved, I just added this code to the video screen Incomingvideocall.endAllCall();' If we call it with the accept call code then the app will not open because the call terminates the app process so move this line to next screen (Video screen) or show and hide VoIP notification. Manage VoIP accept and reject from the API side

3
famfamfam On

I have faced the same problem, I need to wait for Navigation inited before navigate to CallScreen.

UPDATE:

VoipPushNotification.addEventListener('notification', ...)

this is just notification about when the CallKit show the incomming call, if you want to trigger the event when press answer, you must use RNCallKeep event:

RNCallKeep.addEventListener('answerCall', rnCallKeepAnswerCall)
RNCallKeep.addEventListener('endCall', rnCallKeepEndCall)