Skip to content

boostlingo/boostlingo-android

Repository files navigation

Module Boostlingo Android SDK

The Boostlingo Android library enables developers to embed the Boostlingo caller directly into their own applications. This can then be used for placing calls in the Boostlingo platform.

Getting Started

In order to place calls in Boostlingo, you must have a requestor account. You can then embed Boostlingo into your application, request a Boostlingo API token from your server, and start making calls.

Installation

AAR

The SDK is distributed as a prebuilt AAR. Download the latest boostlingo-release.aar from the releases page and put it into your app's /libs folder.

Requires minSdk 23 or higher in the consuming app.

Because a bare AAR carries no dependency metadata, you must also declare every transitive dependency yourself. Add the following to your app module's build.gradle.kts (versions match the SDK build — see gradle/libs.versions.toml):

dependencies {
    // Boostlingo
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))

    // SignalR
    implementation("com.microsoft.signalr:signalr:10.0.8")

    // Rx
    implementation("io.reactivex.rxjava3:rxkotlin:3.0.1")
    implementation("io.reactivex.rxjava3:rxandroid:3.0.2")

    // Retrofit
    implementation("com.squareup.retrofit2:retrofit:3.0.0")
    implementation("com.squareup.retrofit2:adapter-rxjava3:3.0.0")
    implementation("com.squareup.retrofit2:converter-gson:3.0.0")

    // OkHttp
    implementation("com.squareup.okhttp3:okhttp:5.3.2")
    implementation("com.squareup.okhttp3:logging-interceptor:5.3.2")

    // Twilio
    implementation("com.twilio:voice-android:6.10.3")
    implementation("com.twilio:video-android-ktx:7.10.4")
    implementation("com.twilio:audioswitch:1.2.5")
}

If your app still uses a Groovy build.gradle, the equivalent is implementation fileTree(dir: 'libs', include: ['*.aar']) plus implementation 'group:artifact:version' lines for each dependency above.

Usage

These steps will guide you through the basic process of placing calls through Boostlingo.

Request Boostlingo authentication token

First step is to obtain a boostlingo authentication token from your server. Never store an API token or username/password in your front end/mobile code. Your server should be the one that logs the user in, obtains the authentication token, and passes it back down to the web application.

Obtain Boostlingo authentication token via API endpoint

POST https://app.boostlingo.com/api/web/account/signin

Request Model

{
"email": "<string>",
"password": "<string>"
}

Response Model token is what will be needed by the boostlingo sdk

{
"userAccountId": "<integer>",
"refreshToken": "<string>",
"role": "<string>",
"token": "<string>",
"companyAccountId": "<integer>"
}

Quickstart

This is a working example that will demonstrate how to Boostlingo calls.

Enter your TOKEN in the token field, as well as your REGION in the region field.

Create instance of Boostlingo class and load dictionaries

We recommend you do this only once. The Boostlingo library will cache specific data and create instances of classes that do not need to be refreshed very frequently. The next step is typically to pull down the call dictionaries. Whether you expose these directly or are just mapping languages and service types with your internal types, loading these lists will almost definitely be required.

val boostlingoSdk: BoostlingoSDK by lazy {
    BoostlingoSDK(
        authToken,
        this,
        BLLogLevel.DEBUG,
        region
    )
}

boostlingoSdk.initialize()
    .subscribeOn(Schedulers.io())
    .andThen(
        boostlingoSdk.getCallDictionaries()
            .subscribeOn(Schedulers.io())
    )
    .observeOn(AndroidSchedulers.mainThread())
    .doOnSubscribe { updateUI(State.LOADING) }
    .subscribe(
        { callDictionaries ->
            updateUI(State.INIT)
            binding.languageToTextView.text = callDictionaries.languages.first { language -> language.id == 1 }.englishName
            binding.languageFromTextView.text = callDictionaries.languages.first { language -> language.id == 4 }.englishName
            binding.serviceTypeTextView.text = callDictionaries.serviceTypes.first { language -> language.id == 1 }.name
            binding.genderTextView.text = callDictionaries.genders.first().name
        },
        {
            updateUI(State.NOT_INIT)
            val httpException = it as? HttpException
            if (httpException != null) {
                Snackbar.make(
                    binding.rootView,
                    "Error: " + httpException.localizedMessage + " StatusCode: " + httpException.code(),
                    Snackbar.LENGTH_LONG
                ).show()
            } else {
                Snackbar.make(
                    binding.rootView,
                    "Error: " + it.localizedMessage,
                    Snackbar.LENGTH_LONG
                ).show()
            }
        }
    )
    .let { compositeDisposable.add(it) }

Handling BLCallEvent

private fun subscribeToCallEvent() {
    boostlingoSdk.callEventObservable
        .subscribeOn(AndroidSchedulers.mainThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
            ::handleCallEvent,
            ::handleError
        )
        .let { compositeDisposable.add(it) }
}

