Android MVI architecture with Jetpack & Coroutines/Flow — Part 3

Pavlos-Petros Tournaris
Google Developer Experts
3 min readMay 2, 2020

Creating Coroutines/Flow empowered UseCases

If you haven’t read the previous articles of these series, you can find them here:

Part 1 — RecyclerView adapter with ViewBinding

Part 2 — Creating ViewModels in an MVI architecture

In this article we will see how to create different types of UseCases that will be used in our ViewModels.

What is a UseCase?

A UseCase is a list of actions or event steps typically defining the interactions between a role (known in the Unified Modeling Language (UML) as an actor) and a system to achieve a goal. The actor can be a human or other external system. In systems engineering, use cases are used at a higher level than within software engineering, often representing missions or stakeholder goals. The detailed requirements may then be captured in the Systems Modeling Language (SysML) or as contractual statements.

This is the definition provided by Wikipedia here.

But what is actually a UseCase in our project?

In many applications there are parts of our business logic that involve the coordination of several local or remote sources in order to achieve a goal.

For example, whenever we make an API call to fetch a Github repository’s information, we also want to store this information or update it in case we already have this Github repository’s information stored. This is a business flow that can be triggered from several places in our application.

Due to that need we need to have it written only once and test that component in isolation :)

Entering UseCases

Our UseCase examples are heavily influenced from Chris Banes’ TIVI repo.

FlowUseCase

We want to extend FlowUseCase whenever we have observable/streamable results (e.g.: database changes), in the form of Flow<T> .

Our abstract class here, is implementing the ObservableUseCase<T> which defines a property and a method.

  • dispatcher is the Kotlin Coroutines Dispatcher that we will use in order to execute our UseCase’s work. Usually this can be Dispatchers.IO for network or database operations, or Dispatchers.Default if we want to execute CPU intensive tasks.
  • observe is the method that will return a Flow<T> containing the results of our UseCase’s actions.

FlowUseCase<Params: Any, Type : Any> abstract class includes a channel that accepts the Params type. Params can be any type of data that our UseCase needs in order to execute its actions and produce the result.

As we can also see here, the invoke operator is being overriden in order to send the Params input to our internal channel.

The channel then is converted to a Flow and is flatMap ‘d in order to execute the method that produces a Flow<T> result, which is doWork(Params) .

NoResultUseCase

We want to extend NoResultUseCase whenever we have an action that we want to execute without caring for the actual success or failure of the task since it may not be critical to our business logic, or will not impact our user.

In this UseCase all we need to do, is provide the Dispatcher that best fits our needs, based on our previous explanation. It will then switch Coroutines context to that Dispatcher and execute any action given in run method.

ResultUseCase

We want to extend ResultUseCase whenever we have an action that we want to execute, but we actually care of its result. This can be in cases our product needs to also do error handling or inform the user that the action was not successfully completed.

As previously we need to indicate the Dispatcher that we want our UseCase to utilize. Here our run method though has a return type. In our case this type is Result , which can take 2 different forms. It can be either a Success or a Failure . Usually we can create that very simple Result class with a sealed class, but generally this is based on the project’s needs.

Conclusion

The above base UseCase classes are usually enough to satisfy our needs on a project, but like I always say “it depends”. In a later article we will also explore how easy it is to test our UseCases in isolation.

You can find all of the code above and examples of their usage to the following repo, which will be the one we will cover in this series of articles!

--

--