Callkit Call Directory Extension not working with React Native

243 Views Asked by At

I want to implement Callkit Call Directory Extension in my React Native App, and created a Bridge too. I've used objective-c to handle CallDirectoryHandler. I have built the extension by following this tutorial, however, I am still struggling with adding and Identifying numbers. I have tried almost every possible solution, but neither of them worked for me. Below are the codes, I am using: CallkitBridge.m

#import "CallkitBridge.h"

#define DATA_KEY @"CALLER_LIST"

#define DATA_GROUP @"group.org.reactn.futurecodes.CallDirectory"

#define EXTENSION_ID @"org.reactn.futurecodes.CallDirectory"

@implementation CallkitBridge

RCT_EXPORT_MODULE()

-(NSError*) buildErrorFromException: (NSException*) exception withErrorCode: (NSInteger)errorCode {
    NSMutableDictionary* info = [NSMutableDictionary dictionary];
    [info setValue:exception.name forKey:@"Name"];
    [info setValue:exception.reason forKey:@"Reason"];
    [info setValue:exception.callStackReturnAddresses forKey:@"CallStack"];
    [info setValue:exception.callStackSymbols forKey:@"CallStackSymbols"];
    [info setValue:exception.userInfo forKey:@"UserInfo"];
    
    NSError *error = [[NSError alloc] initWithDomain:EXTENSION_ID code:errorCode userInfo:info];
    return error;
}

- (NSArray*)getCallerList {
    @try {
        NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:DATA_GROUP];
        NSArray* callerList = [userDefaults arrayForKey:DATA_KEY];
        if (callerList) {
            return callerList;
        }

        return [[NSArray alloc] init];
    }
    @catch(NSException* e) {
        NSLog(@"CallerId: Failed to getCallerList: %@", e.description);
    }
}

RCT_EXPORT_METHOD(getCallerList: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    @try {
        NSArray* callerList = [NSArray arrayWithArray:[self getCallerList]];
        resolve(callerList);
    }
    @catch (NSException* e) {
        NSError* error = [self buildErrorFromException:e withErrorCode: 100];
        reject(@"getCallerList", @"Failed to getCallerList bridge:", error);
    }
}

RCT_EXPORT_METHOD(setCallerList: (NSArray*) callerList withResolver: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    @try {
      NSLog(@"Caller List -> %@",callerList);
        NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:DATA_GROUP];
        [userDefaults setObject:callerList forKey:DATA_KEY];
        [userDefaults synchronize];
        [CXCallDirectoryManager.sharedInstance reloadExtensionWithIdentifier:EXTENSION_ID completionHandler:^(NSError * _Nullable error) {
            if(error) {
              NSLog(@"error while reloading");
                reject(@"setCallerList", @"Failed to reload extension", error);
            } else {
              NSLog(@"reloading extension....");
                resolve(@true);
            }
        }];
    }
    @catch (NSException* e) {
        NSError* error = [self buildErrorFromException:e withErrorCode: 100];
        reject(@"setCallerList", @"Failed to set caller list", error);
    }
}

RCT_EXPORT_METHOD(getExtensionEnabledStatus: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    // The completionHandler is called twice. This is a workaround
    __block BOOL hasResult = false;
    __block int realResult = 0;
    [CXCallDirectoryManager.sharedInstance getEnabledStatusForExtensionWithIdentifier:EXTENSION_ID completionHandler:^(CXCallDirectoryEnabledStatus enabledStatus, NSError * _Nullable error) {
        // TODO: Remove these conditions when you find a way to return the correct result or Apple just fix their bug.
        if (hasResult == false) {
            hasResult = true;
            realResult = (int)enabledStatus;
        }
        if(error) {
            reject(@"getExtensionEnabledStatus", @"Failed to get extension status", error);
        } else {
            resolve([NSNumber numberWithInt:realResult]);
        }
    }];
}

- (NSDictionary *)constantsToExport
{
    return @{ @"UNKNOWN": @0,  @"DISABLED": @1, @"ENABLED": @2};
}

@end

CallDirectoryHandler.m

#import "CallDirectoryHandler.h"

#define DATA_KEY @"CALLER_LIST"
#define APP_GROUP @"group.org.reactn.futurecodes.CallDirectory"

