Compose : State Part.3

Skele·2024년 6월 3일
0

Android

목록 보기
10/15

StateHolder

Creating a stateHolder seperates states from the composable function and simplifies the code.

class EditableUserInputState(private val hint: String, initialText: String) {
    var text by mutableStateOf(initialText)
        private set

    fun updateText(newText: String) {
        text = newText
    }

    val isHint: Boolean
        get() = text == hint

    companion object{
        val Saver : Saver<EditableUserInputState, *> = listSaver(
            save = { listOf(it.hint, it.text) },
            restore = {
                EditableUserInputState(
                    hint = it[0],
                    initialText = it[1]
                )
            }
        )
    }
}
@Composable
fun rememberEditableUserInputState(hint: String): EditableUserInputState =
    rememberSaveable(hint, saver = EditableUserInputState.Saver) {
        EditableUserInputState(hint, hint)
    }

Since state holder is a plain class, it cannot be saved with rememberSaveable which takes only types that can be put inside a bundle. To save a state holder, implement saver to store the state, such as listSaver or mapSaver.

Put only necessary data, like ids or keys, in the saver and avoid putting large data.

Using StateHolder

@Composable
fun CraneEditableUserInput(
    state: EditableUserInputState = rememberEditableUserInputState(hint = ""),
    caption: String? = null,
    @DrawableRes vectorImageId: Int? = null
) {
    CraneBaseUserInput(
        caption = caption,
        tintIcon = { !state.isHint },
        showCaption = { !state.isHint },
        vectorImageId = vectorImageId
    ) {
        BasicTextField(
            value = state.text,
            onValueChange = {
                state.updateText(it)
            },
            textStyle = if (state.isHint) {
                captionTextStyle.copy(color = LocalContentColor.current)
            } else {
                MaterialTheme.typography.body1.copy(color = LocalContentColor.current)
            },
            cursorBrush = SolidColor(LocalContentColor.current)
        )
    }
}

Composable function takes state and pass event through it.

@Composable
fun ToDestinationUserInput(onToDestinationChanged: (String) -> Unit) {
    val editableUserInputState = rememberEditableUserInputState(hint = "Choose Destination")
    CraneEditableUserInput(
        state = editableUserInputState,
        caption = "To",
        vectorImageId = R.drawable.ic_plane
    )

    val currentOnDestinationChanged by rememberUpdatedState(onToDestinationChanged)
    LaunchedEffect(editableUserInputState) {
        snapshotFlow { editableUserInputState.text }
            .filter { !editableUserInputState.isHint }
            .collect {
                currentOnDestinationChanged(editableUserInputState.text)
            }
    }
}

State is passed as a parameter. Event handling is done by collecting flow from the state.

produceState

A coroutine which returns state. It launches on compositon and cancels when leaving the composition. Value produced by produceState will not trigger recomposition when the computed value does not change.

val uiState by produceState(initialValue = DetailsUiState(isLoading = true)) {
    // In a coroutine, this can call suspend functions or move the computation to different Dispatchers
    val cityDetailsResult = viewModel.cityDetails
    value = if (cityDetailsResult is Result.Success<ExploreModel>) {
        DetailsUiState(cityDetailsResult.data)
    } else {
        DetailsUiState(throwError = true)
    }
}

when {
    uiState.cityDetails != null -> {
        DetailsContent(uiState.cityDetails!!, modifier.fillMaxSize())
    }
    uiState.isLoading -> {
        Box(modifier.fillMaxSize()) {
            CircularProgressIndicator(
                color = MaterialTheme.colors.onSurface,
                modifier = Modifier.align(Alignment.Center)
            )
        }
    }
    else -> { onErrorLoading() }
}
profile
Tireless And Restless Debugging In Source : TARDIS

0개의 댓글