https://d226lax1qjow5r.cloudfront.net/blog/blogposts/handling-voip-push-notifications-with-callkit/callkit-push-notifications.png

Wie man VoIP-Push-Benachrichtigungen mit iOS Callkit handhabt

Zuletzt aktualisiert am May 25, 2023

Lesedauer: 7 Minuten

In diesem Lernprogramm werden Sie CallKit verwenden, um die VoIP-Push-Benachrichtigungen zu verarbeiten, die an ein iOS-Gerät gesendet werden, wenn Sie das Vonage Client SDK für iOS. VoIP-Push-Benachrichtigungen sind die primäre Methode für den Empfang eingehender Anrufe mit dem Client SDK, da sie das Gerät unabhängig davon erreichen, ob sich Ihre Anwendung im Vordergrund befindet oder nicht. Mit CallKit können Sie Ihre iOS-Anwendung in das System integrieren, so dass Ihre Anwendung wie ein nativer iOS-Telefonanruf aussehen kann. Die Verwendung des Vonage Client SDK mit CallKit ermöglicht es Ihnen, Anrufe in Ihre Anwendung zu integrieren und gleichzeitig eine konsistente und vertraute Schnittstelle für eingehende Anrufe zu haben.

Voraussetzungen

  • Ein Vonage API-Konto. Wenn Sie noch kein Konto haben, können Sie sich heute anmelden.

  • Ein Apple Developer-Konto und ein iOS-Gerät (oder einen Simulator in Xcode 14 und höher).

  • Ein GitHub-Konto.

  • Xcode 12 und Swift 5 oder höher.

  • Cocoapods um das Vonage Client SDK für iOS zu installieren.

  • Unser Command Line Interface. Sie können es installieren mit npm install -g @vonage/cli.

Das Starter-Projekt

Dieser Blog baut auf dem Blog "Entgegennahme eines Telefonanrufs in der App" aus dem Vonage-Entwicklerportal auf. Dieses Tutorial beginnt mit dem fertigen Zustand des Tutorial-Projekts. Wenn Sie bereits mit der Erstellung einer Vonage Client SDK Sprachanwendung vertraut sind, können Sie das Starter Projekt von GitHub klonen.

Push-Zertifikate einrichten

Es gibt zwei Arten von Push-Benachrichtigungen, die Sie in einer iOS-App verwenden können: VoIP-Pushes mit PushKit oder User Notifications. Dieses Tutorial konzentriert sich auf VoIP-Push-Benachrichtigungen. Der Apple Push-Benachrichtigungsdienst (APNs) verwendet zertifikatsbasierte Authentifizierung, um die Verbindungen zwischen APNs und Vonage-Servern zu sichern. Sie müssen also ein Zertifikat erstellen und es auf die Vonage-Server hochladen, damit Vonage bei einem eingehenden Anruf einen Push an das Gerät senden kann.

Hinzufügen einer Push-Benachrichtigungsfunktion

Um Push-Benachrichtigungen verwenden zu können, müssen Sie die Push-Benachrichtigungsfunktion zu Ihrem Xcode-Projekt hinzufügen. Vergewissern Sie sich, dass Sie in Ihrem Apple-Entwicklerkonto in Xcode über die Einstellungen angemeldet sind. Wenn ja, wählen Sie Ihr Ziel aus und wählen Sie dann Signierung & Fähigkeiten:

signing and capabilities tagsigning and capabilities tag

Wählen Sie dann Fähigkeit hinzufügen und fügen Sie die Push-Benachrichtigungen und Hintergrund-Modi Fähigkeiten hinzu:

add capability buttonadd capability button

Wählen Sie unter der Funktion Hintergrundmodi Sprache über IP und Hintergrundverarbeitung. Wenn Xcode die Signierung Ihrer Anwendung automatisch verwaltet, wird das mit Ihrem Bundle Identifier verknüpfte Bereitstellungsprofil aktualisiert, um die Fähigkeiten einzubeziehen.

Wenn Sie VoIP-Push-Benachrichtigungen verwenden, müssen Sie das CallKit-Framework einsetzen. Verknüpfen Sie es mit Ihrem Projekt, indem Sie es unter Frameworks, Bibliotheken und eingebettete Inhalte unter Allgemein:

