ViewStateStore

Additional

Language
Kotlin
Version
1.4-beta-3 (Jan 16, 2020)
Created
Feb 20, 2019
Updated
Mar 7, 2020 (Retired)
Owner
Davide Giuseppe Farella (fardavide)
Contributor
Davide Giuseppe Farella (fardavide)
1
Activity
Badge
Generate
Download
Source code
This library is created on an idea of Fabio Collini ( https://proandroiddev.com/unidirectional-data-flow-using-coroutines-f5a792bf34e5 ).

ViewStateStore

ViewStateStore wraps a LiveData for deliver ViewStates to the UI.

Supported ViewState types are;

  • Success holds the real data
  • Error holds and error ( which could be a custom class ) with its Throwable and an optiona customMessageRes.
  • Loading
  • None ( default initial value )

Installation

ViewStateStore

implementation( "studio.forface.viewstatestore:viewstatestore:last_version" )

Paging extension

implementation( "studio.forface.viewstatestore:viewstatestore-paging:last_version" )

Minimal usage

Create

class CarsViewModel(val getCars: GetCars): ViewModel() {
    val cars = ViewStateStore<List<Car>>()

    init {
        cars.setLoading()
        viewModelScope.launch {
            runCatching { withContext(IO) { getCars() } }
                .onSuccess { cars::set(it) }
                .onFailure { cars.setError(it) }
        }
    }
}

Get

observe ( with LifecycleOwner )

class CarsFragment: Fragment(), ViewStateFragment {
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        // carsViewModel.cars.observeData { cars -> ... }
        carsViewModel.cars.observe {
            doOnData(::updateCars)
            doOnError(::showError)
            doOnLoading { isLoading -> progressBar.isVisible = isLoading }
        }
    }
}

Iterator ( with CoroutineScope )

class CarsFragment: Fragment(), ViewStateFragment {
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        lifecycleScope.launch {
            // for (viewState in carsViewModel.cars) { ... }
            // for (cars in carsViewModel.cars.data) { ... }
            for ((onData, onError, onLoadingChange) in carsViewModel.cars.composed) {
                onData?let(::updateCars)
                onError?.let(::showError)
                onLoadingChange?let { isLoading -> progressBar.isVisible = isLoading }
            }
        }
    }
}

await ( with CoroutineScope )

class CarsFragment: Fragment(), ViewStateFragment {
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        lifecycleScope.launch {
            val currentOrNextViewState = carsViewModel.cars.await()
            val currentOrNextData = carsViewModel.cars.awaitData()
            val onlyNextViewState = carsViewModel.cars.awaitNext()
            val onlyNextData = carsViewModel.cars.awaitNextData()
        }
    }
}

get ( nullable )

class CarsFragment: Fragment(), ViewStateFragment {
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        val nullableCurrentViewState = carsViewModel.cars.state()
        val nullableCurrentData = carsViewModel.cars.data()
        try {
         val currentViewState = carsViewModel.cars.unsafeState()
         val currentData = carsViewModel.cars.unsafeData()
        } catch(e: KotlinNullPointerException) {
            ...
        }
    }
}

You can set also an ErrorResolution

// CarsViewModel
    init {
        loadCars()
    }

    private fun loadCars() {
        cars.setLoading()
        viewModelScope.launch {
            runCatching { withContext(IO) { getCars() } }
                .onSuccess(cars::setData)
                .onFailure { cars.setError(it, ::loadCars) }
        }
    }
}

// CarsFragment
    override fun onActivityCreated( savedInstanceState: Bundle? ) {
        carsViewModel.cars.observe {
            ...
            doOnError(::showError)
        }
    }

    fun showError(error: ViewState.Error) {
        Snackbar.make(
            coordinatorLayout,
            error.getMessage(requireContext()),
            Snackbar.LENGTH_SHORT
        ).apply {
            if (error.hasResolution())
                setAction("Retry") { error.resolve() } }
            show()
        }
    }
}

It's also possible to lock the ViewStateStore for make it be mutable only from a ViewStateStoreScope

class CarsViewModel( val getCars: GetCars ): ViewModel(), ViewStateStoreScope {
    val cars = ViewStateStore<List<Car>>().lock // Locking the ViewStateStore
}

class CarsFragment: Fragment(), ViewStateFragment {
    override fun onActivityCreated( savedInstanceState: Bundle? ) {
        carsViewModel.cars.postLoading() // Does NOT compile, LockedViewStateStore.postLoading not resolved
    }
}

From external source

A ViewStateStore can also be created from a LiveData or a DataSource.Factory ( see paging artifact for last one ).

val carsLiveData: LiveData<Car> = roomDatabase.getCars()
val cars = ViewStateStore.from( carsLiveData )
val carsDataSource: DataSource.Factory<Int, Car> = roomDatabase.getCars()
val cars = ViewStateStore.from( carsDataSource )

Wiki

Full Wiki here

ViewStateStore Doc here

Paging Doc here