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

Lukasz Kalnik

11/25/2021, 3:39 PM
What is the recommended way to attach a Composable to a StateFlow from a ViewModel? I have a ViewModel which exposes an alert dialog state:
Copy code
data class NameDialogState(val show: Boolean, val nameFieldValue: TextFieldValue)
val nameDialogState: StateFlow<NameDialogState>
The composable has a parameter for its UI state:
Copy code
@Composable
fun NameDialog(uiState: State<NameDialogState>) {
    if (uiState.value.show) {
        AlertDialog(
            // ...
            text = TextField(value = uiState.value.nameFieldValue)
        )
        // ...
    }
}
Inside the Fragment, the composable for the dialog takes the flow with
collectAsState()
Copy code
val uiState = viewModel.nameDialogState.collectAsState()

setContent {
    NameDialog(uiState = uiState)
}
This works, but I wonder if it is possible to get rid of the
State<*>
in the composable and use
NameDialogState
directly, like this:
Copy code
@Composable
fun NameDialog(uiState: NameDialogState)
All Google examples recommend injecting viewmodel directly into composable, which doesn't seem good architecture... I would like to keep the composables as "stupid" as possible.
a

Adam Powell

11/25/2021, 3:56 PM
Short answer, yes, this is possible. Longer: We specifically avoid using
[Mutable]State<T>
as parameters in API as it complicates usage for little to no benefit over the
T
itself, or
() -> T
if the goal is to defer reads to a narrower scope. Take many of the compose+viewmodel examples with a grain of salt, many of them are written for the audience of android developers who are familiar and comfortable with fragment+viewmodel+view patterns from android architecture components, and it's not always explicitly stated where an example's design decision is load-bearing in compose-centric surroundings or retained primarily to preserve that familiarity and bridge understanding. Asking, "do I really need this?" is always a good question. 🙂
l

Lukasz Kalnik

11/25/2021, 4:00 PM
Maybe I didn't make myself clear: I actually want to get rid of the
State<T>
in my composable and use
T
instead (which I confusingly named
NameDialogState
, sorry for that).
a

Adam Powell

11/25/2021, 4:01 PM
I suppose I didn't either; I agree with your design decision to get rid of
State<T>
and use
T
instead. 🙂
l

Lukasz Kalnik

11/25/2021, 4:01 PM
But I already see that I can achieve it using in my Fragment
Copy code
val uiState by viewModel.nameDialogState.collectAsState()
Then it's already unwrapped from the
State<T>
into
T
And thanks for the super fast answer nevertheless!
👍 1
a

Adam Powell

11/25/2021, 4:05 PM
I'm only a few sips into my first coffee of the day but I am fairly certain we don't have a
Flow<T>.collectAsState()
that is not
@Composable
, so using that from the fragment won't work
l

Lukasz Kalnik

11/25/2021, 4:06 PM
I call it inside
setContent {}
a

Adam Powell

11/25/2021, 4:06 PM
that'll do then
l

Lukasz Kalnik

11/25/2021, 4:07 PM
Yes, works like a charm!
👍 1
b

Berkeli Alashov

11/25/2021, 6:43 PM
Another approach is to create two composables: one that is "stupid" taking only the uiState and another one collecting the state from ViewModel and presenting it via the first composable. Example: https://github.com/chrisbanes/tivi/blob/main/ui-account/src/main/java/app/tivi/account/AccountUi.kt#L80-L110
1
l

Lukasz Kalnik

11/26/2021, 9:26 AM
Oh, that's a great example, thank you 🙏
7 Views