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.
I’ll be loading a “PersonEntity” and an “ItemEntity”, these are the entities I’ll be using in this example:
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.
This is the abstract class that all our use cases will be extending from. I really like the approach Fernando Cejas uses in Clean Architecure Kotlin, and I strongly suggest you to take a look at it. I used it as a base, but since I have more functionality in mind ( like multiple asynchronous calls and delivering any entity ), I added & changed some stuff on top of it.
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:
* 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
This will be the base of our view models that has a use case. What it’ll do is just starts to consume & redirect results / states sent by channel to resolve method, since we wouldn’t like to do this in every single view model.
So how does view model for MainActivity looks like ? Take a look:
While it may add complexity, like coroutine launching data type checking, I think using UseCases this way can introduce cleaner view models which focus on processing data rather than processing data state / status to continue with more loading, also separate tests for data rules.
You can find example repo in Github.
Thanks for reading and please share your thoughts and ideas !