j

    julioromano

    1 year ago
    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?
    Colton Idle

    Colton Idle

    1 year ago
    You could maybe use alpha to make it invisible instead?
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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

    1 year ago
    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?
    @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.
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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.:
    @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

    1 year ago
    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!
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    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

    1 year ago
    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 recomposed 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).
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    1 year ago
    I have the feeling that when the other tab is active, the 
    MyWebView
     composable is removed from the composition
    Ah, yea that sounds right