Skip to content

Flutter Guide for Codox + QuillJS

This guide explains how to add basic Codox integration with QuillJS into Flutter application with InAppWebView plugin

GitHub

Full integration example can be found here

Main Steps of the Integration

  1. Flutter loads local html, which contains prepared javascript code with quill editor and codox init
  2. flutter app invokes js function to setup initial editor state.
  3. flutter app adds listeners (callbacks) for codox hooks and error events.
  4. flutter app triggers js function which starts codox with config.
  5. When codox hook is triggered in javascript, the flutter corresponding callback is invoked and data is passed to flutter.

Basic Integration

Begin by preparing local files in the project.
Create seprate directory assets/ in project root to store html and javascript files

Terminal window
mkdir assets

Prepare local html

Navigate to created directory assets/ and create local html file quill.html there

Terminal window
cd assets/
touch quill.html

Download Codox lib

Create separate directory codoxLib in the project root for downloading Codox lib

Terminal window
mkdir codoxLib/

For downloading Codox Lib need NodeJS to be installed in local machine. Before proceed to next step, check if nodejs is installed

Navigate to codoxLib/ and create package.json file for javascript dependencies

Terminal window
cd codoxLib/
touch package.json

Insert Codox lib @codoxhq/quill-provider dependency into package.json file

{
"dependencies": {
"@codoxhq/quill-provider": "^1.0.1"
}
}

Install Codox lib locally

Terminal window
# will create node_modules/ dir and download codox provider inside
npm install

Copy downloaded Codox lib file into assets/ to place the lib with local html file

Terminal window
# copy from source dir to local target dir with renaming to codox.js
cp codoxLib/node_modules/@codoxhq/quill-provider/dist/index.js assets/codox.js

At this step, should have assets/ directory with following structure:

Terminal window
assets/
quill.html
codox.js

QuillJS Editor init

In local html quill.html add the following init Quill editor code.

<!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>
<!-- Import codox lib core from local file -->
<script src="file:///android_asset/flutter_assets/assets/codox.js"></script>
</head>
<body>
<!-- Create the editor container -->
<div id="editor"></div>
<script>
// create editor toolbar options
var toolbarOptions = [
[
'bold',
'italic',
'underline',
'strike',
'blockquote',
'code-block',
'image',
'clean',
],
[{ header: 1 }, { header: 2 }],
[{ list: 'ordered' }, { list: 'bullet' }],
['link'],
[{ script: 'sub' }, { script: 'super' }],
[{ intent: '-1' }, { intent: '+1' }],
[{ direction: 'rtl' }],
[{ size: ['small', false, 'large', 'huge'] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ font: [] }],
[{ align: [] }],
];
// create Quill Editor instance
var quill = new Quill('#editor', {
modules: {
toolbar: toolbarOptions, // tollbar options
},
theme: 'snow', // css theme
placeholder: 'Enter some text...',
});
</script>
</body>
</html>

InAppWebView init

In flutter app in lib/main.dart init InAppWebView with local html with Quill editor load.

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle; // Import rootBundle
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const MyWebViewPage(),
),
);
}
class MyWebViewPage extends StatefulWidget {
const MyWebViewPage({super.key});
@override
_MyWebViewPageState createState() => _MyWebViewPageState();
}
class _MyWebViewPageState extends State<MyWebViewPage> {
late InAppWebViewController _webViewController;
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<String>(
future: _loadLocalHtml(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
return InAppWebView(
initialData: InAppWebViewInitialData(
data: snapshot.data!,
/**
* IMPORTANT:
* Setting baseUrl is essential for codox sync to work:
* base url is considered by codox as "domain" which is allowed by codox subscription.
* Example: codox subscription has whitelisted domain "flutter_demo.app", configured in codox dashboard,
* then here need to specify baseUrl with http prefix, like "http://flutter_demo.app"
*/
baseUrl: Uri.parse("http://[domain from Codox subscription]"),
mimeType: 'text/html',
encoding: 'utf-8',
),
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
javaScriptEnabled:
true, // Important: this must be enabled for js to work in local html
useOnLoadResource: true,
allowUniversalAccessFromFileURLs: true,
allowFileAccessFromFileURLs: true,
),
),
);
}
}
},
),
);
}
Future<String> _loadLocalHtml() async {
try {
// Load the local HTML file content
final String htmlString =
await rootBundle.loadString('assets/quill.html');
if (htmlString.isEmpty) {
throw Exception('HTML content is empty');
}
return htmlString;
} catch (e) {
print('Error loading HTML file: $e');
return '';
}
}
}

