Power Up Your UseCases With States and Coroutines in Android

I’ve been working with clean architecture approach and using UseCases as interactors for a while now, but most of the time I end up calling corresponding data from required source in it, and return result. If you search and check examples, this is also the case you’ll see in general. Since I’m feeling they are not contributing to logic the way I wanted, I decided to make them more useful.

Let me introduce you what I need from my use case:

  • Load person from persistence first
  • Load person from network after persistence loading ends
  • Update persistence with latest person data
  • Load person’s “item” from persistence and network asynchronously
  • Cancel loading from persistence if loading from network completes first

So, from here, I can conclude:

  • I need cancelable asynchronous work
  • I need some sort of observable for emitting multiple entity types “Person”, “Item”

On top of these, I want to emit state ( Loading / Loaded, or any introduced state like LoadingFromPersistence etc. if needed ) from my UseCase too. So I need another observable ( or do I ? ) for emitting state of loading / progress.

Entities

As you notice ( or to get your attention, if you didn’t ) my entities are extended from an “Entity” class, which is an empty abstract class. Don’t hang on it too much for now, but keep it in mind. It’ll make sense when constructing use case.

Improving BaseUseCase

First of all, let’s encapsulate any result from network / persistence with a Result class:

Since I’ll use Result in my BaseUseCase to emit any results, I also added State as subclass ( like Redux actions ). This allows me to emit a Success, Failure or State seamlessly.

Now onto BaseUseCase:

Relationship between Result, BaseUseCase and Entity is pretty strict. My BaseUseCase is expecting parameters as a type, but not the type of result it’ll be processing.

Let’s go over what’s there and why it’s there:

  • It will not return the result, it’ll emit any result / state. This is why we have a Channel. Since this channel can be used as both sender and receiver, I encapsulated it for inherited usage only, exposing ReceiverChannel as a different variable.
  • It has its own CoroutineContext, Main dispatcher with a SupervisorJob. This will make sure any exception thrown by children will not cancel whole coroutine. To execute tasks in background, it switches to background context. You can read more about coroutine patterns & anti-patterns.
  • It has a method which starts an async and gives back its Deferred, through it I can cancel / await.

Before proceeding, let me explain why I didn’t use LiveData<T> and proceeded with channels. Since we’re on a different thread, we need to use livedata.postValue() to post our state, loaded data etc., but postData is not reliable. Why ? Take a look at it’s javadoc:

/**
* Posts a task to a main thread to set the given value. So if you have a following code executed in the main thread:
* liveData.postValue("a");
* liveData.setValue("b");
* The value "b" would be set at first and later the main thread would override it with the value "a".
* If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
*
* @param value The new value
*/

So you may encounter problems like data you sent is swallowed by new one before it’s processed. I’m currently trying to create a LiveData implementation that solves this issue.

Do you remember above rules ? Send state, load persistence, stop network etc. ? Let me introduce you how easy it becomes to apply above business rules in an inherited use case now:

Adding a BaseViewModel for UseCases

So how does view model for MainActivity looks like ? Take a look:

Conclusion

You can find example repo in Github.

Thanks for reading and please share your thoughts and ideas !

Android Engineer @IKEA

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store