add callkit frameworkadd callkit framework

Erzeugen eines Push-Zertifikats

Um ein Push-Zertifikat zu erstellen, müssen Sie sich in Ihrem Apple-Entwickler-Account anmelden und die Seite Zertifikate, Kennungen und Profile gehen und ein neues Zertifikat hinzufügen:

add certificate buttonadd certificate button

Wählen Sie Apple Push-Benachrichtigungsdienst SSL (Sandbox & Produktion) und fahren Sie fort.

Certificate wizard checkboxCertificate wizard checkbox

Sie müssen nun die App-ID für die App auswählen, der Sie VoIP-Push-Benachrichtigungen hinzufügen möchten, und fortfahren. Wenn Ihre App nicht aufgeführt ist, müssen Sie eine App-ID erstellen. Xcode kann dies für Sie tun, wenn es Ihre Signierung automatisch verwaltet. Andernfalls können Sie eine neue App-ID auf der Seite Zertifikate, Bezeichner & Profile Seite unter Bezeichner erstellen. Stellen Sie dabei sicher, dass Sie die Push-Benachrichtigungsfunktion auswählen.

Sie werden aufgefordert, eine Certificate Signing Request (CSR) hochzuladen. Sie können die Anweisungen auf der Hilfe-Website von Apple folgen, um eine CSR auf Ihrem Mac zu erstellen. Sobald die CSR hochgeladen ist, können Sie das Zertifikat herunterladen. Doppelklicken Sie auf die .cer Datei, um es im Schlüsselbund zu installieren.

Um das Push-Zertifikat in dem Format zu erhalten, das von den Vonage-Servern benötigt wird, müssen Sie es exportieren. Suchen Sie Ihr VoIP-Services-Zertifikat in Keychain Access und exportieren Sie es mit der rechten Maustaste. Benennen Sie den Export applecert und wählen Sie .p12 als Format:

keychain access exportkeychain access export

Ihr Push-Zertifikat hochladen

Sie laden hoch. Ihr Zertifikat zu Vonage über das API-Dashboard. Öffnen Sie Ihre Anwendung auf dem Dashboardund öffnen Sie dann die Option "Enable Push Benachrichtigungen" Registerkarte:

Push Upload on the dashboardPush Upload on the dashboard

Sie können Ihr Zertifikat hochladen .p12 aus dem vorherigen Schritt hochladen und bei Bedarf ein Passwort hinzufügen.

Die Klasse ClientManager

Erstellen Sie eine neue Swift-Datei (CMD + N) und rufen Sie diese auf ClientManager. Diese Klasse wird den Code kapseln, der für die Schnittstelle zum Client-SDK benötigt wird, da Sie in zukünftigen Schritten an mehreren Stellen Informationen vom Client-SDK abrufen müssen:

Ersetzen Sie ALICE_JWT durch das JWT, das Sie zuvor generiert haben. In einer Produktionsumgebung würden Sie hier ein JWT von Ihrem Authentifizierungsserver/Endpunkt abrufen.

Mit dieser neuen Klasse müssen Sie den Aufruf des Client-SDK-Codes von der ViewController Klasse in die ClientManager Klasse verschieben. Die beiden Klassen kommunizieren mit den ClientManagerDelegate Beobachtern. Nehmen Sie die folgenden Änderungen an Ihrer ViewController Klasse vor:

class ViewController: UIViewController {
    
    private let connectionStatusLabel = UILabel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        ClientManager.shared.delegate = self
        
        connectionStatusLabel.text = ""
        connectionStatusLabel.textAlignment = .center
        connectionStatusLabel.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(connectionStatusLabel)
        
        view.addConstraints([
            connectionStatusLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            connectionStatusLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
    }
}

extension ViewController: ClientManagerDelegate {
    func clientStatusUpdated(_ clientManager: ClientManager, status: String) {
        DispatchQueue.main.async {
            self.connectionStatusLabel.text = status
        }
    }
}

Für Push-Benachrichtigungen registrieren

Der nächste Schritt besteht darin, ein Gerät für Push-Benachrichtigungen zu registrieren, damit Vonage weiß, an welches Gerät die Push-Benachrichtigung für welchen Benutzer gesendet werden soll. In der ClientManager Klasse fügen Sie die pushToken Eigenschaft und die folgenden Funktionen, um das Push-Token des Geräts zu verarbeiten:

final class ClientManager: NSObject {
    public var pushToken: Data?
    weak var delegate: ClientManagerDelegate?
    ...