@interface Caller : NSObject
@property NSString* name;
@property NSArray<NSNumber*>* numbers;
-(instancetype) initWithDictionary: (NSDictionary*) dictionary;
@end

@implementation Caller
-(instancetype) initWithDictionary: (NSDictionary*) dictionary {
    if (self = [super init]) {
        self.name = dictionary[@"name"];
        self.numbers = dictionary[@"numbers"];
    }
    return self;
}
@end

@interface CallDirectoryHandler () <CXCallDirectoryExtensionContextDelegate>

@end

@implementation CallDirectoryHandler

- (void)beginRequestWithExtensionContext:(CXCallDirectoryExtensionContext *)context {
    context.delegate = self;
    if (context.isIncremental) {
        [context removeAllIdentificationEntries];
    }
    NSLog(@"reloaded and adding numbers...");
    [self addAllIdentificationPhoneNumbersToContext:context];
    
    [context completeRequestWithCompletionHandler:nil];
}

- (NSArray*)getCallerList {
    @try {
        NSUserDefaults* userDefaults = [[NSUserDefaults alloc] initWithSuiteName:APP_GROUP];
        NSArray* callerList = [userDefaults arrayForKey:DATA_KEY];
        if (callerList) {
          NSLog(@"%@",callerList);
            return callerList;
        }
        return [[NSArray alloc] init];
    }
    @catch(NSException* e) {
        NSLog(@"Failed to get caller list: %@", e.description);
    }
}

- (void)addAllIdentificationPhoneNumbersToContext:(CXCallDirectoryExtensionContext *)context {
    @try {
        NSArray* callerList = [self getCallerList];
        NSLog(@"Call dir list %@",callerList);
        NSMutableDictionary<NSNumber*, NSString*>* labelsKeyedByPhoneNumber = [[NSMutableDictionary alloc] init];
        NSUInteger callersCount = [callerList count];
        if(callersCount > 0) {
            for (NSUInteger i = 0; i < callersCount; i += 1) {
                Caller* caller = [[Caller alloc] initWithDictionary:([callerList objectAtIndex:i])];
                for (NSUInteger j = 0; j < [caller.numbers count]; j++) {
                    NSNumber* number = caller.numbers[j];
                    [labelsKeyedByPhoneNumber setValue:caller.name forKey:number];
                }
            }
        }
        for (NSNumber *phoneNumber in [labelsKeyedByPhoneNumber.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
            NSString *label = labelsKeyedByPhoneNumber[phoneNumber];
            [context addIdentificationEntryWithNextSequentialPhoneNumber:(CXCallDirectoryPhoneNumber)[phoneNumber longLongValue] label:label];
        }
    } @catch (NSException* e) {
        NSLog(@"Failed to get caller list: %@", e.description);
    }
    
}

- (void)requestFailedForExtensionContext:(nonnull CXCallDirectoryExtensionContext *)extensionContext withError:(nonnull NSError *)error {
    NSLog(@"Request failed: %@", error.localizedDescription);
}

@end

And this is how I am calling it in my React Native App.js

import React, {useEffect} from 'react';
import {View, Text, NativeModules} from 'react-native';

function App() {
  const {CallkitBridge} = NativeModules;
  const callers = [
    {
      name: 'Debt Collector',
      number: '+923100000000',
    },
  ];

  useEffect(() => {
    (async () => {
      try {
        const status = await CallkitBridge.getExtensionEnabledStatus();
        if (status === 2) {
          try {
            await CallkitBridge.setCallerList(callers);
          } catch (error) {
            console.log('Adding error => ', error);
          }
        }
      } catch (error) {
        console.log('Err -> ', error);
      }
    })();
  }, []);
  return (
    <View>
      <Text>Hello world</Text>
    </View>
  );
}

export default App;

P.S: I have checked the App Group and it is correct, and the +92 is my Country Code i.e. Pakistan, however, I am not seeing the caller ID. I have checked and enable the extension, and I am seeing the Extension Status === 2 which is enabled. Please Note: While I tried every solution, I am unable to debug the Call Directory Extension in XCode. Any help is appreciated, thanks.

I tried changing the callers array from name and a array of numbers inside the object, as well as changing a bit the native codes too. Expectation: I want the Caller ID to be shown, whenever, someone calls me from the number, I provide back from my React Native App.

0

There are 0 best solutions below