private fun handleCallEvent(callEvent: BLCallEvent) {
    when (callEvent) {
        is BLCallEvent.CallConnected -> {
            Log.d(null, "BLCallEvent.CallConnected")
            audioSwitch.activate()
            currentCall = callEvent.call as BLVideoCall
            updateUI(State.IN_PROGRESS)

            Log.d(null, "Participants: ${callEvent.call.participants.count()}")
            callEvent.call
                .participants
                .firstOrNull()
                ?.let {
                    currentCall?.addRenderer(it.identity, binding.primaryVideoView)
                }
        }
        is BLCallEvent.CallDisconnected -> {
            Log.d(null, "BLCallEvent.CallDisconnected")
            audioSwitch.deactivate()
            currentCall = null
            updateUI(State.NO_CALL)
            Snackbar.make(
                binding.rootView,
                "Call did disconnect with error: " + callEvent.e?.localizedMessage,
                Snackbar.LENGTH_LONG
            ).show()
        }
        is BLCallEvent.CallFailedToConnect -> {
            Log.d(null, "BLCallEvent.CallFailedToConnect")
            audioSwitch.deactivate()
            currentCall = null
            updateUI(State.NO_CALL)
            Snackbar.make(
                binding.rootView,
                "Call did fail to connect with error: " + callEvent.e?.localizedMessage,
                Snackbar.LENGTH_LONG
            ).show()
        }
        BLCallEvent.ChatConnected -> {
            Log.d(null, "BLCallEvent.ChatConnected")
        }
        BLCallEvent.ChatDisconnected -> {
            Log.d(null, "BLCallEvent.ChatDisconnected")
        }
        is BLCallEvent.ChatMessageReceived -> {
            Log.d(null, "BLCallEvent.ChatMessageReceived: ${callEvent.message}")
        }
        is BLCallEvent.ParticipantAdded -> {
            Log.d(null, "BLCallEvent.ParticipantAdded ${callEvent.participant.accountId}")
            Log.d(null, "Participants: ${callEvent.call.participants.count()}")
            callEvent.participant
                .let {
                    currentCall?.addRenderer(it.identity, binding.primaryVideoView)
                }
        }
        is BLCallEvent.ParticipantRemoved -> {
            Log.d(null, "BLCallEvent.ParticipantUpdated: " + callEvent.participant.accountId)
        }
        is BLCallEvent.ParticipantUpdated -> {
            Log.d(null, "BLCallEvent.ParticipantUpdated: " + callEvent.participant.accountId)
        }
    }
}

private fun handleError(t: Throwable) {
    updateUI(State.NO_CALL)
    Snackbar.make(
        binding.rootView,
        "Error: " + t.localizedMessage,
        Snackbar.LENGTH_LONG
    ).show()
}

Placing a call

Before placing a call you will need to check Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO and Manifest.permission.BLUETOOTH_CONNECT permissions and activate com.twilio.audioswitch.AudioSwitch

private fun makeCall() {
    val callRequest = CallRequest(
        languageFromId = 4,
        languageToId = 1,
        serviceTypeId = 1,
        genderId = null,
        data = listOf(
            AdditionalField(
                key = "MyInternalId",
                value = "1984"
            ),
            AdditionalField(
                key = "CustomCallField",
                value = "CustomCallFieldValue"
            )
        ),
        isVideo = false
    )

    boostlingoSdk.makeVoiceCall(callRequest)
        .subscribeOn(AndroidSchedulers.mainThread())
        .observeOn(AndroidSchedulers.mainThread())
        .doOnSubscribe { updateUI(State.CALLING) }
        .subscribe(
            {
                currentCall = it
            },
            ::handleError
        )
        .let { compositeDisposable.add(it) }
}

private fun makeCall() {
    val callRequest = CallRequest(
        languageFromId = 4,
        languageToId = 1,
        serviceTypeId = 1,
        genderId = null,
        data = listOf(
            AdditionalField(
                key = "MyInternalId",
                value = "1984"
            ),
            AdditionalField(
                key = "CustomCallField",
                value = "CustomCallFieldValue"
            )
        ),
        isVideo = true
    )

    boostlingoSdk.makeVideoCall(
        callRequest,
        binding.thumbnailVideoView
    )
        .subscribeOn(AndroidSchedulers.mainThread())
        .observeOn(AndroidSchedulers.mainThread())
        .doOnSubscribe { updateUI(State.CALLING) }
        .subscribe(
            {
                currentCall = it
            },
            ::handleError
        )
}

Adding a video participant renderer

is BLCallEvent.ParticipantAdded -> {
    Log.d(null, "BLCallEvent.ParticipantAdded ${callEvent.participant.accountId}")
    Log.d(null, "Participants: ${callEvent.call.participants.count()}")
    callEvent.participant
        .let {
            currentCall?.addRenderer(it.identity, binding.primaryVideoView)
        }
}

Sending a chat message

boostlingoSdk.sendChatMessage("TEST")
    .subscribeOn(AndroidSchedulers.mainThread())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
        {
            Snackbar.make(
                binding.rootView,
                "Chat message sent: " + it.text,
                Snackbar.LENGTH_LONG
            ).show()
        },
        ::handleError
    )
    .let { compositeDisposable.add(it) }

is BLCallEvent.ChatMessageReceived -> {
    Log.d(null, "BLCallEvent.ChatMessageReceived: ${callEvent.message}")
}

Dialing a third-party participant

Getting requestor profile image url.

currentCall.dialThirdParty("18004444444")
    .subscribeOn(AndroidSchedulers.mainThread())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
        {
            Log.d(null, "dialThirdParty success")
        },
        {
            Log.d(null, it.localizedMessage.orEmpty())
        }
    )
    .let { compositeDisposable.add(it) }

More Documentation

You can find more documentation and useful information below:

Package com.boostlingo.android

Boostlingo SDK package.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

2 watching

Forks

Packages

 
 
 

Contributors

Languages