    func invalidatePushToken(_ completion: (() -> Void)? = nil) {
        print("VPush: Invalidate token")
        if let deviceId = UserDefaults.standard.object(forKey: Constants.deviceId) as? String {
            client.unregisterDeviceTokens(byDeviceId: deviceId) { error in
                if error == nil {
                    self.pushToken = nil
                    UserDefaults.standard.removeObject(forKey: Constants.pushToken)
                    UserDefaults.standard.removeObject(forKey: Constants.deviceId)
                    completion?()
                }
            }
        } else {
            completion?()
        }
    }

    private func registerPushIfNeeded(with token: Data) {
        shouldRegisterToken(with: token) { shouldRegister in
            if shouldRegister {
                self.client.registerVoipToken(token, isSandbox: true) { error, deviceId in
                    if error == nil {
                        print("VPush: push token registered")
                        UserDefaults.standard.setValue(token, forKey: Constants.pushToken)
                        UserDefaults.standard.setValue(deviceId, forKey: Constants.deviceId)
                    } else {
                        print("VPush: registration error: \(String(describing: error))")
                        return
                    }
                }
            }
        }
    }

    private func shouldRegisterToken(with token: Data, completion: @escaping (Bool) -> Void) {
        let storedToken = UserDefaults.standard.object(forKey: Constants.pushToken) as? Data
        
        if let storedToken = storedToken, storedToken == token {
            completion(false)
            return
        }
        
        invalidatePushToken {
            completion(true)
        }
    }

Die Funktion registerPushIfNeeded Funktion nimmt ein Token und verwendet dann die shouldRegisterToken Funktion, um zu prüfen, ob das Token bereits registriert wurde. Wenn dies nicht der Fall ist, registerVoipToken auf dem Client die Push-Benachrichtigung bei Vonage registrieren. In der AppDelegate Klasse können Sie sich nun für VoIP-Push-Benachrichtigungen registrieren. Importieren Sie PushKit am Anfang der Datei:

import PushKit

Fügen Sie eine lokale Instanz der ClientManager Klasse:

class AppDelegate: UIResponder, UIApplicationDelegate {
    ...
    private let clientManager = ClientManager.shared
    ...
}

Erstellen Sie eine neue Erweiterung am Ende der Datei, die eine Funktion zur Registrierung des Geräts für Push-Benachrichtigungen enthält:

extension AppDelegate: PKPushRegistryDelegate {
    func registerForVoIPPushes() {
        let voipRegistry = PKPushRegistry(queue: nil)
        voipRegistry.delegate = self
        voipRegistry.desiredPushTypes = [PKPushType.voIP]
    }
}

Aktualisieren Sie die didFinishLaunchingWithOptions Funktion zum Aufruf der registerForVoIPPushes Funktion aufzurufen und sich beim Client SDK anzumelden:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    AVAudioSession.sharedInstance().requestRecordPermission { (granted:Bool) in
        print("Allow microphone use. Response: \(granted)")
    }
    registerForVoIPPushes()
    clientManager.login()
    return true
}

Fügen Sie die PKPushRegistryDelegate Funktionen für die Registrierung von Push-Benachrichtigungen in die Erweiterung ein:

extension AppDelegate: PKPushRegistryDelegate {
    ...

    func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        clientManager.pushToken = pushCredentials.token
    }

    func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
        clientManager.invalidatePushToken(nil)
    }
}

Das Push-Token wird als Eigenschaft der Klasse ClientManager Klasse gespeichert, da Sie das Token nur dann bei Vonage registrieren wollen, wenn der Kunde angemeldet ist, bearbeiten Sie also die login Funktion in der ClientManager Klasse, um dies zu handhaben:

