Kotlin Guide for Codox + QuillJS
This guide shows you how to integrate Codox with QuillJS in a Kotlin application using the WebView class.
GitHub
Full integration example can be found here
Main Steps of the Integration
-
Create and load an HTML file with the Quill editor and Codox initialization logic using a WebView
-
In Kotlin, fetch the initial document state from the backend and initialize the Quill editor.
-
In Kotlin, set up listeners (callbacks) to respond to Codox client library session and error events.
-
Explicitly start Codox session via a javascript interface.
-
When Codox triggers hooks in the JavaScript code, corresponding callbacks in Kotlin are invoked, passing the necessary data.
Step-by-Step Integration Tutorial
Step 1: Set Up Local Files
-
The first step is to prepare the local files for your project. Create a directory app/src/main/assets/ in project root to store html and javascript files.
Terminal window mkdir app/src/main/assets -
Navigate to created directory app/src/main/assets/ and create local html file:
Terminal window cd app/src/main/assets/touch quill.html -
Create a directory for Codox library files:
Terminal window mkdir codoxLib/ -
Navigate to the codoxLib/ directory and create a package.json file:
Terminal window cd codoxLib/npm init -y -
Install the library:
Terminal window npm install @codoxhq/quill-provider@latest -
Copy the Codox lib to the app/src/main/assets/ directory:
Terminal window cp codoxLib/node_modules/@codoxhq/quill-provider/dist/index.js app/src/main/assets/codox.jsThe
app/src/main/assets/
directory should now look like this:Terminal window assets/├── quill.html└── codox.js
Step 2: Initialize QuillJS Editor
Add the following code to quill.html
to set up the Quill editor:
<!DOCTYPE html><html lang="en"> <head> <title>Codox+QuillJS Integration</title> <!-- Include quill css stylesheet --> <link href="https://cdn.quilljs.com/1.3.7/quill.snow.css" rel="stylesheet" />
<!-- Include the Quill library --> <script src="https://cdn.quilljs.com/1.3.7/quill.js"></script> </head> <body> <!-- Editor Container --> <div id="editor"></div>
<script> // create editor toolbar options var toolbarOptions = [ ['bold', 'italic', 'underline', 'strike'], [{ header: 1 }, { header: 2 }], [{ list: 'ordered' }, { list: 'bullet' }], ['link', 'image'], ];
var quill = new Quill('#editor', { modules: { toolbar: toolbarOptions }, theme: 'snow', placeholder: 'Start typing...', }); </script> </body></html>
Step 3: Initialize WebView in Kotlin
In kotlin app in MainActivity.kt init WebView with local html with Quill editor load.
To display the HTML/Javascript app in Kotlin, we will use the WebView class. The following code in MainActivity.kt
sets this up:
package com.example.kotlinquillcodoximport android.os.Bundleimport android.webkit.JavascriptInterfaceimport android.webkit.WebSettingsimport android.webkit.WebViewimport android.webkit.WebViewClientimport android.widget.Toastimport androidx.appcompat.app.AppCompatActivityimport com.example.kotlinquillcodox.databinding.ActivityMainBindingimport java.io.BufferedReaderimport java.io.InputStreamReaderimport android.util.Log
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Inflate the binding layout binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // Access WebView using ViewBinding val webView: WebView = binding.webView // Enable JavaScript val webSettings: WebSettings = webView.settings webSettings.javaScriptEnabled = true webView.settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW /** * Setting baseUrl to a domain whitelisted in * the Subscription dashboard */ val baseUrl = "http://[domain from codox subscription]/" val htmlData = assets.open("quill.html").bufferedReader().use { it.readText() } webView.loadDataWithBaseURL(baseUrl, htmlData, "text/html", "UTF-8", null)
webView.webViewClient = object : WebViewClient() }}
Step 4: Inject Codox library script
Inject Codox library script codox.js into WebView after local html page is loaded. The following code in MainActivity.kt
sets this up:
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
// other configs
// Set a custom WebViewClient to handle URLs within WebView webView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) // Inject codox lib injectCodoxLib() // other actions } } }
// Function to inject codox lib script into webview private fun injectCodoxLib() { val fileName = "codox.js" val inputStream = assets.open(fileName) val bufferedReader = BufferedReader(InputStreamReader(inputStream)) val stringBuilder = StringBuilder() bufferedReader.forEachLine { line -> stringBuilder.append(line) } val jsContent = stringBuilder.toString() binding.webView.evaluateJavascript(jsContent, null) }
...
}
Step 5: Initialize the Editor State
Before initiating or joining a Codox session, the latest version of the document content must be available and must be setup in editor. The following code shows how to set up this from Kotlin app.
Add an initialization function in your HTML/Javascript quill.html
to expose a method for Kotlin to set up the editor with initial content.
var quill = new Quill('#editor', { ...options,});// callback to set initial state for editorfunction setQuillInitState(initQuillState) { // Set initial content to Quill quill.setContents(initQuillState, 'silent');}
Then, invoke this function from Kotlin after the page has been fully loaded using the onPageFinished
method from WebView .
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
// other configs
// Set a custom WebViewClient to handle URLs within WebView webView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) // Inject codox lib first
// init editor state - should be fetched from backend val initStateJson = "" // json is expected setInitEditorState(initStateJson) // other actions } } }
private fun setInitEditorState(initStateJson: String) { // invoke js to set init content to quill editor binding.webView.evaluateJavascript("setQuillInitState($initStateJson);", null) }
...
}
Start Codox Session
Setup start session code in quill.html.
To start or joining a Codox session, you need to explicitly call codox.start
to connect to the session.
Setup start session code in quill.html
:
var codox = null; // codox instance will be assigned
// create Quill Editor instancevar quill = new Quill('#editor', { // editor params});
// function to start codox sessionfunction startCodox(/* can pass params here, e.g. docId, apiKey, username */) { // create new instance codox = new Codox();
// create codox config const codoxConfig = { app: 'quilljs', editor: quill, docId: 'demo_docId', //this is the unique id used to distinguish different documents username: 'demo_username', //unique user name apiKey: '[your apiKey here]', // codox apiKey }; // async start codox codox.start(codoxConfig);}
Then, invoke this function from Kotlin after the page has been fully loaded using the onPageFinished
callback from WebView, in MainActivity.kt
. Note that starting the session proceeds after the initial document state has been passed into the editor.
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
// other configs
// Set a custom WebViewClient to handle URLs within WebView webView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { super.onPageFinished(view, url) // Inject codox lib injectCodoxLib() // init editor state - should be fetched from backend setInitEditorState(initStateJson) // start codox startCodox() } } }
private fun startCodox() { // invoke codox start in javascript binding.webView.evaluateJavascript("startCodox();", null) }
...
}
Optional: Codox Hooks and Error events
You can listen to events from Codox to allow the Kotlin app to respond to content change notifications, member change notifications, and errors triggered within the Codox session.
In quill.html
, define callback functions and include them in the config object passed to Codox. These callbacks are triggered internally when specific events occur and serve as bridges to invoke corresponding handlers in the Kotlin app.
var codox = null; // codox instance will be assigned
// create Quill Editor instancevar quill = new Quill('#editor', { // editor params});
// callback for fetchDocOnNetworkReconnect hookasync function fetchDocOnNetworkReconnectHook() { return await new Promise((resolve, reject) => { // see sample code on github for details
// define handler for Kotlin response window.fetchDocOnReconnectHookResponse = (data) => { const { content, timestamp } = data; resolve({ content, timestamp }); delete window.fetchDocOnReconnectHookResponse; };
// trigger kotlin to fetch state AndroidApp.fetchDocOnNetworkReconnect(); });}
// callback for usersUpdate hookfunction usersUpdateHook(data) { const json = JSON.stringify(data); // send data to kotlin AndroidApp.usersUpdate(json);}
// callback for contentChanged hookfunction contentChangedHook(data) { const json = JSON.stringify(data); // send data to kotlin AndroidApp.contentChanged(json);}
// callback for codox error eventsfunction onCodoxError(data) { let json = JSON.stringify(data); // send data to kotlin AndroidApp.onCodoxError(json);}
// start codox sessionfunction startCodox() { // create new instance codox = new Codox();
// subscribe to codox error events and pass callback codox.on('error', onCodoxError);
// demo username - random number generation let username = `user_${Math.round(Math.random() * 1000)}`; // create codox config const codoxConfig = { // ...
// provide hooks callbacks in config hooks: { fetchDocOnNetworkReconnect: fetchDocOnNetworkReconnectHook, usersUpdate: usersUpdateHook, contentChanged: contentChangedHook, }, };
// async start codox codox.start(codoxConfig);}
On the Kotlin side you need to setup handlers to manage JavaScript messages sent from Codox, like the following example in MainActivity.kt
:
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
// other configs
// Add a JavaScript interface to interact with JavaScript in HTML webView.addJavascriptInterface(WebAppInterface(), "AndroidApp")
}
...
/** JavaScript interface class to communicate with javacript code: js code can invoke the api to pass data back here to kotlin */ inner class WebAppInterface() {
@JavascriptInterface fun contentChanged(json: String) { // take full editor state and do any custom actions, like write to db runOnUiThread { binding.webView.evaluateJavascript("getQuillEditorState();") { state -> // Handle the JavaScript result here } } }
@JavascriptInterface fun usersUpdate(usersJson: String) { // handle users data }
@JavascriptInterface fun onCodoxError(dataJson: String) { // handle codox error eventts }
@JavascriptInterface fun fetchDocOnNetworkReconnect() { /** NOTE: by design of communication between js and kotlin, js does not wait for response from kotlin here, that's why here, when kotlin needs to pass back response to js, it invokes another method which is processed by js. In js code it is coded as if js is waiting for response (using lang specific features) **/
val content = "" // json expected runOnUiThread { binding.webView.evaluateJavascript("window.fetchDocOnReconnectHookResponse($content);", null) } } }
}
Github
A working integration example can be found here