TIL: Lorem ipsum text for Compose Previews

When working with Compose, I miss the tools:sample data that’s available in android View XML for rendering some sample data in view previews.

TIL: There’s nothing as extensive, but compose does provide a Lorem Ipsum preview parameter for previewing sample text.

@Preview
@Composable
private fun TextPreview(@PreviewParameter(LoremIpsum::class) text: String) {
    Box(
        modifier = Modifier
            .size(128.dp)
            .background(Color.LightGray)
            .padding(8.dp),
    ) {
        Text(text = text, overflow = TextOverflow.Ellipsis)
    }
}
Screenshot of the output showing a block of text consisting of random lorem ipsum
The output

Callbacks Vs JavaScript promises & Kotlin coroutines

Convert callback based asynchronous methods for more linear code, with Promises + Async/Await in JavaScript and Coroutines in Kotlin.

In JavaScript, wrap the callback based method inside a new Promise((resolve, reject) => {...}) call. Then call the resolve/reject method of the promise on callback completion. The result is an asynchronous promise that resolves with the result of the callback.

In Kotlin, wrap the callback based method inside a suspendCoroutine {continuation-> ...}. Then call continuation.resume or continuation.resumeWithException on callback completion. The result is an asynchronous/suspending Coroutine that returns the value of the callback.

A small drawback of these approaches is that they return a single value, while the callback can have multiple values. The solution to this is to wrap the values returned from the callback in an object (JS) or data class (Kotlin).

TIL: Webview @JavascriptInterface methods run in a different thread

I was trying to update a LiveData in a ViewModel from a Javascript method within the Webview. The Kotlin method was being called but the LiveData was not being updated. There were no errors either.

This fails silently:

@JavascriptInterface
fun onSelection(selection: String) {
    readerViewModel.setSelectedText(text)
}

After a lot of frustrating tinkering, I got the LiveData to update when I wrapped the update statement within a CoroutineScope.launch  on the Main thread.

This works:

@JavascriptInterface
fun onSelection(selection: String) {
    setSelectedText(selection)
}
...
private fun setSelectedText(text: String) {
    CoroutineScope(Dispatchers.Main).launch {
        readerViewModel.setSelectedText(text)
    }
}

It appears that the @JavascriptInterface methods are executed on a separate non-Main thread. Usually an exception is thrown when a ViewModel is updated from a non-Main thread in Kotlin. Surprisingly (and very confusingly), this exception isn’t thrown when updating the ViewModel from @JavascriptInterface. It just fails silently :/