func login(isPushLogin: Bool = false) {
    print("VPush: Login - isPush:", isPushLogin)
    guard !isActiveCall else { return }
    
    ongoingPushLogin = isPushLogin
    
    getJWT { jwt in
        self.client.createSession(jwt) { error, sessionID in
            let statusText: String
            if error == nil {
                statusText = "Connected"
                
                if isPushLogin {
                    self.handlePushLogin()
                } else {
                    self.handleLogin()
                }
            } else {
                statusText = error!.localizedDescription
            }
            
            self.delegate?.clientStatusUpdated(self, status: statusText)
        }
    }
}

private func handlePushLogin() {
    ongoingPushLogin = false
    
    if let storedAction = storedAction {
        storedAction()
    }
}

private func handleLogin() {
    if let token = pushToken {
        registerPushIfNeeded(with: token)
    }
}

Eingehende Push-Benachrichtigungen verarbeiten

Wenn das Gerät registriert ist, kann es nun Push-Benachrichtigungen von Vonage empfangen. Das Client SDK verfügt über Funktionen zur Überprüfung, ob eine Push-Benachrichtigung die erwartete Nutzlast ist, und zur Verarbeitung der Nutzlast. Wenn processCallInvitePushData aufgerufen wird, wandelt es die Nutzlast in einen Aufruf um, der auf der didReceiveInviteForCall Funktion des VGVoiceClientDelegate.

Wie bei der Registrierung eines Push-Tokens wollen Sie einen eingehenden Push nur dann verarbeiten, wenn das Client SDK angemeldet wurde. Implementieren Sie die Funktionen der ClientManager Klasse zusammen mit einer lokalen Variable, um einen eingehenden Push zu speichern:

final class ClientManager: NSObject {
    ...

    private var ongoingPushLogin = false
    private var ongoingPushKitCompletion: () -> Void = { }
    private var storedAction: (() -> Void)?

    ...

    func isVonagePush(with userInfo: [AnyHashable : Any]) -> Bool {
        VGVoiceClient.vonagePushType(userInfo) == .unknown ? false : true
    }

    func processPushPayload(with payload: [AnyHashable : Any], pushKitCompletion: @escaping () -> Void) -> String? {
        self.ongoingPushKitCompletion = pushKitCompletion
        return client.processCallInvitePushData(payload)
    }

    ...
}

Die PKPushRegistryDelegate hat eine Funktion, die aufgerufen wird, wenn ein Push eingeht, der didReceiveIncomingPushWith zur Erweiterung hinzufügen PKPushRegistryDelegate in der AppDelegate.swift Datei:

func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    if clientManager.isVonagePush(with: payload.dictionaryPayload) {
        clientManager.login(isPushLogin: true)
        _ = clientManager.processPushPayload(with: payload.dictionaryPayload, pushKitCompletion: completion)
    }
}

Es wird empfohlen, eine Anmeldung durchzuführen, wenn Sie eine eingehende VoIP-Push-Benachrichtigung haben, weshalb login hier aufgerufen wird. Dies verwendet die Logik in der ClientManager Klasse, die Informationen über einen Push speichert, der nach der Anmeldung verwendet werden soll. Die Logik wird zu einem späteren Zeitpunkt implementiert.

Wenn Ihre iOS-Anwendung eine eingehende VoIP-Push-Benachrichtigung erhält, müssen Sie diese mit der CXProvider Klasse im CallKit-Framework behandeln. Erstellen Sie eine neue Swift-Datei (CMD + N) mit dem Namen ProviderDelegate:

import CallKit
import AVFoundation
import VonageClientSDKVoice

final class ProviderDelegate: NSObject {
    private let provider: CXProvider
    private let callController = CXCallController()
    private var activeCall: UUID? = nil
    
    override init() {
        provider = CXProvider(configuration: ProviderDelegate.providerConfiguration)
        super.init()
        provider.setDelegate(self, queue: nil)
    }
    
    static var providerConfiguration: CXProviderConfiguration = {
        let providerConfiguration = CXProviderConfiguration()
        providerConfiguration.maximumCallsPerCallGroup = 1
        providerConfiguration.supportedHandleTypes = [.generic, .phoneNumber]
        return providerConfiguration
    }()
}

