https://kotlinlang.org logo
#compose
Title
# compose
j

julioromano

02/25/2021, 1:14 PM
Is there a way to “hide/show” a composable in the hierarchy without making it recompose? I’m hiding/showing an
AndroidView
with a
WebView
inside and every time it becomes visible again it will recompose and therefore reload the website. Any ideas how to get around this?
1
c

Colton Idle

02/25/2021, 3:23 PM
You could maybe use alpha to make it invisible instead?
z

Zach Klippenstein (he/him) [MOD]

02/25/2021, 3:47 PM
A composable can be invoked (recomposed) any number of times - if something relies on a specific number of recompositions, something is wrong. It sounds like there’s either a bug in
AndroidView
or
WebView
here, or you’re triggering a reload in the composable directly instead of using an effect.
j

julioromano

02/25/2021, 6:42 PM
This is how the composable looks like more or less (it is more complex than that though but the gist is that the
factory
param is stateless whilst the stuff that changes goes in the
update
lambda). Am I doing it wrong?
Copy code
@Composable
fun MyWebView(url: String) {
  AndroidView(
    factory = {
      WebView(it)
    }
  ) {
    it.loadUrl(url)
  }
}
I understand that if
url
changes it will recompose but in my case it’s not changing.
z

Zach Klippenstein (he/him) [MOD]

02/25/2021, 7:23 PM
loadUrl
that lambda you’re calling
it.loadUrl
from, i believe, is the update function. It should effectively be idempotent, and should typically only set state on your view.
loadUrl
is, from what you described, a side effect. It’s not just setting some state, it’s actually triggering an action. That’s the problem. Instead, you should save the
WebView
instance somewhere (e.g. a
MutableState
in your composable), then use a
DisposableEffect
or something to trigger the load. E.g.:
Copy code
@Composable fun MyWebView(url: String) {
  var webView by remember { mutableStateOf<WebView?>(null) }
  AndroidView({ context ->
    WebView(context).also { webView = it }
  })
  DisposableEffect(webView, url) {
    webView.loadUrl(url)
    onDispose {
      webView.cancelLoading()
    }
  }
}
j

julioromano

02/25/2021, 7:30 PM
loadUrl
 that lambda you’re calling 
it.loadUrl
 from, i believe, is the update function. It should effectively be idempotent, and should typically only set state on your view.
Yes,
loadUrl()
is a method of the
WebView
class. I thought that calling it was actually “setting state on my view” (isn’t the currently shown url an internal state of the WebView?).
It’s not just setting some state, it’s actually triggering an action. That’s the problem.
I think I’m not getting it: What is the triggered action? I really thought it was setting state on the webview.
Instead, you should save the WebView instance somewhere (e.g. a MutableState in your composable), then use a DisposableEffect or something to trigger the load.
Sounds like a plan! I’ll try that, thanks a lot!
z

Zach Klippenstein (he/him) [MOD]

02/25/2021, 8:11 PM
The javadoc on
loadUrl
is pretty much useless: “Loads the given URL.” No duh. But from the behavior you’re describing, it sounds like calling
loadUrl
triggers the web view to load the page from scratch every time, even if it’s the same URL as the view is currently showing. That’s why I’m thinking it’s actually a side effect and not an idempotent state setter.
If
loadUrl
actually were idempotent and nooped if the same value was sent twice, it should probably be called
setUrl
, and then your code would be correct.
j

julioromano

02/25/2021, 8:17 PM
That’s why I’m thinking it’s actually a side effect and not an idempotent state setter.
Very clear, thanks for the explanation. Now I got the nuance.
I’ve tried your approach Zach though the WebView still reloads. Thing is that
MyWebView
is conditionally called inside another composable (it’s a tabbed screen: depending on the active tab the code will either call
MyWebView()
or another composable instead. My compose mental model is not accurate enough but I have the feeling that when the other tab is active, the
MyWebView
composable is removed from the composition, therefore when switching tabs again it is ~re~composed from scratch and therefore it reloads the website.
What I meant to say is that not only
loadUrl
is called again, but also the
factory
lambda is.
I managed to make it working by hoisting the
var webView by remember { mutableStateOf<WebView?>(null) }
up to the outer composable. This way the webview keeps its state (even the web page scroll position) unfortunately when switching tabs there’s still a visual artifact: the webview “flashes” once (it appears, then briefly disappears and then reappears).
z

Zach Klippenstein (he/him) [MOD]

02/26/2021, 12:47 AM
I have the feeling that when the other tab is active, the 
MyWebView
 composable is removed from the composition
Ah, yea that sounds right
🙏 1
5 Views