Initial Editor Content

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 initial content from flutter app into quill editor.

In quill.html need to add javascript function to set init content to quill editor:

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');
}

In flutter in lib/main.dart need to add “onLoadStop” callback to InAppWebView and invoke javascript inside it. OnLoadStop is invoked when page is fully loaded.

class _MyWebViewPageState extends State<MyWebViewPage> {
...
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<String>(
...
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
return InAppWebView(
...
onLoadStop: (controller, url) async {
// set initial quill editor state
String initQuillStateJSON = ""; // json is expected here
// invoke javascript function and pass state as arg
await _webViewController.evaluateJavascript(
source: "setQuillInitState($initQuillStateJSON)");
},
);
}
}
},
),
);
}
}

Start Codox 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);
}

Setup flutter code to invoke javascript to start codox session inside OnLoadStop callback:

...
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<String>(
future: _loadLocalHtml(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
return InAppWebView(
initialData: InAppWebViewInitialData(
// init params
),
initialOptions: InAppWebViewGroupOptions(
// init options
),
onWebViewCreated: (controller) {
_webViewController = controller;
},
onLoadStop: (controller, url) async {
/**
* When page is fully loaded:
* - set initial state to editor
* - start codox
*/
// set initial quill editor state
String initQuillStateJSON = "";
await _webViewController.evaluateJavascript(
source: "setQuillInitState($initQuillStateJSON)");
/**
* Start codox.
*/
await _webViewController.evaluateJavascript(
source: "startCodox()");
},
);
}
}
},
),
);
}
...

Codox hooks and error events

To subscribe to codox hooks and error events, need to add related code both on javascript and flutter sides.
In quill.html need to add hooks and error events callbacks to codox config. The callbacks contain code which sends data to flutter

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() {
// invoke flutter handler and wait for response
const data = await window.flutter_inappwebview.callHandler(
'fetchDocOnNetworkReconnectHookHandler'
);
return data;
}
// callback for usersUpdate hook
function usersUpdateHook(data) {
const json = JSON.stringify(data);
// send data to flutter
window.flutter_inappwebview.callHandler(
'usersUpdateHookHandler',
json
);
}
// callback for contentChanged hook
function contentChangedHook(data) {
const json = JSON.stringify(data);
// send data to flutter
window.flutter_inappwebview.callHandler(
'contentUpdatedHookHandler',
json
);
}
// callback for codox error events
function onCodoxError(data) {
// pass data to flutter when errors from codox
window.flutter_inappwebview.callHandler(
'codoxErrorEventListener',
JSON.stringify(data)
);
}
// 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 = {
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
// provide hooks callbacks in config
hooks: {
fetchDocOnNetworkReconnect: fetchDocOnNetworkReconnectHook,
usersUpdate: usersUpdateHook,
contentChanged: contentChangedHook,
},
};
// async start codox
codox.start(codoxConfig);
}

On flutter side need to implement the handling of javascript messages

@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<String>(
future: _loadLocalHtml(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
return InAppWebView(
initialData: InAppWebViewInitialData(
// initt data params
),
initialOptions: InAppWebViewGroupOptions(
// init options
),
onWebViewCreated: (controller) {
_webViewController = controller;
/**
* Add listeners to codox hooks:
* - contentUpdated hook
* - usersUpdate
* - fetchDocOnNetworkReconnectHook
*
* + add listener to codox errors
*/
_webViewController.addJavaScriptHandler(
handlerName: "usersUpdateHookHandler",
callback: (args) {
String users = args[0];
print("[usersUpdateHookHandler]: $users");
});
_webViewController.addJavaScriptHandler(
handlerName: "contentUpdatedHookHandler",
callback: (args) {
String data = args[0];
print("[contentUpdatedHookHandler]: $data");
// get full state json
dynamic fullState =
_webViewController.evaluateJavascript(
source: "getQuillEditorState()");
print("quill editor full state json: $fullState");
});
_webViewController.addJavaScriptHandler(
handlerName: "fetchDocOnNetworkReconnectHookHandler",
callback: (args) {
print(
"[fetchDocOnNetworkReconnectHookHandler] invoked");
// fetch state from backend
// response must match schema: {content: {ops: [...]}, timestamp}
return fetchedData;
});
_webViewController.addJavaScriptHandler(
handlerName: "codoxErrorEventListener",
callback: (args) {
String data = args[0];
print("[codoxErrorEventListener]: $data");
});
},
// restt of params
);
}
}
},
),
);
}

Github

Working integration example can be found here