Engine-only Usage¶
Use HighlightEngine directly when you need to:
- Highlight code in a ViewModel or repository layer (outside of Compose)
- Produce
AnnotatedStringfor reuse across multiple widgets - Access raw highlighted HTML for custom rendering
- Query engine metadata (supported languages, Highlight.js version)
ViewModel example¶
import dev.hossain.highlight.engine.HighlightEngine
import dev.hossain.highlight.engine.HighlightTheme
class CodeViewModel(application: Application) : AndroidViewModel(application) {
private val engine = HighlightEngine(application.applicationContext)
private val _highlighted = MutableStateFlow<AnnotatedString?>(null)
val highlighted: StateFlow<AnnotatedString?> = _highlighted.asStateFlow()
init {
viewModelScope.launch { engine.initialize() }
}
fun highlight(code: String, language: String, theme: HighlightTheme) {
viewModelScope.launch {
engine.highlight(code, language, theme).onSuccess { result ->
_highlighted.value = result.annotated
}
}
}
override fun onCleared() {
engine.close()
}
}
Using the result in Compose¶
import dev.hossain.highlight.ui.SyntaxHighlightedCodeDefaults
import dev.hossain.highlight.ui.rememberTomorrowNightTheme
@Composable
fun CodeScreen(code: String, viewModel: CodeViewModel = viewModel()) {
val highlighted by viewModel.highlighted.collectAsState()
val theme = rememberTomorrowNightTheme()
// Ask the ViewModel to highlight whenever the code changes
LaunchedEffect(code, theme) { viewModel.highlight(code, "kotlin", theme) }
SelectionContainer {
Text(
text = highlighted ?: AnnotatedString("loading..."),
style = SyntaxHighlightedCodeDefaults.codeTextStyle,
)
}
}
Raw HTML output¶
Get the raw Highlight.js HTML tokens for custom rendering pipelines:
engine.highlightToHtml("val x = 42", "kotlin").onSuccess { result ->
// result.html: "<span class=\"hljs-keyword\">val</span> x = <span class=\"hljs-number\">42</span>"
Log.d("HTML", result.html)
}
Querying engine metadata¶
engine.supportedLanguages().onSuccess { languages ->
Log.d("Engine", "Supports ${languages.size} languages")
}
engine.highlightJsVersion().onSuccess { version ->
Log.d("Engine", "highlight.js $version")
}
Error handling¶
All suspend methods return Result<T>. Failures are wrapped in HighlightException:
import dev.hossain.highlight.engine.HighlightException
engine.highlight(code, language, theme)
.onSuccess { result -> /* use result.annotated */ }
.onFailure { error ->
if (error is HighlightException) {
when (error) {
is HighlightException.WebViewInitFailed -> { /* WebView not available (Go edition, MDM-disabled, mid-update) */ }
is HighlightException.JsExecutionFailed -> { /* JS evaluation error */ }
is HighlightException.HtmlParseFailed -> { /* HTML parse error */ }
is HighlightException.ThemeNotFound -> { /* Theme CSS file missing or contains no parseable rules */ }
is HighlightException.RendererGone -> { /* WebView renderer process crashed or was killed by OS LMK */ }
is HighlightException.EngineDestroyed -> { /* engine was destroyed or closed while this call was in flight */ }
is HighlightException.Timeout -> { /* timed out waiting for WebView */ }
}
}
}
Automatic renderer recovery
If the WebView renderer process terminates (HighlightException.RendererGone), the host application
is protected from crashing. The current in-flight operation fails immediately with RendererGone,
and subsequent highlight calls automatically re-initialize a fresh WebView on demand.
Important rules¶
- Always pass
applicationContext- never an Activity context. The engine retains the context beyond Activity lifecycle. - Always call
destroy()to release the WebView when done. Destroying is not permanent: a later call on the same engine re-creates the WebView, which then needs anotherdestroy(). Don't reuse an engine after destroying it. - Use
rememberHighlightEngine()in Compose for automatic lifecycle management. HighlightEngineis safe to call from any coroutine - all WebView operations are automatically dispatched to the Main thread internally.