What's the community's take on stateful Composable...
# compose
r
What's the community's take on stateful Composables like a favorite button? It seems logical that we want to show the user the filled state of Favoriting something right away, and then update our internal ViewModel state once our network request goes through. Thoughts on this? Seems to make sense to me. Code sample and screenshot in the thread
Copy code
@Composable
fun FavoriteButton(
    onClick: (Boolean) -> Unit,
    isFavorited: Boolean,
    modifier: Modifier = Modifier
) {
    val fill = remember { mutableStateOf(isFavorited) }

    IconButton(onClick = {
        fill.value = !fill.value
        onClick.invoke(isFavorited)
    }, modifier = modifier) {
        Icon(
            imageVector = if (fill.value) Icons.Default.Favorite else Icons.Outlined.FavoriteBorder,
            contentDescription = stringResource(
                id = if (fill.value) R.string.remove_favorite_button else R.string.favorite_button
            ),
            tint = Theme.colors.onBackground,
        )
    }
}
As a follow up, how do we feel about having the stateful and the stateless composables like the below? Definitely makes it easier to understand.
Copy code
@Composable
fun FavoriteButton(
    onClick: (Boolean) -> Unit,
    isFavorited: Boolean,
    modifier: Modifier = Modifier
) {
    val fill = remember { mutableStateOf(isFavorited) }

    if (fill.value) {
        FavoritedButton(onClick = { onClick(fill.value) }, modifier = modifier)
    } else {
        NotFavoritedButton(onClick = { onClick(fill.value) }, modifier = modifier)
    }
}

@Composable
fun FavoritedButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
    IconButton(onClick = onClick, modifier = modifier) {
        Icon(
            imageVector = Icons.Default.Favorite,
            contentDescription = stringResource(id = R.string.remove_favorite_button),
            tint = SizzleTheme.colors.onBackground,
        )
    }
}

@Composable
fun NotFavoritedButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
    IconButton(onClick = onClick, modifier = modifier) {
        Icon(
            imageVector = Icons.Outlined.FavoriteBorder,
            contentDescription = stringResource(id = R.string.favorite_button),
            tint = SizzleTheme.colors.onBackground,
        )
    }
}
n
It seems like this way of doing it leaves you no good way to reset the icon state if the network request fails for any reason. If you need to force a reset of the visual state, you'd need to set
isFavorited
to true and then switch it back to false, which is rather clunky and somewhat defeats the purpose (avoiding altering the ViewModel until it's actually worked). If instead you just update the ViewModel state right away but have the ViewModel roll the state back if the network request fails, it would work fine right out of the box. In general, I have a strong preference for using a single source of truth where possible. I've seen far too many UIs that duplicate their state in one way or another, and it always comes back to bite you later by getting out of sync in hard-to-debug ways.
r
You know, i hadn't fully considered the disconnect between the ViewModel's state during the network request and the button's state. You're right lol. I think it's best to make it stateless. My entire architecture is built around state being managed by the ViewModel as the source of truth. Thanks for pointing that out @nschulzke
👍 1
p
Your view model can stay the source of truth. Save whater fav stuff is pending and use it to overwrite the view state fav state
🙏🏻 1
z
I think this could be handled by your repository layer as well, and might even argue that’s the best place to put it, since the whole job of the repository is to reconcile local and network state.
r
@Zach Klippenstein (he/him) [MOD] Ah, you mean have the repository cache the favorite state locally?
z
Yep
r
That makes a whole lot of sense to me. ViewModel caching this would likely create some weird bugs later down the line anyways once we need that favorite state to show up elsewhere in the app. Thanks for the input!
👍🏻 1