2
respostas

Praticas para manter o estado da tela durante rotação ou quando app fica em segundo plano

Eu tenho um stateful chamado LoginUiStateFul, ao qual utilizo para armazenar meus estados, via construtor eu recebo um Contexto e o StateHolder ( um data class utilizado para deter os dados da tela).

internal class LoginUiStateFul(
    private val context: Context,
    private val userState: UserState
) {
    var userEmail = mutableStateOf(context.getString(R.string.empty))
        private set
    var userPassword = mutableStateOf(context.getString(R.string.empty))
        private set
    var passwordVisibility = mutableStateOf(false)
        private set
    
    var typeErro = mutableStateOf(userState.data?.typesErro)
        private set
    
    fun userEmailChange(value: String) {
        userEmail.value = value
    }

    fun userPasswordChange(value: String) {
        userPassword.value = value
    }

    fun passwordVisibilityChange(value: Boolean) {
        passwordVisibility.value = value
    }
    
    fun geTrailingIcon(): String {
        return if (passwordVisibility.value) {
            context.getString(R.string.hide)
        } else {
            context.getString(R.string.show)
        }
    }
}

Se eu cosumir os estados desse stateful, eles não irão se manter durante uma rotação de tela.

Visto isso, criei uma variavel do tipo rememberSavable e atribui minha LoginUiStateFul a ela.

 val context = LocalContext.current
val stateHolder by viewModel.userState.collectAsState()
val stateFul = rememberSaveable { LoginUiStateFul(context, stateHolder) }

Em tempo de compilação ocorre esse crash

java.lang.IllegalArgumentException: com.example.login.activity.login.presentation.stateholder.LoginUiStateFul@92a92fd cannot be saved using the current SaveableStateRegistry. The default implementation only supports types which can be stored inside the Bundle. Please consider implementing a custom Saver for this class and pass it to rememberSaveable().

Esse tipo de dado não é possivel salvar dentro de um bundle a IDE meio que sugere criar um saver custimizado, como deveria proceder nesse cenário?

2 respostas

Oi Thailan, tudo bem?

Você tentou utilizar a função rememberSaveable para salvar o estado do LoginUiStateFul, mas está recebendo um erro de IllegalArgumentException, informando que o LoginUiStateFul não pode ser salvo usando o SaveableStateRegistry padrão, pois ele só suporta tipos que podem ser armazenados dentro de um Bundle.

Nesse caso, a IDE sugere criar um saver personalizado para essa classe. Para resolver esse problema, você pode implementar um Saver personalizado para o LoginUiStateFul. Um Saver é responsável por serializar e desserializar o estado de um objeto para que ele possa ser armazenado e restaurado corretamente.

Aqui está um exemplo de como você pode implementar um Saver personalizado para o LoginUiStateFul:

@Immutable
data class LoginUiStateFulSaveable(
    val userEmail: String,
    val userPassword: String,
    val passwordVisibility: Boolean,
    val typeErro: String?
) : Parcelable {
    constructor(stateFul: LoginUiStateFul) : this(
        userEmail = stateFul.userEmail.value,
        userPassword = stateFul.userPassword.value,
        passwordVisibility = stateFul.passwordVisibility.value,
        typeErro = stateFul.typeErro.value
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(userEmail)
        parcel.writeString(userPassword)
        parcel.writeByte(if (passwordVisibility) 1 else 0)
        parcel.writeString(typeErro)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<LoginUiStateFulSaveable> {
        override fun createFromParcel(parcel: Parcel): LoginUiStateFulSaveable {
            return LoginUiStateFulSaveable(
                userEmail = parcel.readString() ?: "",
                userPassword = parcel.readString() ?: "",
                passwordVisibility = parcel.readByte() != 0.toByte(),
                typeErro = parcel.readString()
            )
        }

        override fun newArray(size: Int): Array<LoginUiStateFulSaveable?> {
            return arrayOfNulls(size)
        }
    }
}

@Composable
fun LoginUiStateFulSaver(stateFul: LoginUiStateFul): LoginUiStateFulSaveable {
    val saveable = rememberSaveable { LoginUiStateFulSaveable(stateFul) }
    val context = LocalContext.current

    DisposableEffect(stateFul) {
        val registry = LocalSavedStateRegistryOwner.current!!.savedStateRegistry
        val key = "LoginUiStateFulSaveable"
        val saver = rememberSaveableStateHolderSaver(saveable)

        registry.registerSavedStateProvider(key) {
            Bundle().apply {
                putParcelable(key, saver.save())
            }
        }

        val savedState = registry.consumeRestoredStateForKey(key)
        if (savedState != null) {
            saver.restore(savedState.getParcelable(key))
        }

        onDispose {
            registry.unregisterSavedStateProvider(key)
        }
    }

    return saveable
}

No exemplo acima, criamos uma nova classe chamada LoginUiStateFulSaveable, que implementa a interface Parcelable para que possa ser armazenada em um Bundle. Em seguida, criamos uma função LoginUiStateFulSaver, que recebe um LoginUiStateFul e retorna um LoginUiStateFulSaveable. Essa função utiliza o rememberSaveable para salvar e restaurar o estado do LoginUiStateFulSaveable.

Para utilizar o Saver personalizado, você pode substituir a linha val stateFul = rememberSaveable { LoginUiStateFul(context, stateHolder) } por val stateFul = LoginUiStateFulSaver(LoginUiStateFul(context, stateHolder)).

Espero que isso ajude a resolver o seu problema!

Um abraço e bons estudos.

Ok, muito obrigado.