Wednesday, 5 January 2022

Creating a queue for the evaluateJavascript function on a WebView

I have a hybrid app, some of my Activities use a WebView to display a web content. The web app that I show in the WebView has a JS interface that lets me send commands to the web app to navigate to different places or do different things.

For example if I need my web app to navigate to the "user profile" page I execute a command like:

class SomeActivity: AppCompatActivity {
   ...
   webView.evaluateJavascript("navigateTo(\"userprofile\")")
   ...
}

Then via the JS interface I get a response and the app reacts accordingly.

To improve performance I introduced a JS queue, so the JS commands are executed sequentially. Instead of calling the evaluateJavascript() function directly on the WebView I've created a custom WebView component with this JS queue set as a property.

class SomeActivity: AppCompatActivity {
   ...
   webView.jsQueue.queueEvaluateJavascript("navigateTo(\"userprofile\")")
   ...
}

Now I would like to add a new behavior on top of that, and that is being able to pre-process the commands within the queue. What I mean by pre-processing is that if I ever queue commands of the same "type", like:

class SomeActivity: AppCompatActivity {
   ...
   webView.jsQueue.queueEvaluateJavascript("navigateTo(\"userprofile\")")
   webView.jsQueue.queueEvaluateJavascript("navigateTo(\"about-me\")")
   webView.jsQueue.queueEvaluateJavascript("navigateTo(\"user-list\")")
   ...
}

What I would like to happen is that the queue is smart enough to ditch those two first "navigate" commands - "navigateTo(\"userprofile\")" and "navigateTo(\"about-me\")" - because I don't want my WebView to navigate to those two places just to finally navigate to "navigateTo(\"user-list\")".

The implementation of this JS queue looks like this:

class JsQueue(
    private val webView: WebView,
    private val scope: CoroutineScope
) {
    
    init {
        scope.launch { 
            for (jsScript in jsChannel) {
                runJs(jsScript)
            }
        }
    }

    private val jsChannel = Channel<String>(BUFFERED)

    fun queueEvaluateJavascript(script: String) {
        runBlocking {
            jsChannel.send(script)
        }
    }

    suspend fun runJs(script: String) = suspendCoroutine<String> { cont ->
        webView.evaluateJavascript(script) { result ->
            cont.resume(result)
        }
    }
}
  • How can I pre-process the js commands in the Channel<String> so I ditch duplicated js commands?
  • Also, some times my WebView will become invisible and I want to pause the queue when that happens. I'm wondering if there's any way to programmatically pause a Channel?

Edit #1

Also, some times my WebView will become invisible and I want to pause the queue when that happens. I'm wondering if there's any way to programmatically pause a Channel?

I've tried using this PausableDispatcher implementation and it seems that it is doing the trick.



from Creating a queue for the evaluateJavascript function on a WebView

No comments:

Post a Comment