Um ViewModel com estado

Mar 13 2023
Hoje, quero compartilhar com você uma variante útil da classe ViewModel que escrevi e que venho usando há algum tempo. Para gerenciar e expor o estado de um ViewModel, pode-se usar a popular API do Kotlins, StateFlow.
Foto de Kelly Sikkema no Unsplash

Hoje, quero compartilhar com você uma variante útil da classe ViewModel que escrevi e que venho usando há algum tempo.

Para gerenciar e expor o estado de um ViewModel , pode-se usar a popular API do Kotlins, StateFlow .

Vamos representar nosso estado de IU com a classe de dados abaixo:

data class UiState(
    val isLoading: Boolean = false,
    val hasError: Boolean = false,
    val data: ScreenData? = null,
)

Para expor o estado acima à interface do usuário, usamos uma classe que estende o ViewModel:

class ScreenViewModel() : ViewModel() {

    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    suspend fun fetchData() {
        _uiState.update { state ->
            state.copy(
                isLoading = true,
                hasError = false
            )
        }

        val fetchedData = fetchDataFromNetwork()
        val failed = fetchedData is Error
        val data = fetchedData as? Data

        _uiState.update { state ->
            state.copy(
                isLoading = false,
                hasError = failed,
                data = data,
            )
        }
    }

}

Vamos supor ainda que precisamos de outra função que atualize os dados, então escreveríamos algo como:

suspend fun updateData(newData: Data) {
    _uiState.update { state ->
        state.copy(
            isLoading = true,
            hasError = false
        )
    }

    val update = updateAndGetData(newData)
    val failed = update is Error
    val data = update as? Data

    _uiState.update { state ->
        state.copy(
            isLoading = false,
            hasError = failed,
            data = data,
        )
    }
}

Como muitos de vocês provavelmente já fizeram, usei esse padrão muitas vezes. Então decidi escrever um ViewModel genérico estendido, chamei-o de StatefulViewModel:

open class StatefulViewModel<T>(initState: T): ViewModel() {

    private val _uiState = MutableStateFlow(initState)
    val uiState = _uiState.asStateFlow()

    protected fun updateState(f: T.() -> T) {
        _uiState.update(f)
    }

    protected fun updateState(newState: T) {
        _uiState.value = newState
    }

    protected val state: T
        get() = _uiState.value
}

Usando esta nova classe, nosso ScreenViewModel escrito acima pode ser refatorado como abaixo:

class ScreenViewModel : StatefulViewModel<UiState>(UiState()) {

    suspend fun fetchData() {
        updateState {
            copy(isLoading = true, hasError = false)
        }

        val fetchedData = fetchDataFromNetwork()
        val failed = fetchedData is Error
        val data = fetchedData as? Data

        updateState {
            copy(isLoading = false, hasError = failed, data = data)
        }
    }

    suspend fun updateData(newData: Data) {
        updateState {
            copy(isLoading = true, hasError = false)
        }

        val update = updateAndGetData()
        val failed = update is Error
        val data = update as? Data

        updateState {
            copy(isLoading = false, hasError = failed, data = data)
        }
    }
    
}

Espero que você ache a classe StatefulViewModel útil e a use em seu próximo projeto!