Skip to content

Instantly share code, notes, and snippets.

@quentin41500
Last active June 8, 2021 12:28
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save quentin41500/f93978d97f5008cde387db19ce5e2ef6 to your computer and use it in GitHub Desktop.
Save quentin41500/f93978d97f5008cde387db19ce5e2ef6 to your computer and use it in GitHub Desktop.
Using Sealed class and LiveData to handle network request through the repository layer.
/**
* Observable manager for saving the [Cart]'s resource information.
*/
class CartManager : LiveData<Resource<Cart?>>() {
init {
value = Success(null)
}
/**
* Set the [Cart] value and notifies observers.
*/
internal fun set(cart: Cart) {
postValue(Success(cart))
}
/**
* Clear any information from the device.
*/
internal fun clear() {
postValue(Success(null))
}
/**
* Signals that the resource information is being retrieved from network.
*/
internal fun loading() {
postValue(Loading())
}
/**
* Signals that an error occurred when trying to fetch the resource information.
*/
internal fun error(t: Throwable) {
postValue(Failure(t))
}
}
/**
* [Cart] repository class in charge of providing api for getting and updating a [Cart] data.
*/
class CartRepository(private val service: RetrofitApi, private val cartManager: CartManager) {
fun getCartResource(): LiveData<Resource<Cart?>> = cartManager
/**
* Get a [Cart].
*/
suspend fun get() {
withContext(Dispatchers.IO) {
try {
cartManager.loading()
val cart = service.getCart().await()
cartManager.set(cart)
} catch (e: Exception) {
cartManager.error(e)
}
}
}
/**
* Add a [Cart] shipping information.
*/
suspend fun addShippingAddress(shipping: ShippingAddress) {
withContext(Dispatchers.IO) {
try {
cartManager.loading()
val cart = service.addShippingAddress(shipping).await()
cartManager.set(cart)
} catch (e: Exception) {
cartManager.error(e)
}
}
}
}
class CheckoutActivity : AppCompatActivity() {
private lateinit var vm: CheckoutViewModel
override fun onCreate(savedInstanceState: Bundle?) {
// Initialization
...
vm.cart.observe(this, Observer { resource ->
when (resource) {
is Loading -> showLoading()
is Success -> {
displayCart(resource.data) // Do something with your data.
hideLoading()
}
is Failure -> {
showError(resource.throwable) // Do something with your error.
hideLoading()
}
})
}
}
class CheckoutViewModel(repo: CartRepository) : ViewModel() {
/**
* Live data for [Cart] information.
*/
val cart: LiveData<Resource<Cart?>> = repo.getCartResource()
}
/**
* Represents a network bound resource. Each subclass represents the resource's state:
* - [Loading]: the resource is being retrieved from network.
* - [Success]: the resource has been retrieved (available in [Success.data] field)
* - [Failure]: the resource retrieving has failed (throwable available in [Failure.throwable] field)
*/
sealed class Resource<out T> {
class Loading<out T> : Resource<T>()
data class Success<out T>(val data: T) : Resource<T>()
data class Failure<out T>(val throwable: Throwable) : Resource<T>()
}
@iRYO400
Copy link

iRYO400 commented Jun 4, 2019

Hello! I've read your article. This implementation is exactrly what I looked for. But due to my experience, I dont understand what resource is in CartManager:

...
resource = Success(null)
...
resource = Loading()
...
resource = Failure(t)
...

Thanks!

@quentin41500
Copy link
Author

I originally was using a variable but realized later I didn't need it. You can just pass the created objects to live data right away.
postValue(Success(null))

I will make the edit.

@aalap03
Copy link

aalap03 commented Feb 17, 2020

What if we use a boolean in loading so we have to just pass boolean on completion once, and not twice at both error and success case ?

@whoyawn
Copy link

whoyawn commented Sep 1, 2020

What if you're integrating a database layer? Would we still be observing the resource in the UI if you want to fetched cached objects?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment