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

molikto

04/01/2020, 3:03 AM
I've been using the code bellow for RxJava (actually I am using https://github.com/badoo/Reaktive basically the same thing). I think I just realized that it automately solves the reactive-diamond problem https://staltz.com/rx-glitches-arent-actually-a-problem.html because all recompose is lockstep with drawing. This is really nice!
z

Zach Klippenstein (he/him) [MOD]

04/01/2020, 1:36 PM
I agree it is really nice! You can simplify your code a bit though: 1. Use
onActive
instead of
remember
, so you don’t need to store your subscription in your state. 2. Annotate your state with
@Model
, which will automatically wire up recomposition so you don’t need
Recompose
or
current.composing
. You probably also want to key the subscription on the observable itself, so if the caller starts passing in a different observable, you update the subscription.
Copy code
@Model private class State(
  var set: Boolean = false,
  var value: Any? = null
)

@Composable fun <T> Observe(
  @Pivotal observable: Observable<T>,
  children: @Composable() (T) -> Unit
) {
  val current by remember { State() }
  onActive {
    val subscription = observable.subscribe {
      current.set = true
      current.value = it
    }
    onDispose { subscription.dispose() }
  }

  @Suppress("UNCHECKED_CAST")
  if (current.set) {
    children(current.value as T)
  }
}
@Pivotal
ensures that, if the caller starts passing in a different observable, your entire
Observe
composable is removed from the composition and recreated (including
current
and the subscription), which means that
children
will temporarily stop getting called until the first value is received from the new observable. If you want to hang onto the last value until the new observable emits, remove the
@Pivotal
annotation and change
onActive
to
onCommit(observable)
, which will keep your state around but just tear down and recreate the subscription.
👏 2
m

molikto

04/01/2020, 4:10 PM
thanks for the simpliciation! One thing I am not sure is making Observable pivatal, because most of time the observable created is some kind of
source.map{}
or
source.distinctUntilChanged()
so they don't have a meaningful
equals
? I do encountered a lot of time the Observable body needs to be forcely recomposed entirely, these cases I have a
key(..)
outside where I manually select a proper key
Tried it in my app. Another thing I noticed is subscribing in
onActive
will cause the observable body always miss the first frame. If the observable is a
ReplaySubject
or a
BehaviourSubject
currently having a value, then my code will compose the children during the first frame
l

Leland Richardson [G]

04/03/2020, 3:08 PM
you can use onPreCommit instead of onCommit and it will hit the first frame. Alterntively, if you use a BehaviorSubject instead of an Observable, you can initialize the value with the current value and it will work how you’d expect (though this might not always be possible)
z

Zach Klippenstein (he/him) [MOD]

04/03/2020, 5:56 PM
You might want to consider `remember`ing your Observable chains, so it's not recreating all the operators on every compose pass. If you do that locally then you can take advantage of referential equality, but I don't know if that's a scalable solution.
m

molikto

04/24/2020, 6:04 AM
I ended up with a live-data adapter like API where an
State<T>
is returned. Although in case of Observable you need to provide a default value. I found this works better than what I have before, 1. less indentation... 2. I actually needs to provide an meaningful default value most of time. 2. it is convenient to work with multiple observables compared to
combineLatest