Die callController Eigenschaft ist ein CXCallController Objekt, das von der Klasse verwendet wird, um Benutzeraktionen auf der CallKit-Benutzeroberfläche zu verarbeiten. Als nächstes erstellen Sie eine Erweiterung am Ende der Datei, um die CXProviderDelegate:

extension ProviderDelegate: CXProviderDelegate {
   func providerDidReset(_ provider: CXProvider) {
        activeCall = nil
    }

    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        ClientManager.shared.answer(activeCall!.uuidString.lowercased()) { error in
            if error == nil {
                action.fulfill()
            } else {
                action.fail()
            }
        }
    }
    
    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        hangup(action: action)
    }
    
    func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
        VGVoiceClient.enableAudio(audioSession)
    }
    
    func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
        VGVoiceClient.disableAudio(audioSession)
    }
}

Wenn CallKit die Audiositzung aktiviert und deaktiviert, aktivieren und deaktivieren die Delegatenfunktionen das Client-SDK-Audio über die CallKit-Audiositzung.

Wenn die CallKit-Benutzeroberfläche den Anruf beantwortet, ruft sie die CXAnswerCallAction Delegaten-Funktion auf. Diese ruft die answer Funktion auf, die Sie in einem späteren Schritt in der ClientManager in einem späteren Schritt implementieren werden.

CXEndCallAction wird aufgerufen, wenn der Anruf von der CallKit UI beendet wird, die die hangup Funktion aufruft, die Sie als nächstes implementieren werden. Implementieren Sie den Rest der benötigten Funktionen in der ProviderDelegate Klasse:

final class ProviderDelegate: NSObject {
    ...
    
    func reportCall(_ callID: String, caller: String, completion: @escaping () -> Void) {
        activeCall = UUID(uuidString: callID)
        let update = CXCallUpdate()
        update.localizedCallerName = caller
        
        provider.reportNewIncomingCall(with: activeCall!, update: update) { error in
            if error == nil {
                completion()
            }
        }
    }
    
    func didReceiveHangup(_ callID: String) {
        let uuid = UUID(uuidString: callID)!
        provider.reportCall(with: uuid, endedAt: Date.now, reason: .remoteEnded)
    }
    
    func reportFailedCall(_ callID: String) {
        let uuid = UUID(uuidString: callID)!
        provider.reportCall(with: uuid, endedAt: Date.now, reason: .failed)
    }
    
    private func hangup(action: CXEndCallAction) {
        if activeCall == nil {
            endCallTransaction(action: action)
        } else {
            ClientManager.shared.reject(activeCall!.uuidString.lowercased()) { error in
                if error == nil {
                    self.endCallTransaction(action: action)
                }
            }
        }
    }
    
    private func endCallTransaction(action: CXEndCallAction) {
        self.callController.request(CXTransaction(action: action)) { error in
            if error == nil {
                self.activeCall = nil
                action.fulfill()
            } else {
                action.fail()
            }
        }
    }
}

Die Funktion reportCall Funktion ruft reportNewIncomingCall die die CallKit-System-UI auslöst, die anderen Funktionen dienen entweder der Aktualisierung oder dem Beenden von Aufrufen. Jetzt, wo die ProviderDelegate Klasse vollständig ist, können Sie die ClientManager Klasse aktualisieren, um sie zu verwenden. Fügen Sie die providerDelegate Eigenschaft zum Client Manager hinzu:

final class ClientManager: NSObject {
    ...

    private let providerDelegate = ProviderDelegate()

    ...
}

Dann implementieren Sie die VGVoiceClientDelegate:

extension ClientManager: VGVoiceClientDelegate {
    func voiceClient(_ client: VGVoiceClient, didReceiveInviteForCall callId: VGCallId, from caller: String, with type: VGVoiceChannelType) {
        print("VPush: Received invite", callId)
        providerDelegate.reportCall(callId, caller: caller, completion: ongoingPushKitCompletion)
    }
    
    func voiceClient(_ client: VGVoiceClient, didReceiveHangupForCall callId: VGCallId, withQuality callQuality: VGRTCQuality, reason: VGHangupReason) {
        print("VPush: Received hangup")
        isActiveCall = false
        providerDelegate.didReceiveHangup(callId)
    }
    
