Creating a queue for the evaluateJavascript function on a WebView
I have a hybrid app; some of my Activities use a WebView to display 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 different places or do other 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, I get a response via the JS interface, and the app reacts accordingly.
I introduced a JS queue to improve performance, 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 behaviour on top of that, which 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, sometimes my WebView will become invisible, and I want to pause the queue when that happens. I wonder if there's any way to programmatically pause a Channel?
Edit #1
Also, sometimes my WebView will become invisible, and I want to pause the queue when that happens. I wonder if there's any way to programmatically pause a Channel?
I've tried using this PausableDispatcher
implementation, and it seems to be doing the trick.
Solution 1:
All of the command examples you gave follow a specific pattern: they're all functions. We can use this to our advantage!
First, let's create some terminology.
navigateTo()
is a function (of course!).
And lets call the navigateTo
part of the function a type
.
An example of some type
s are:
console.log() => `console.log`,
gotoUrl(url) => `gotoUrl`.
I just made this terminology up. But it will help you understand the logic.
Now, what we need to do is look at the array of commands, understand it's type
, and check if any other commands have the same type. If they do, they need to be excluded from the queue before the queue is executed.
Easy!
I've written a sample code that you can integrate with your script:
// Example array of commands for demonstration.
let commands = [
'navigateTo("a")',
'navigateTo("b")',
'navigateTo("c")',
];
/** A list of non-duplicate types*/
let types = [];
/** A list of non-duplicate commands */
let newCommands = [];
// Reverse the array because the most important commands start from the end of array.
for(let command of commands.reverse()){
let type = command.slice(0, command.indexOf('('));
// Determine if type already exists
let alreadyExists = false;
for(let commandType of types){
if(type == commandType){
alreadyExists = true;
break;
}
}
if(alreadyExists)
// Type already exists. Do not add to command list.
continue;
// This type & command does not exist.
// Update command & type arrays
types.push(type);
newCommands.push(command)
}
// New Commands
console.log("Commands: ", newCommands);
// If you want to keep same queue order without duplicates:
console.log("Commands: ", newCommands.reverse())
Let me know if I missed the mark answering this question. Otherwise, cheers to a great queue system!