Issue
I have a list of Urls and I want to iterate over the list to load each one of them, but when trying to do that, the iteration fails, and it only loads the last item of the list.
Initially, this url is being loaded when the WebView first starts running:
webView.loadUrl("https://translate.google.com/?sl=$from&tl=$into&text=Hello&op=translate")
I have a list of Urls that I want to iterate over them:
val items = listOf(
"https://translate.google.com/?sl=$from&tl=$into&text=First text&op=translate",
"https://translate.google.com/?sl=$from&tl=$into&text=Second text&op=translate",
"https://translate.google.com/?sl=$from&tl=$into&text=Third text&op=translate"
)
When the page finished loading, I iterate over the list to load each one of the urls:
webView.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
GlobalScope.launch {
delay(5000)
withContext(Dispatchers.Main) {
items.forEach {
view?.loadUrl(it)
injectJavascript(view)
}
}
}
}
}
I get only the result of the last Url, which is "Third text", what's the problem? And how can I make the code work?
Solution
The problem you are facing is due to the fact that WebView's loadUrl()
function operates asynchronously.
When you call loadUrl()
in your forEach
loop, it initiates the loading of the URL, but it does not wait for the page to finish loading before proceeding to the next URL.
Consequently, the loop quickly iterates over all the URLs and only the last URL has the chance to fully load, which is why you are only seeing the result of the last URL.
To handle this, you could use a queue-like approach, where you load the next URL only when the current one has finished loading.
For instance:
val items = mutableListOf(
"https://translate.google.com/?sl=$from&tl=$into&text=First text&op=translate",
"https://translate.google.com/?sl=$from&tl=$into&text=Second text&op=translate",
"https://translate.google.com/?sl=$from&tl=$into&text=Third text&op=translate"
)
var isLoading = false
webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
isLoading = true
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
isLoading = false
}
}
val handler = Handler(Looper.getMainLooper())
val runnable = object : Runnable {
override fun run() {
if (!isLoading && items.isNotEmpty()) {
val nextUrl = items.removeAt(0)
webView.loadUrl(nextUrl)
injectJavascript(webView)
}
handler.postDelayed(this, 5000)
}
}
handler.post(runnable)
In this revised code:
Here, onPageStarted
and onPageFinished
are used to maintain a flag isLoading
that indicates whether the WebView is currently loading a page or not.
The runnable
object checks this flag, and if the WebView is not currently loading a page, it starts loading the next URL. This runnable
object is posted to a Handler
every 5 seconds.
In this way, the next URL is loaded only when the WebView is not currently loading a page.
Ok, it's working, but why are the URLs loading twice?
Not in a row necessarily, but after several other requests are made, the same request is made after that.
The WebViewClient
onPageStarted
and onPageFinished
methods are called multiple times per single loadUrl
call due to the nature of how web pages load - they load multiple resources like scripts, stylesheets, images, frames, etc. Therefore, if a web page has iframes
, for example, these methods can be called for each iframe's URL.
One way to ensure that you only react to the main page load is to compare the URL parameter in the onPageStarted
and onPageFinished
callbacks with the URL you are loading. If they match, then it is the main page load. Here is how you can modify your code to do this:
val items = mutableListOf(
"https://translate.google.com/?sl=$from&tl=$into&text=First text&op=translate",
"https://translate.google.com/?sl=$from&tl=$into&text=Second text&op=translate",
"https://translate.google.com/?sl=$from&tl=$into&text=Third text&op=translate"
)
var isLoading = false
var currentUrl: String? = null
webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
if (url == currentUrl) {
isLoading = true
}
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
if (url == currentUrl) {
isLoading = false
}
}
}
val handler = Handler(Looper.getMainLooper())
val runnable = object : Runnable {
override fun run() {
if (!isLoading && items.isNotEmpty()) {
currentUrl = items.removeAt(0)
webView.loadUrl(currentUrl!!)
injectJavascript(webView)
}
handler.postDelayed(this, 5000)
}
}
handler.post(runnable)
Here, a currentUrl
variable is introduced: each time a new URL is loaded, currentUrl
is updated to that URL.
Then, in onPageStarted
and onPageFinished
, the isLoading
flag is updated only when the url
parameter matches currentUrl
. This ensures that isLoading
reflects whether the main page is loading, not any of the subresources.
Answered By - VonC
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.