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 iOS 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, 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,
// for example, window.<MESSAGE_HANDLER_IDENTIFIER>
let MESSAGE_HANDLER_INDENTIFIER = "doceboFlowMessageHandler"
// The web page will use this identifier to send a message asking for a new token
let RENEW_TOKEN_MESSAGE_INDENTIFIER = "RENEW_ACCESS_TOKEN"
// The web URL
let WEBPAGE_URL = "<WEBPAGE_URL>"
Next, proceed to create a full screen WebView that will show the web page and load the SDK.
struct WebView: UIViewRepresentable {
// We'll define our message controller here
func makeUIView(context: Context) -> WKWebView {
return WKWebView(frame: .zero)
}
}
As a two way communication between the native code and JavaScript code needs to be performed, next, create a userContentController
in the WebView body structure and assign it a message controller which implements the WKScriptMessageHandlerWithReply
protocol.
let messageController: MessageController;
class MessageController: NSObject, WKScriptMessageHandlerWithReply {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) async -> (Any?, String?) {
let messageType = message.name;
let messageBody = message.body as! String;
if messageType == MESSAGE_HANDLER_INDENTIFIER && messageBody == RENEW_TOKEN_MESSAGE_INDENTIFIER {
let accessToken = await getAccessToken()
return (accessToken, nil)
}
return ("", nil)
}
}
The WebView is then instantiated and the JavaScript necessary to provide the two way communication and to initialize the SDK is injected into it as soon as the document is loaded.
Next, the messageController
created earlier is connected to the userContentController
to handle the messages received from the JavaScript.
func updateUIView(_ webView: WKWebView, context: Context) {
let launcherOptionsValues: [String: String] = [
"domain": domain,
"accessToken": accessToken,
"launcherCode": launcherCode,
"context": context,
"language": language,
]
let encoder = JSONEncoder()
if let serializedLauncherOptions = try? encoder.encode(launcherOptionsValues) {
let launcherOptionsJson = String(data: serializedLauncherOptions, encoding: .utf8) ?? ""
let source = """
const requestAccessToken = () => window.webkit.messageHandlers.\(MESSAGE_HANDLER_INDENTIFIER).postMessage('\(RENEW_TOKEN_MESSAGE_INDENTIFIER)').then(window.postMessage); \
connect('\(launcherOptionsJson)');
"""
let userScript = WKUserScript(
source: source,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true
)
webView.configuration.userContentController.addUserScript(userScript)
webView.configuration.userContentController.addScriptMessageHandler(
messageController,
contentWorld: .page,
name: MESSAGE_HANDLER_INDENTIFIER
)
}
let request = URLRequest(url: URL(string: WEBPAGE_URL)!)
}
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:
_ = webView.load(request)
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.swift
let MESSAGE_HANDLER_INDENTIFIER = "doceboFlowMessageHandler"
let RENEW_TOKEN_MESSAGE_INDENTIFIER = "RENEW_ACCESS_TOKEN"
let WEBPAGE_URL = "<WEBPAGE_URL>"
struct WebView: UIViewRepresentable {
let messageController: MessageController;
class MessageController: NSObject, WKScriptMessageHandlerWithReply {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) async -> (Any?, String?) {
let messageType = message.name;
let messageBody = message.body as! String;
if messageType == MESSAGE_HANDLER_INDENTIFIER && messageBody == RENEW_TOKEN_MESSAGE_INDENTIFIER {
let accessToken = await getAccessToken()
return (accessToken, nil)
}
return ("", nil)
}
}
func makeUIView(context: Context) -> WKWebView {
return WKWebView(frame: .zero)
}
func updateUIView(_ webView: WKWebView, context: Context) {
let launcherOptionsValues: [String: String] = [
"domain": domain,
"accessToken": accessToken,
"launcherCode": launcherCode,
"context": context,
"language": language,
]
let encoder = JSONEncoder()
if let serializedLauncherOptions = try? encoder.encode(launcherOptionsValues) {
let launcherOptionsJson = String(data: serializedLauncherOptions, encoding: .utf8) ?? ""
let source = """
const requestAccessToken = () => window.webkit.messageHandlers.\(MESSAGE_HANDLER_INDENTIFIER).postMessage('\(RENEW_TOKEN_MESSAGE_INDENTIFIER)').then(window.postMessage); \
connect('\(launcherOptionsJson)');
"""
let userScript = WKUserScript(
source: source,
injectionTime: .atDocumentEnd,
forMainFrameOnly: true
)
webView.configuration.userContentController.addUserScript(userScript)
webView.configuration.userContentController.addScriptMessageHandler(
messageController,
contentWorld: .page,
name: MESSAGE_HANDLER_INDENTIFIER
)
}
let request = URLRequest(url: URL(string: WEBPAGE_URL)!)
}
}