Is there an exemple of how to bind a `Composable` ...
# compose
m
Is there an exemple of how to bind a
Composable
to an Activity lifecycle ? With
Views
, I could hold a reference to the View and call a method from
onDestroy
but I'm not sure how to do with compose ?
Copy code
override fun onDestroy() {
        super.onDestroy()
        
        // How do I get a reference to a composable or how do I dispose my composable ?
        composable.dispose()
    }
m
Since a composable is a function... how would you dispose a function? Can you provide a more specific scenario about what needs to be disposed?
m
Let's say I want to provide a Composable widget that acts like a stopwatch. I'd like users of my widget to not have to handle timers themselves and therefore my toplevel compose function will register a timer to update the time.
But I still need to stop the timer at some point.
a
If you take a look at the snippets for observing LiveData and other observables floating around using
onCommit { onDispose {} }
you can see the general pattern. You'll want to listen to both the composition commit/dispose pair and the lifecycle callbacks combined
once the
suspend
bugs are taken care of with our IR compiler setup there will probably be some variant that just gives you a suspending block that cancels on dispose, you can
collect {}
flows directly in, etc.
πŸ’― 2
πŸ‘ 3
m
Sorry I have to ask but where are the LiveData snippets πŸ˜… ? In the AOSP compose source code itself ? Or somewhere else ?
a
(I'm playing around with some animation-related ideas around this right now)
ah, sorry. General pattern:
Copy code
onCommit(observable) {
  val observer = makeObserver()
  val disposable = observable.subscribe(observer)
  onDispose {
    disposable.dispose()
  }
}
m
Ah yes, I've seen that in the Pokedex app πŸ‘
a
the parameter to
onCommit
is important; if it changes from composition to composition, the old scope will have its
onDispose
called and a new one will run. If it's equal from composition to composition, it's left alone.
m
Do I have to call something from
onDestroy
to finish composition ? Or is that taken care of automagically ?
a
what do you mean by finish composition?
m
Not sure actually
I would expect I'd have to "signal" somehow that my activity is destroyed but I'm still learning the concepts. I'll keep digging.
a
ah. The
Activity.setContent
extension should set all of that up for you
πŸ‘ 1
(and if it doesn't, it's a bug πŸ™‚ )
l
(i think right now it does not, but i agree in categorizing that as a bug)
for the moment you can call
disposeComposition
to get this behavior
πŸ‘ 2
m
Can we roll back to:
my toplevel compose function will register a timer to update the time
To me, this feels like "my compose function will call a Web service". Is that really the architecturally-correct way to do this sort of thing in Compose?
βž• 2
a
good clarification, @Mark Murphy. I take a lot of shortcuts in discussion around these things and assume that when we're talking about observable subscriptions to such things that they're backed by the usual repository-backed cached, shared and deduped structures we all know and love. Compose happens to be very good at maintaining endpoint subscriptions to such things.
Same as one might observe a LiveData from a ViewModel in arch components-land
I'm also working under the assumption that as these middle layers change, folks will find places to simplify those app-backendy parts of the pipeline
m
I suppose it's a matter of figuring out the shades of gray between:
Copy code
@Composable
private fun CountingButton() {
  Padding(8.dp) {
    val count = state { 0 }

    Button(
      text = "You clicked the button ${count.value} times!",
      onClick = { count.value += 1 }
    )
  }
}
and having the composable call a Web service. These
state
examples make things simple, but I'm still struggling with the dividing line between that and when you need something more elaborate. Going back to Martin's timer, the objective of making the timer be self-contained is wonderful. And doing it all within a pure composable should be doable. I'm just still wrapping my head around whether that's the right solution for something small-ish like a timer.
a
yeah it's quite the interesting question to ask. The usual questions of scope apply - should an operation cease if the UI for it goes away? If yes, leaving it in the composable seems not only harmless but desirable from a simplicity standpoint. If no, you probably want the usual hoisted scope patterns, of which ViewModels and repositories are both examples.
Even in your example though, there's a pretty quick motivator for hoisting that state out of
CountingButton
into a parameter with a default - testability and anything outside of the button observing, saving or restoring the current count.
πŸ‘ 1
Those natural pressures often lead to pulling more and more state ownership out of individual components and aggregating it at each layer in some way
it's fairly straightforward to wire up a composable that looks like
state {}
but ties into the most-local
ViewModelStoreOwner
, e.g.
val counter = viewModel<CounterViewModel> { ... }
if anything, compose permits - but doesn't require - re-evaluating a lot of these patterns already present in Android UIs
m
Given the "copy and paste from Stack Overflow" era that we are in, I'm just trying to provide decent advice out of the gate. Any chance you could hop in the company time machine and see how all this played out in, say, five years? 😁
a
the only thing I'm sure of is that in 5 years, any architecture-level prescriptive advice we gave out of the gate is going to look horribly naive πŸ˜„
πŸ˜… 2
if I had to make a prediction though, I'd guess that the patterns we publish for developers starting out from existing apps to integrate with pre-existing code turns into a cargo cult that lasts long past the point when that scaffolding complexity for compatibility isn't needed anymore
but people just keep doing it because it was long since accepted as best practice
and will be justified in discussions such as these as, "yes well, it's not always necessary but if you do it this way then it's transferrable knowledge that works everywhere"
😬 1
m
For the record, I played a bit with the
onCommit/onDispose
apis. It seems to work well but calling
disposeComposable()
from
onDestroy
doesn't seem to dispose anything. Calling
setContent { Container { }
instead did the job though.
m
@mbonnin disposeComposable() seems to be broken for me to. But you can get rid of the Container {} because an empty composable is enough.
πŸ‘ 1
l
i’m working on cleaning up a lot of the composition/dispose stuff right now, and i think i see what the bug is
hopefully will have this stuff fixed by next release
❀️ 1