    func voiceClient(_ client: VGVoiceClient, didReceiveInviteCancelForCall callId: String, with reason: VGVoiceInviteCancelReason) {
        print("VPush: Received invite cancel")
        providerDelegate.reportFailedCall(callId)
    }
    
    func client(_ client: VGBaseClient, didReceiveSessionErrorWith reason: VGSessionErrorReason) {
        let reasonString: String!
        
        switch reason {
        case .tokenExpired:
            reasonString = "Expired Token"
        case .pingTimeout, .transportClosed:
            reasonString = "Network Error"
        default:
            reasonString = "Unknown"
        }
        
        delegate?.clientStatusUpdated(self, status: reasonString)
    }
}

Nachdem das SDK den Aufruf verarbeitet hat, erhalten Sie eine Aufrufaufforderung für die didReceiveInviteForCall Delegatenfunktion, die ihrerseits den Anruf meldet. didReceiveHangupForCall und didReceiveInviteCancelForCall rufen ebenfalls ihre jeweiligen Funktionen auf dem Provider-Delegaten auf. Zur Vervollständigung der ClientManager Klasse zu vervollständigen, fügen Sie die answer und reject Funktionen hinzu:

func answer(_ callID: String, completion: @escaping (Error?) -> Void) {
    let answerAction = {
        print("VPush: Answer", callID)
        self.isActiveCall = true
        self.client.answer(callID, callback: completion)
    }
    
    if ongoingPushLogin {
        print("VPush: Storing answer")
        storedAction = answerAction
    } else {
        answerAction()
    }
    
}

func reject(_ callID: String, completion: @escaping (Error?) -> Void) {
    let rejectAction = {
        print("VPush: Reject", callID)
        self.isActiveCall = false
        self.client.reject(callID, callback: completion)
    }
    
    if ongoingPushLogin {
        print("VPush: Storing Reject")
        storedAction = rejectAction
    } else {
        rejectAction()
    }
}

Auch hier können Sie einen Anruf nur annehmen oder ablehnen, nachdem Sie sich beim Client SDK angemeldet haben. Beide Funktionen verwenden die ongoingPushLogin um zu prüfen, ob die Anmeldung erfolgreich abgeschlossen wurde; wenn nicht, wird die Aktion mit storedAction. Wenn Sie sich die handlePushLogin Funktion ansehen, können Sie sehen, dass sie nach Abschluss der Anmeldung eine gespeicherte Aktion aufruft, falls eine solche vorhanden ist.

Probieren Sie es aus

Erstellen Sie das Projekt und führen Sie es auf Ihrem iOS-Gerät (oder Simulator in Xcode 14 und höher) aus (CMD + R), akzeptieren Sie die Mikrofonberechtigungen und sperren Sie das Gerät. Rufen Sie dann die Nummer an, die mit Ihrer Vonage-Anwendung von vorhin verknüpft ist. Sie werden den eingehenden Anruf direkt auf Ihrem Sperrbildschirm sehen; sobald Sie ihn annehmen, wird der bekannte iOS-Anrufbildschirm angezeigt:

incoming call with locked screenincoming call with locked screen

active call from locked screenactive call from locked screen

Wenn Sie die Anrufprotokolle auf dem Gerät überprüfen, wird der Anruf auch dort aufgeführt.

Was kommt als Nächstes?

Sie finden das fertige Projekt auf GitHub. Mit dem Client SDK und CallKit können Sie noch viel mehr tun; Sie können CallKit für ausgehende Anrufe verwenden. Erfahren Sie mehr über das Client SDK auf der Vonage Client SDK Übersicht und CallKit auf developer.apple.com.

Share:

https://a.storyblok.com/f/270183/400x400/19c02db2d3/abdul-ajetunmobi.png
Abdul AjetunmobiSenior Advocate für Entwickler

Abdul ist ein Developer Advocate für Vonage. Er hat einen Hintergrund als iOS-Ingenieur im Bereich Verbraucherprodukte. In seiner Freizeit fährt er gerne Rad, hört Musik und berät diejenigen, die gerade ihre Reise in die Technologiebranche beginnen.