Skip to content

Instantly share code, notes, and snippets.

@svasquezm
Last active August 7, 2020 16:09
Show Gist options
  • Save svasquezm/18e50d4eac98358f788b43387efe18cd to your computer and use it in GitHub Desktop.
Save svasquezm/18e50d4eac98358f788b43387efe18cd to your computer and use it in GitHub Desktop.
Flow data with Events Approach
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlin.random.Random
/**
* Events to be emitted on Data level
*/
sealed class Event<T> {
class Loading<T>: Event<T>()
class Success<T>(val data: T): Event<T>()
sealed class Error<T>: Event<T>() {
class NotFound<T>: Error<T>()
class InternalServer<T>: Error<T>()
class Unauthenticated<T>: Error<T>()
}
}
// DATA: Generic repository
/**
* Handles data from server and locally.
* A Mixed approach can be achieved using fetchUsers(false)
*/
class Repository(
private val dummyUserApiService: DummyUserApiService,
private val dummyUserDao: DummyUserDAO
) {
/**
* Retrieves an user locally
*/
suspend fun getUsers() = flow<Event<List<User>>> {
dummyUserDao.getUsers()
.takeIf { it.isNotEmpty() }
?.let { users -> emit(Event.Success(users)) }
?: emit(Event.Error.NotFound())
}
/**
* Retrieves users from API
*/
suspend fun fetchUsers(serverOnly: Boolean = false) = flow<Event<List<User>>> {
val result = dummyUserApiService.fetchUsers()
if(result.statusOk){
insert(result.body)
if(serverOnly) {
if(result.body == null){
emit(Event.Error.NotFound())
} else {
emit(Event.Success(result.body))
}
} else {
getUsers()
}
} else {
emit(Event.Error.Unauthenticated())
}
}
private fun insert(users: List<User>?){
users?.let { dummyUserDao.insert(it) }
}
}
// PRESENTATION: Generic ViewModel
class UserViewModel(private val repo: Repository) : ViewModel(){
val userLiveData = MutableLiveData<Event<List<User>>>()
/**
* From DB approach
*/
fun getUsers(){
viewModelScope.launch {
repo.getUsers().collect {
userLiveData.value = it
}
}
}
/**
* From Network approach (can return Event.Error objects)
*/
fun fetchUsers() {
viewModelScope.launch {
repo.fetchUsers().collect {
userLiveData.value = it
}
}
}
}
// Dummy classes (a retrofit service like)
class DummyUserApiService {
data class ApiServiceResult<N>(val statusOk: Boolean, val body: N? = null)
suspend fun fetchUsers(): ApiServiceResult<List<User>> {
delay(3000)
return if(Random.nextBoolean()){
ApiServiceResult(true,
listOf(
User(1, "Andrecito"),
User(2, "Juancito")
)
)
} else {
ApiServiceResult(false)
}
}
}
class DummyUserDAO {
val usersInFakeDB = mutableListOf(
User(0, "Jorgito")
)
fun getUsers(): List<User> = usersInFakeDB
fun insert(users: List<User>) = usersInFakeDB.addAll(users)
}
data class User(val id: Int, val name: String)
// Cases
// Next lines simulates we are in an Activity / Fragment component
val dummyUserDAO = DummyUserDAO()
val dummyUserApiService = DummyUserApiService()
val repo = Repository(dummyUserApiService, dummyUserDAO)
val viewModel = UserViewModel(repo)
// Owner should be the activity or fragment it self
viewModel.userLiveData.observe(owner, Observer {
when(it) {
is Event.Success -> {
// it.data contains retrieved users
}
is Event.Loading -> {
// Shows a Progressbar or something
}
is Event.Error.Unauthenticated -> {
// Handle 401 Error
}
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment