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.