Skip to content

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

  1. Create and load an HTML file with the Quill editor and Codox initialization logic using a WebView

  2. In Kotlin, fetch the initial document state from the backend and initialize the Quill editor.

  3. In Kotlin, set up listeners (callbacks) to respond to Codox client library session and error events.

  4. Explicitly start Codox session via a javascript interface.

  5. 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

  1. 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
  2. Navigate to created directory app/src/main/assets/ and create local html file:

    Terminal window
    cd app/src/main/assets/
    touch quill.html
  3. Create a directory for Codox library files:

    Terminal window
    mkdir codoxLib/
  4. Navigate to the codoxLib/ directory and create a package.json file:

    Terminal window
    cd codoxLib/
    npm init -y
  5. Install the library:

    Terminal window
    npm install @codoxhq/quill-provider@latest
  6. 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.js

    The 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.kotlinquillcodox
import android.os.Bundle
import android.webkit.JavascriptInterface
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.kotlinquillcodox.databinding.ActivityMainBinding
import java.io.BufferedReader
import java.io.InputStreamReader
import 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 editor
function 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 instance
var quill = new Quill('#editor', {
// editor params
});
// function to start codox session
function 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 instance
var quill = new Quill('#editor', {
// editor params
});
// callback for fetchDocOnNetworkReconnect hook
async 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 hook
function usersUpdateHook(data) {
const json = JSON.stringify(data);
// send data to kotlin
AndroidApp.usersUpdate(json);
}
// callback for contentChanged hook
function contentChangedHook(data) {
const json = JSON.stringify(data);
// send data to kotlin
AndroidApp.contentChanged(json);
}
// callback for codox error events
function onCodoxError(data) {
let json = JSON.stringify(data);
// send data to kotlin
AndroidApp.onCodoxError(json);
}
// start codox session
function 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