Customizing user information - Bandyer/Bandyer-iOS-SDK GitHub Wiki

This guide will discuss how you can customize the appeareance of the Kaleyra Video Call and Chat user interface changing the user information shown to the end user while taking a call or messaging through the Kaleyra Video 3.0 version. If you are looking at the guide for 2.x versions please take a look at this guide instead.

Table of contents

User alias

Kaleyra Video SDKs are designed to work with the bare minimum information about a user, this means that user contact information like first name, last name, email and so on, are not available in the SDK. We refer to a user in the Kaleyra Video platform through hers/his "userId", which is an alphanumeric string unique within a company (you can think of it as a slug). This approach has the advantage that we don't store any user information on our back-end, nor we send user's information over the network, but it has one drawback, whenever the end user has to be presented with contact information about another user, the only information when can show her/him is an "user alias".

custom-user-info-1

As you can see from the image above, when the call user interface is presented to the user, the Kaleyra Video will show the user id of the user by default, because it doesn't have any other information to show about the user being called. In order for the SDK to show some other user information it must be given those information and it must be told how to present them.

Fetching user information

The Kaleyra Video provides a protocol named UserDetailsProvider which serve the purpose of "retrieving" the user information to be displayed when the call or the chat user interface is displayed to the end-user. In order for the Kaleyra Video to use it, you must conform one of your class to the UserDetailsProvider protocol and provide an instance of that class to the Kaleyra Video SDK before initializing it or to the call (or chat) view controller configuration object before presenting it. The following snippet of code shows how the UserDetailsProvider protocol can be implemented:

import Foundation
import CallKit
import Bandyer

class Provider: UserDetailsProvider {
    private let addressBook: AddressBook
    
    init(_ addressBook: AddressBook) {
        self.addressBook = addressBook
    }

    func provideDetails(_ userIds: [String], completion: @escaping ([UserDetails]) -> Void) {
        var details = [UserDetails]()
        for userId in userIds {
            let contact = addressBook.findContact(userId: userId)
            let detail = UserDetails(userID: userId, firstname: contact?.firstName, lastname: contact?.lastName, email: contact?.email, imageURL: contact?.profileImageURL)
            details.append(detail)
        }
        
        completion(details)
    }
    
    func provideHandle(_ userIds: [String], completion: @escaping (CXHandle) -> Void) {
		[...]
    }

}
#import "Provider.h"
#import "Contact.h"
#import "AddressBook.h"

#import <Bandyer/Bandyer.h>
#import <CallKit/CallKit.h>

@implementation Provider

- (instancetype)initWithAddressBook:(AddressBook *)addressBook
{
    self = [super init];

    if (self)
    {
        _addressBook = addressBook;
    }

    return self;
}

- (void)provideDetails:(NSArray<NSString *> *)userIds completion:(void (^)(NSArray<BDKUserDetails *> * _Nonnull))completion
{
    NSMutableArray<BDKUserDetails *> *details = [NSMutableArray arrayWithCapacity:userIds.count];

    for (NSString *userId in userIds)
    {
        Contact *contact = [self.addressBook contactForAlias:alias];
        BDKUserDetails *detail = [[BDKUserDetails alloc] initWithUserID:userId
                                                           firstname:contact.firstName
                                                            lastname:contact.lastName
                                                               email:contact.email
                                                            imageURL:contact.profileImageURL];
        [details addObject: detail];
    }

    completion(details);
}

- (void)provideHandle:(NSArray<NSString *> *)userIds completion:(void (^)(CXHandle * _Nonnull))completion
{
	[...]
}

@end

In the above snippet of code, we are pretending there's a class in our app named "AddressBook" which contains "Contact" information for any user. The class Provider conforms to the SDK protocol and creates an array of UserDetails getting the information from the "AddressBook" and then calls the completion block to signal the Kaleyra Video SDK it finished gathering the user information the SDK requested. In order for the Kaleyra Video SDK to use an instance of the "Provider" class we created, we must provide it to the userDetailsProvider [property][user-details-provider-property-doc-ref] on the Kaleyra Video singleton instance. Eventually, the final result looks like this:

custom-user-info-2

CallKit contact handles

As you might have noticed, the UserDetailsProvider requires you to implement two methods. The provideHandle(_:, completion:) method is needed by the SDK when CallKit support is enabled and the SDK needs to update the native system UI with user information. CallKit needs CXHandle objects to identify a contact inside the end-user address book. When an incoming or outgoing call is going to be processed by CallKit, the Bandyer SDK must provide those CXHandle objects to it. In order to create CXHandle objects with meaningful user information, the SDK must be provided an UserDetailsProvider object that is able to create CXHandle objects from "user aliases". The following snippets of code will show you how your class conforming to the UserDetailsProvider might create those CXHandle objects.

import Foundation
import CallKit
import Bandyer

class Provider: UserDetailsProvider {
    private let addressBook: AddressBook
    
    init(_ addressBook: AddressBook) {
        self.addressBook = addressBook
    }

    func provideDetails(_ userIds: [String], completion: @escaping ([UserDetails]) -> Void) {
		[...]
    }
    
    func provideHandle(_ userIds: [String], completion: @escaping (CXHandle) -> Void) {
        let contacts = addressBook.findContacts(userIds: userIds)
        let names = contacts.compactMap({$0.value.fullName})
        let handle = CXHandle(type: .generic, value: names.joined(separator: ", "))
        completion(handle)
    }

}
#import "Provider.h"
#import "Contact.h"
#import "AddressBook.h"

#import <Bandyer/Bandyer.h>
#import <CallKit/CallKit.h>

@implementation Provider

- (instancetype)initWithAddressBook:(AddressBook *)addressBook
{
    self = [super init];

    if (self)
    {
        _addressBook = addressBook;
    }

    return self;
}

- (void)provideDetails:(NSArray<NSString *> *)userIds completion:(void (^)(NSArray<BDKUserDetails *> * _Nonnull))completion
{
	[...]
}

- (void)provideHandle:(NSArray<NSString *> *) userIds completion:(void (^)(CXHandle * _Nonnull))completion
{	
    NSArray<Contact*> *contacts = [self.addressBook findContactsByUserIds: userIds];
    NSMutableArray<NSString*> *names = [NSMutableArray array];
    for (Contact *contact in contacts) {
       NSString *name = [contact fullName];
       if (name != nil)
            [names addObject:name];
    }
    NSString *value = [names componentsJoinedByString:@", "];
    CXHandle *handle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:value];
    completion(handle);
}

@end

Eventually, the final result should look like this:

CallKit handle

What's next

⚠️ **GitHub.com Fallback** ⚠️