Stanowy model widoku

Mar 13 2023
Dzisiaj chcę się z Wami podzielić przydatnym wariantem klasy ViewModel, który napisałem i którego używam od jakiegoś czasu. Aby zarządzać i ujawniać stan z ViewModel, można użyć popularnego API Kotlinsa, StateFlow.
Zdjęcie Kelly Sikkema na Unsplash

Dzisiaj chcę się z Wami podzielić przydatnym wariantem klasy ViewModel , który napisałem i którego używam od jakiegoś czasu.

Aby zarządzać i ujawniać stan z ViewModel , można użyć popularnego API Kotlinsa StateFlow .

Przedstawmy nasz stan interfejsu użytkownika za pomocą poniższej klasy danych:

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

Aby udostępnić powyższy stan interfejsowi użytkownika, używamy klasy, która rozszerza 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,
            )
        }
    }

}

Załóżmy dalej, że potrzebujemy innej funkcji, która zaktualizuje dane, wtedy napiszemy coś takiego:

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,
        )
    }
}

Jak zapewne wielu z Was zrobiło, wielokrotnie korzystałam z tego wzoru. Postanowiłem więc napisać rozszerzony ogólny ViewModel, nazwałem go 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
}

Korzystając z tej nowej klasy, nasz powyżej napisany model ScreenView można zrefaktoryzować w następujący sposób:

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)
        }
    }
    
}

Mam nadzieję, że klasa StatefulViewModel okaże się przydatna i wykorzystasz ją w swoim następnym projekcie!