[Outdated] Functional Use Cases in Android

Denis Brandi
The Startup
Published in
4 min readMay 29, 2020

--

This article is now outdated, please find latest approach using Functional Interfaces here.

What if I tell you that you can remove all the boilerplate code of your use cases and avoid writing them when not needed without affecting your presentation layer?

No, I’m not joking! 😎

A while ago I wrote why you need use cases and what they are so in this article I’m not going to dig too much into details, if you are new to them I suggest you read my previous article before.

To summarize we can say that Use cases are an evolution of Application Services that bring the following benefits:

  1. Isolation of application logic (this was given by Application Services too)
  2. Adhesion to Interface Segregation Principle: for example, your ViewModel will have as collaborators only the needed Use cases and not a huge interface (either application service or repository if you are skipping the layer) with 10+ methods where only 1 or 2 are needed.
  3. Thinner Presenters/ViewModels
  4. Screaming Architecture: you know what the app does by looking at the use case names.

The evolution of Use cases in Kotlin

When developers started migrating their Java code to Kotlin they kept the original Java convention so Use cases were looking something like:

and used as

So Use Case interface name reflecting the action to be performed and method name adhering to the Command Pattern convention.

After a while, someone came up with a more readable solution that looked like the following:

Basically the operator fun invoke allows you to omit the invoke method when calling it, this way it looks like you are calling a function (yes you are getting the hint 🙂).

The hate for Use cases

Despite all the benefits they bring the excessive boilerplate of the Use cases (and the application layer in general) is something to consider and for many is a deal-breaker.

I cannot really blame who is against using them because most of the time these Use cases have really small logic (if any).

To quote Martin Fowler:

My preference is thus to have the thinnest Service Layer you can, if you even need one. My usual approach is to assume that I don’t need one and only add it if it seems that the application needs it. However, I know many good designers who always use a Service Layer with a fair bit of logic, so feel free to ignore me on this one.

As you can see better engineers than me and you have already discussed this topic in the past and never came to an agreement 😢.

Functional Programming to the rescue

Let’s say you have these Repositories:

Repositories

A Use case that just calls a Repository (“useless” use case):

OOP “Useless” Use case

A Use case with a little bit of logic (multiple resources):

OOP Use case with logic

And finally a ViewModel:

The ViewModel

You can see quite a bit of boilerplate code: Use case interfaces (because we adhere to the DIP, aren’t we? 😈), Use case classes (even when they literally do nothing)...

With the following approach, I’ll get rid of the Use case interfaces and the “useless” Use case (GetUserUseCaseImpl ) without affecting the presentation layer (TransactionListViewModelImpl ).

FP conversion

The conversion of GetTransactionsUseCaseImpl to higher-order function would be:

FP Use case

The typealias GetTransactionsUseCase will be used for removing the verbosity of the function return type and for minimizing the refactoring (the TransactionListViewModelImpl thanks to this trick will not change a single line).
GetUserUseCase is another typealias.
P.S.: You don’t need to use type aliases, this approach works with or without them.

You should have noticed that there are no input parameters in the typealiases, this is because we don’t want to make the dependencies explicit (just like interfaces hide the dependencies of their concrete implementations).
In fact, the injector will assign the functions to the typealiases in this way:

We moved from class composition to functions composition!

Did you notice anything else? 🙂

userRepository::getUserreplaces GetUserUseCaseImpl which has not been implemented at all!

If you are using Dagger you can achieve the same by doing:

Dagger module

The ViewModel did not change at all:

The typealias minimized the effort of the refactoring (a change of an import is all you need 🙂).

Quick recap

  1. “Useless” Use cases are not implemented and the injector assigns the Repository function to the ViewModel (without knowing about the Repository, this way the ISP is not broken).
  2. “Useful” Use cases are implemented as higher-order functions and the injector hides the function inputs.
  3. Optional: Type aliases are used for removing the verbosity of function definitions and for minimizing the changes required by eventual refactoring.

Last notes

I find that this approach saves a lot of time, reduces the maintenance cost of the project, and helps in keeping the architecture of the codebase consistent.

You can find the full source code here:

--

--