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

Manu Eder

02/24/2021, 11:30 AM
Hey, I'm new to compose, been reading the documentation. One thing I'm wondering about is, why does the documentation promote passing down state that is to be mutated downstream as two parameters (
state: T, onChange: (T) -> Unit
) instead of a single parameter (
stateD: MutableState<T>
). E.g. why is the
Checkbox2
-example below promoted over
Checkbox1
. (I know the two are subtly different in when the state that the modification is based on is read.) More generally, I think what I was expecting to find, but haven't seen yet is some standard way to handle what I expect to be a common case of creating some "widget" whose entire state can be described by some (immutable data) class and where "subwidgets" modify part of that state. I.e. I guess I'm looking for a blessed function:
(MutableState<T>, Lens<T, S>) -> MutableState<S>
or, with automatic creation of Lenses (https://www.reddit.com/r/Kotlin/comments/4i5wsp/lenses_for_kotlin/):
(MutableState<T>, KProperty1<T, S>) -> MutableState<S>
. Does something like this exist? Is there a good reason why it shouldn't?
Checkbox1
and
Checkbox2
-example:
Copy code
@Composable
fun Checkbox1(stateD: MutableState<Boolean>) {
    var state by stateD
    Button(onClick = {state = !state}) {
        Text( if (state) "on" else "off")
    }
}

@Preview
@Composable
fun UseCheckbox1() {
    val stateD = remember { mutableStateOf(false) }
    Checkbox1(stateD)
}

@Composable
fun Checkbox2(stateVal: Boolean, stateChanger: (Boolean) -> Unit ) {
    Button(onClick = {stateChanger(!stateVal)}) {
        Text( if (stateVal) "on" else "off")
    }
}

@Preview
@Composable
fun UseCheckbox2() {
    val stateD = remember { mutableStateOf(false) }
    var state by stateD
    Checkbox2(stateVal = state, stateChanger = {state = it} )
}
a

allan.conda

02/24/2021, 12:11 PM
composable with mutablestate is stateful, the other approach is to make the composable stateless. Read more here https://developer.android.com/jetpack/compose/state
m

Manu Eder

02/24/2021, 12:32 PM
That's the exact page I spent a lot of time looking at and thinking about :-) I'll try to rephrase: For any not-completely-trivial "widget" built with compose which reacts to user interaction there will be "state" somewhere. I typically won't want to "create" the state variable in the widget itself (with
remember { mutableStateOf( ... ) }
), but instead will want to pass it down. The documentation promotes passing down a value and a setter (which is very similar to a getter and a setter). I would like to be able to just say "use this field of this variable that I already have" (which was probably passed down to me, and might again be a field of some other variable), without having to write getters and setters all the time. So, I'd like to be able to have something like
Copy code
data class TwoBools( val one: Boolean, val two: Boolean )

@Composable fun TwoCheckboxes( state: GetSet<TwoBools> ) {
  Checkbox( state.field(TwoBools::one) )
  Checkbox( state.field(TwoBools::two) )
}

@Composable fun Checkbox( state: GetSet<Boolean> ) {
  ...
}
field
would be a member/extension function of GetSet<T>. And
GetSet<T>
might or might not be equal to
MutableState<T>
.
The type of
field
would be the aforementioned
(GetSet<T>).(KProperty1<T, S>) -> GetSet<S>
I think I can probably write
field
. This just seems like such an obvious pattern that I felt it should probably exist somewhere already. Also, I don't understand the internals of compose and don't know what kind of performance implications the different ways of doing this might have. Should
GetSet<T>
be an interface (i.e. =
MutableState<T>
?), should it be
data class GetSet<T>(get: () -> T, set: (T) -> Unit)
, should it be
data class GetSet<T>(get: T, set: (T) -> Unit)
, etc...?
(Without the proposed field function the
TwoCheckboxes
code above would look like this:
Copy code
data class TwoBools( val one: Boolean, val two: Boolean )

@Composable fun TwoCheckboxes( state: TwoBools, stateSetter: (TwoBools) -> Unit ) {
  Checkbox( state.one, { stateSetter(state.copy(one=it)) } )
  Checkbox( state.two, { stateSetter(state.copy(two=it)) } )
}

@Composable fun Checkbox( state: Boolean, stateSetter: (Boolean) -> Unit ) {
  ...
}
, which isn't a lot longer, but does get kinda repetitive, and the field name is referenced twice instead of once...)
a

Albert Chang

02/24/2021, 2:13 PM
I think it depends on whether you want to allow your user to control the state change. Taking the current value and a lambda allows the user to prevent the state change under certain conditions, or do something extra when the state changes. On the contrary, if the state change shouldn't be interfered by the user, you should use
MutableState
or something like that.
LazyListState
is a good example. It is essentially a mutable state.
m

Manu Eder

02/24/2021, 3:25 PM
Thanks for the reply 🙂
MutableState<T>
is an interface. Anything that you can do with
currentValue
and
lambdaFun
you can do with `MutableState<T>`:
Copy code
object : MutableState<Boolean> {
  override operator fun component1() = currentValue
  override operator fun component2() = lambdaFun
  override var value : Boolean
    get() = currentValue
    set(newVal) = lambdaFun(newVal)
}
(And you can make this less verbose if you plan to do it a lot.) I noticed LazyListState as one of the few examples where both getter and setter are passed as one variable. I was just wondering why it wasn't using something like
MutableState<ImmutableLazyListState>
, i.e. why there wasn't some kind of standardized way of doing what LazyListState does.
a

Albert Chang

02/25/2021, 1:21 AM
Though
MutableState
is an interface, it is the
SnapshotMutableStateImpl
class that actually enables the
Recomposer
to subscribe to changes of the value (the subscription relies on the
Snapshot
mechanism). You own implementation of
MutableState
won't have this ability thus automatic recomposition will not work. You can verify this yourself. Besides
LazyListState
, essentially the return types of all
remember*State()
functions are mutable states, such as
ScaffoldState
,
ScrollState
, etc. They contain relatively complex logics and provide the user with extra methods.
5 Views