Introduction
The embedded learning Mobile SDK allows you to integrate an instance of your Docebo platform in your iOS, Android or React Native mobile application.
The Docebo platform instance opens when the user clicks on a button or interacts with an element - such as a link, or a string - in your mobile application. When embedded learning opens, the platform instance shows the training content you, as the Superadmin, have selected for the user based on the action the user is performing, in order to provide the best learning on the fly experience, enriching it with ad hoc training.
Depending on the embedded learning configuration, users can be automatically provisioned so that their learning in the flow of work is not an event interrupted by the need to log in.
This document is a technical guide on how to embed embedded learning into your Android mobile application.
Activating embedded learning
To activate embedded learning, reach out to Docebo via the Help Center, or by contacting your Account Manager (if your plan includes this option).
Prerequisites
Before you can proceed with this guide, please make sure you have followed the instructions in Embedded learning in your mobile application.
Native implementation
The mobile application needs to open a webview and inject some script to initialize the embedded learning SDK and to provide the two way synchronous communication between the JavaScript and the native code.
First, we declare some constants:
- the message identifier that the web page will use to request the new token
- the name of the javascript interface that we will inject in the web page
- the web page url
// We will access native methods in the web page using this identifier, eg window.<MESSAGE_HANDLER_IDENTIFIER>
const val MESSAGE_HANDLER_IDENTIFIER = "doceboFlowMessageHandler"
// The web page will use this identifier to send a message asking for a new token
const val RENEW_TOKEN_MESSAGE_IDENTIFIER = "RENEW_ACCESS_TOKEN"
// The web URL
const val WEBPAGE_URL = "<WEBPAGE_URL>"
We then need to create a screen containing the webview.
fun WebViewScreen() {
var webView: WebView? = null
AndroidView(factory = {
WebView(it).apply {
// We will place the necessary code to handle the two way communication here
}
}, update = {
webView = it
})
}
In a separate file, we have to implement the JavaScript interface; once registered, this will be available from the web page as a global variable (under the window object) and we will be able to access its functions. In this case, we create a postMessage
function that retrieves a new token when called with the specific message identifier.
class AndroidNativeInterface(private val webView: WebView?) {
@JavascriptInterface
fun postMessage(message: String) {
if (message == RENEW_TOKEN_MESSAGE_IDENTIFIER) {
GlobalScope.launch {
// Fetch the new access token and post the message to the JS side
val accessToken = getAccessToken()
accessToken?.let {
webView?.post {
webView?.evaluateJavascript(
"window.postMessage('$accessToken');",
null
)
}
}
}
}
}
}
The JavaScript interface has then to be registered inside the webview. The addJavascriptInterface
method allows us to store the interface in a global variable in the web environment (for example, available under the window object) under the specified name, which in this case is the message handler identifier defined earlier.
addJavascriptInterface(
AndroidNativeInterface(this),
MESSAGE_HANDLER_IDENTIFIER
)
Once the interface is in place we just have to instantiate the webview client and inject the script as soon as the web page is ready. The injected script will hold the function that the web page will use to communicate with the native side and initialize the SDK.
val launcherOptions = LauncherOptions(
accessToken = accessToken,
launcherCode = launcherCode,
context = context,
language = language,
domain = domain,
)
val launcherOptionsJson = Gson().toJson(launcherOptions)
webViewClient = object : WebViewClient() {
// Wait for the page to be fully loaded before injecting the script
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
val userScript = "const requestAccessToken = () => window.${MESSAGE_HANDLER_IDENTIFIER}.postMessage('${RENEW_TOKEN_MESSAGE_IDENTIFIER}'); connect('${launcherOptionsJson}');"
// Inject the script into the webview
evaluateJavascript(userScript, null)
}
}
The injected script defined the requestAccessToken
function is necessary to notify the javascript interface about the need for a new token and to provide it back to the JavaScript. The connect function is then invoked to initialize the SDK with the provided values.
The last step is to open the webview with the url that hosts the web page:
loadUrl(WEBPAGE_URL)
At this point the webview will open and the connect function will be called. Whenever the SDK requests a new token, the platform dependent postMessage
will be called through the requestAccessToken
function, which will in turn provide the new token to the JavaScript through a regular postMessage
.
Complete code
// WebViewScreen.kt
const val MESSAGE_HANDLER_IDENTIFIER = "doceboFlowMessageHandler"
const val RENEW_TOKEN_MESSAGE_IDENTIFIER = "RENEW_ACCESS_TOKEN"
const val WEBPAGE_URL = "<WEBPAGE_URL>"
fun WebViewScreen(accessToken: String, launcherCode: String, context: String, language: String, domain: String) {
var webView: WebView? = null
AndroidView(factory = {
WebView(it).apply {
val launcherOptions = LauncherOptions(
accessToken = accessToken,
launcherCode = launcherCode,
context = context,
language = language,
domain = domain,
)
val launcherOptionsJson = Gson().toJson(launcherOptions)
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
val userScript = "const requestAccessToken = () => window.${MESSAGE_HANDLER_IDENTIFIER}.postMessage('${RENEW_TOKEN_MESSAGE_IDENTIFIER}'); connect('${launcherOptionsJson}');"
evaluateJavascript(userScript, null)
}
}
addJavascriptInterface(
AndroidNativeInterface(this),
MESSAGE_HANDLER_IDENTIFIER
)
loadUrl(WEBPAGE_URL)
webView = this
}
}, update = {
webView = it
})
}
// AndroidNativeInferface.kt
class AndroidNativeInterface(private val webView: WebView?) {
@JavascriptInterface
fun postMessage(message: String) {
if (message == RENEW_TOKEN_MESSAGE_IDENTIFIER) {
GlobalScope.launch {
// Fetch the new access token and post the message to the JS side
val accessToken = getAccessToken()
accessToken?.let {
webView?.post {
webView?.evaluateJavascript(
"window.postMessage('$accessToken');",
null
)
}
}
}
}
}
}