Hi Folks, Need some guidance on 1. Whats the righ...
# compose
n
Hi Folks, Need some guidance on 1. Whats the right patterns to pass parameters to Composable functions, if they are related and can be grouped. Compose API guidelines address this with
Jetpack Compose framework and Library development SHOULD declare hoisted state types as interfaces instead of abstract or open classes if they are not declared as final classes
at https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-api-guidelines.md#default-policies-through-hoisted-state-objects It is understood that keeping Extensibility of hoisted type in mind, it’s better to expose Interface instead of concrete types, but if for a given environment if Extensibility is not a priority can we use
data
class a parameter to composables? is this encouraged?
Copy code
data class BarState {
    val name: String
    val avatarUrl: String
}

@Composable
fun Bar(barState: BarState) {
If its okay to use as above, Again its understood that we can’t have access to LocalContext or Theme objects inside data class, an easy workaround is
Copy code
data class BarState(
    val name: String
    val avatarUrl: String
    val contentColor: @Composable () -> Color = {
        MaterialTheme.colors.primary
    }
)
This makes the
BarState
data class mutable, hence the input to composable too, is this pattern still has any value? Have observed in the forum to avoid ever using
[Mutable]State<T>
in a parameter list to composable
getFoo: () -> Foo
is always going to be a more flexible parameter than
foo: State<Foo>
2. With this , if i can pas a function as a parameter to composable, why can’t i use same inside a data class? Any feedback/ inputs will be greatly helpful.
m
getFoo: () -> Foo
is preferred over
foo: State<Foo>
in function parameters for smart re-composition purposes, so, in this example:
Copy code
@Composable
fun MyFunc(foo: State<Foo>) {
    Button(/**/) {
        // reading foo parameter
    }
}
when foo changes, compose will schedule a re-composition for
MyFunc
even when the value is actually read & used within `Button`'s block, Hence, the scope that should be re-composed is Button's composable lambda's block and the outer scope shouldn't be restarted. if you swapped
foo: State<Foo>
with
getFoo: () -> Foo
you will reduce the invalidated scope, Hence, only the Button's lambda block will get re-composed as intended
this is not the case with the
data class
as the final value which the entire class instance would cause
MyFunc
to recompose the same as
foo: State<Foo>
n
That makes sense now, but regarding question 1, where if extensibility of a hoisted type is not a priority is data class preferred over interface?
a
() -> Foo
and
State<Foo>
are actually the same in terms of recomposition. The value of a
State<Foo>
changes doesn’t mean the
State<Foo>
itself changes.
() -> Foo
is preferred because it’s more flexible, e.g. you can pass an expression or an observable property from an object.
I think you are misunderstanding “hoisted state objects”. “hoisted state objects” means internal states of a composable that can be hoisted to its parent. In your code,
BarState
is a model class instead of a state class (or at least you should call it external state).
n
True that i am little confused with State Hoisting here, as per https://developer.android.com/jetpack/compose/state#state-hoisting, its about moving the internal states of composable to its caller. On the other side, as per Compose API Guidelines, https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-api-guidelines.md#hoisted-state-types
hoisted state types
are declared to collect and group interrelated stateless params. What i am trying to do is to reduce the number of parameter to composable. If i can group interrelated parameters, Compose API guidelines suggests to provide an
interface
and default implementation. This is the pattern i see even in
Button.kt
via
androidx.compose.material.ButtonColors
. Is this pattern recommended because it provide extensibility? ( as explained here https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-api-guidelines.md#extensibility-of-hoisted-state-types) If Extensibility is not a priority, then can we make use of
Data
Classes ?
a
No, “Hoisted state types” are not used to group interrelated stateless params
Copy code
interface VerticalScrollerState {
    var scrollPosition: Int
    var scrollRange: Int
}
Here,
scrollPosition
and
scrollRange
are both internal states of the
VerticalScroller
. They are specific to
VerticalScroller
(you won’t need them if you don’t use a
VerticalScroller
) and they are (normally) only consumed and mutated in
VerticalScroller
.
“Hoisted state types” and model classes are different things and in most cases extensibility doesn’t make sense for model classes.
n
as per code snipper for
before
Copy code
@Composable
fun VerticalScroller(
    scrollPosition: Int,
    scrollRange: Int,
    onScrollPositionChange: (Int) -> Unit,
    onScrollRangeChange: (Int) -> Unit
) {
it seems they are passed as parameter, but this is what is recommended via
State Hoisting
. I see your point now, Assuming VerticalScroller being stateful earlier, via
State hoisting
pattern it became
Copy code
@Composable
fun VerticalScroller(
    scrollPosition: Int,
    scrollRange: Int,
    onScrollPositionChange: (Int) -> Unit,
    onScrollRangeChange: (Int) -> Unit
) {
now an in improvement over this,
Copy code
@Stable
interface VerticalScrollerState {
    var scrollPosition: Int
    var scrollRange: Int
}

@Composable
fun VerticalScroller(
    verticalScrollerState: VerticalScrollerState
) {
verticalScrollerState
comes into picture. Now i see “Hoisted state types” and model classes are different things.
As far as stateless params are concerned , to group them is model classes/`data` classes sufficient? why do we see Interface patterns here as well ( taking reference from
Buttons.kt
and
ButtonColors
and
ButtonDefaults
)
a
What do you mean by “stateless params”? Stateful/stateless are used to describe a composable, not a parameter.
n
I just meant input parameters to Composable. Consider a case where
Copy code
@Composable
fun SomeComposable(
    width: Dp,
    radius: Float
) {
}
Can be converted into
Copy code
data class CornerModel(
    val width: Dp,
    val radius: Float)

@Composable
fun SomeComposableWithModelClass(
    model: CornerModel
) {
}
??
Also when should we consider interface type as parameter to composable like seen with
androidx.compose.material.ButtonColors
type in
Button
Composable?
a
When you want to provide the flexibility e.g. for your user to create their own implementation.
🙏 1
n
@Albert Chang Thanks for the inputs. Have one more observations, All through the
androidx.compose.material
Colors and elevations interfaces are expressed as states ex :
Copy code
@Composable
fun backgroundColor(enabled: Boolean): State<Color>
selecting the right background color based on
enabled
is possible even with return value
Color
as well, something similar to below.
Copy code
override fun backgroundColor(enabled: Boolean): Color {
    return if (enabled) backgroundColor else disabledBackgroundColor
}
But its expressed as
State<Color>
Copy code
@Composable
override fun backgroundColor(enabled: Boolean): State<Color> {
    return rememberUpdatedState(if (enabled) backgroundColor else disabledBackgroundColor)
}
What could be the thought process behind this?