Hello, everyone! :wave: I’m having some difficult...
# compose
e
Hello, everyone! 👋 I’m having some difficult to implement my custom
Saver
to be used with
rememberSaveable
. Could you please help me? I have a list of items that, when clicked, shows a
BottomSheet
with the item data. Implementing my custom
Saver
works only or the first time and the first item saved will show every time the screen is rotate. When in portrait or landscape, if I click in any item, it shows the item correctly. But when rotate, the first one saved is shown again. Code is in the comments. Thanks a lot in advance! ❤️
Copy code
@Stable
internal class CategoryBottomSheetState(val category: Category) {

    private val id by mutableStateOf(category.id)

    var name by mutableStateOf(category.name)

    var color by mutableStateOf(category.color)

    fun isEditing(): Boolean =
        id > 0L

    fun toCategory(): Category =
        Category(
            id = id,
            name = name,
            color = color
        )

    companion object {
        fun Saver(): Saver<CategoryBottomSheetState, *> = Saver(
            save = { state -> state.toCategory() },
            restore = { category -> CategoryBottomSheetState(category) }
        )
    }
}

@Composable
internal fun rememberCategoryBottomSheetState(vararg key: Any?, category: Category) =
    rememberSaveable(key, stateSaver = CategoryBottomSheetState.Saver()) {
        mutableStateOf(CategoryBottomSheetState(category))
    }
Usage:
Copy code
val state by rememberCategoryBottomSheetState(category, category = category)
Changing the state:
Copy code
CategoryNameField(
    name = state.name,
    onNameChange = { state.name = it },
    modifier = Modifier.weight(5F)
)
After more tries, it seems that adding both
inputs
and
key
for the remember did work. However, if I updated some value before rotating, it does not show the updated value.🤔 Is it expected? I have to provide different keys for both the
remember
and the
bundle
?
Copy code
@Composable
internal fun rememberCategoryBottomSheetState(vararg key: Any?, category: Category) =
    rememberSaveable(key, key = category.toString(), stateSaver = CategoryBottomSheetState.Saver()) {
        mutableStateOf(CategoryBottomSheetState(category))
    }
After digging on IssueTracker, maybe this is a bug after all? https://issuetracker.google.com/issues/182403380
a
what now is happening I think is the new empty array is created and passed as a key for rememberSaveable. new object - state is reset.
e
It seems to work now, Andrey. Thanks!
Copy code
@Composable
internal fun rememberCategoryBottomSheetState(vararg key: Any?, category: Category) =
    rememberSaveable(*key, key = category.toString(), stateSaver = CategoryBottomSheetState.Saver()) {
        mutableStateOf(CategoryBottomSheetState(category))
    }
But I had to keep the “bundle key” as well, otherwise the issue happens again. Is it okay?
a
no, it shouldn’t be needed. I saw your usage from the third message and it looks strange. so you use the returned state as
val bottomContent by
which means you never update the state. can you show more code?
e
Sure. I updated the third message. Names were different because they are not in same Composable function.
I tried to simplify the code, but basically it is:
Copy code
@Composable
fun CategoryBottomSheet(category: Category?, onHideBottomSheet: () -> Unit) {
    val bottomSheetState by rememberCategoryBottomSheetState(category, category = category)

    CategorySheetLoader(
        colorList = CategoryColors.values().toList(),
        bottomSheetState = bottomSheetState,
        onHideBottomSheet = onHideBottomSheet
    )
}

@Composable
private fun CategorySheetLoader(
    editViewModel: CategoryEditViewModel = getViewModel(),
    bottomSheetState: CategoryBottomSheetState,
    colorList: List<Color>,
    onHideBottomSheet: () -> Unit,
) {
    CategorySheetContent(
        colorList = colorList,
        state = bottomSheetState,
        onCategoryChange = { updatedState ->
            editViewModel.updateCategory(updatedState.toCategory())
            onHideBottomSheet()
        },
    )
}

@Composable
private fun CategorySheetContent(
    state: CategoryBottomSheetState,
    colorList: List<Color>,
    onCategoryChange: (CategoryBottomSheetState) -> Unit,
) {
    Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.SpaceAround) {
        Row(verticalAlignment = Alignment.CenterVertically) {

            CategoryNameField(
                name = state.name,
                onNameChange = { state.name = it },
                modifier = Modifier.weight(5F)
            )

            CategoryColorSelector(
                colorList = colorList,
                value = Color(state.color),
                onColorChange = { state.color = it.toArgb() }
            )

            CategorySaveButton(
                currentColor = Color(state.color),
                onClick = { onCategoryChange(state) }
            )
        }
    }
}
a
hm, seems like you always see the first category exactly because the Saver works as intended 🙂 CategoryBottomSheetState is not the source of truth for you, the current category is saved somewhere in the ViewModel. and then when you try to show the second category CategoryBottomSheet is reexecuted with the second category, but we restore the initial state. why you used rememberSaveable in first place? to remember the currently typed by the user values?
😅 1
e
I’m trying to keep what the user is editing if the screen is rotated. Otherwise when rotating, it will load the “original content” not the updated one.
a
so if setting the key works for you seems like you want to reset the state when the input category changes. for example try to put
category.id
as a vararg param and remove your own bundle key.
e
Sorry, do you mean something like this?
Copy code
@Composable
internal fun rememberCategoryBottomSheetState(category: Category) =
    rememberSaveable(category.id, stateSaver = CategoryBottomSheetState.Saver) {
        mutableStateOf(CategoryBottomSheetState(category))
    }
If so, it seems very similar to the my first attempt.
a
yes, this seems fine
e
Hello, Andrey. Thanks a lot for you patience. 🙂 Unfortunately the solution above does not work. It happens the original issue: it only shows the first clicked item when rotating. Am I doing something else wrong? I thought that once the remember is called again with different
category.id
, it would “refresh” the stored value.
a
actually yeah, I see what you mean. maybe it is indeed the same issue as in https://issuetracker.google.com/issues/182403380
e
Got it. Again, thanks a lot for your help, Andrey! 😊
a
Copy code
@Composable
internal fun rememberCategoryBottomSheetState(category: Category) =
    key(category.id) {
        rememberSaveable(stateSaver = CategoryBottomSheetState.Saver) {
            mutableStateOf(CategoryBottomSheetState(category))
        }
    }
please try this variant for now
e
It seems that is now working perfectly.
a
cool. I will fix it in the library in one of the following betas then
e
Great! Thanks a lot! ❤️