Naga
04/21/2022, 5:40 PMJetpack 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?
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
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.MR3Y
04/21/2022, 8:43 PMgetFoo: () -> Foo
is preferred over foo: State<Foo>
in function parameters for smart re-composition purposes, so, in this example:
@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 intendeddata class
as the final value which the entire class instance would cause MyFunc
to recompose the same as foo: State<Foo>
Naga
04/21/2022, 11:02 PMAlbert Chang
04/22/2022, 1:16 AM() -> 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.BarState
is a model class instead of a state class (or at least you should call it external state).Naga
04/22/2022, 3:23 AMhoisted 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 ?Albert Chang
04/22/2022, 3:30 AMinterface 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
.Naga
04/22/2022, 3:37 AMbefore
@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
@Composable
fun VerticalScroller(
scrollPosition: Int,
scrollRange: Int,
onScrollPositionChange: (Int) -> Unit,
onScrollRangeChange: (Int) -> Unit
) {
now an in improvement over this,
@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.Buttons.kt
and ButtonColors
and ButtonDefaults
)Albert Chang
04/22/2022, 3:53 AMNaga
04/22/2022, 4:18 AM@Composable
fun SomeComposable(
width: Dp,
radius: Float
) {
}
Can be converted into
data class CornerModel(
val width: Dp,
val radius: Float)
@Composable
fun SomeComposableWithModelClass(
model: CornerModel
) {
}
??androidx.compose.material.ButtonColors
type in Button
Composable?Albert Chang
04/22/2022, 7:00 AMNaga
04/23/2022, 3:56 AMandroidx.compose.material
Colors and elevations interfaces are expressed as states
ex :
@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.
override fun backgroundColor(enabled: Boolean): Color {
return if (enabled) backgroundColor else disabledBackgroundColor
}
But its expressed as State<Color>
@Composable
override fun backgroundColor(enabled: Boolean): State<Color> {
return rememberUpdatedState(if (enabled) backgroundColor else disabledBackgroundColor)
}
What could be the thought process behind this?