Android Functional Clean Architecture, Part 2

m0skit0
4 min readDec 29, 2019

--

In the first part we saw how to get rid of interfaces in favor of function injection, which simplifies our code.

In this second part I will continue building on the same functional architecture, highlight some aspects missing on the first part and try to improve it.

Self-modifying code

Self-modifying code is code that can change its behavior at runtime. For example a given function can change its implementation under given circumstances. Using (and abusing) function injection can give us a lot of flexibility, almost to the level of self-modifying code without using any bytecode manipulation libraries (e.g. Javassist).

Laziness

One of the most immediate improvements of the implementation presented in part 1 is the optimization of repeated injections every time a function is called. For example, here:

Every time we call provideRetrofit() it will call Koin to inject the Retrofit instance. This can be solved by caching the value lazily. Unfortunately Kotlin does not support delegates for functions, so we can’t write

But we can use an intermediate lazy property:

Note that you have to be careful when to lazily cache values, since after the first call, retrofit property will always return the same Retrofit instance. In this case this is exactly what we want, but it might not be in some cases (for example unit testing), so use it with caution.

Same thing happens with injected functions, for example here:

Every time we call provideGetColorsDto() it will inject the NAMED_FLAVOR function. This can be solved exactly like the previous situation

Note that function injection laziness limits our ability to change the function injected on subsequent calls since it is now cached. For example, if we use this new implementation with laziness, our previous test DataSourceTest implementation will not work.

Because each tests injects a different implementation for the NAMED_ASSET function, but only the first one will be actually injected since it is cached. This also affects to any potential self-modifying code using function injection.

So in my case I prefer to inject functions on every call, but to make it more readable I prefer to keep the property for the function.

By the way, do you think making these values lazy is premature optimization? Would love to hear your opinion!

Enter Arrow

Arrow is a great functional library for Kotlin that allows us to implement various functional programming patterns. Let’s add it as a dependency:

IO

IO is a data type that allows us, as its name implies, to specify I/O operations explicitly. So we wrap our current I/O operations in it.

Of course this changes our Koin injection definition for NAMED_GET_COLORS_DTO as well:

We’ll see how IO is useful while constructing the rest of the app.

Repository

As a good practice I suggest you always keep your data models separate in each layer. Never propagate your DTOs to your UI layer. This way if ever the DTO changes (which it will), you only need to make adjustments on your mapping at the repository level and it won’t affect any other layers. The same logic applies for each layer and its data model.

So let’s define our repository model for our color data:

Note that we’re now storing an integer instead of its string hexadecimal representation. So we need to map ColorDto to Color:

Now we can define our provider functions for the repository:

You can notice how this is very similar to the data source definition. Note also how IO provides a very welcome map function.

And of course the Koin module for the repository:

Use case

The use case is also pretty straightforward:

Several things to note here:

  • Use case is suspended. It is a good practice to make use cases suspended because 1) it informs that this function might perform I/O operations (and even block on them), and 2) it gives us flexibility on to run it blocking or asynchronously.
  • IO is transformed to Either. The IO wrapper is finally executed using attempt().suspended() which returns either the list of colors or an error. I prefer Either to throwing exceptions for several reasons: 1) try/catch feels like a goto, 2) Kotlin does not need to declare exceptions on the signature so we don’t know if a function does throw an exception or not, and 3) Either allows map/flatMap operation chaining, which makes it more readable (a.k.a. monad comprehensions).

UI layer

Finally we arrived to the UI layer. Note that this architecture is focused on the model layer, so it can be used with any UI design pattern (e.g. MVP, MVVM, MVC, MVI, etc…). As an example I will show a very simple view-model since it is the UI design pattern Google is pushing lately.

Our color item model for a list of colors:

The mapper function from repository to UI:

And finally the view-model itself:

Several things to note about this view model implementation:

  • Not unit testable since it instantiates a MutableLiveData(). You would be able to test it using Robolectric, but I’m not a big fan of it. I recommend injecting the MutableLiveData instead, so you can mock it on the tests. Note that MutableLiveData is a generic as well, so you will have to name it as well if you’re injecting different kinds.
  • The colors will be loaded automatically once the view starts observing listOfColors, hence loadColors() being private.
  • Our view-model is implementing a CoroutineScope by delegation. You can see how we’re cancelling all its jobs when onCleared() is called.

Note that I wanted to keep this quite simple. There are many more places where you can inject functions, basically every function call can be injected (e.g. previous loadColors() function in the view-model).

So what is your opinion? What are the pros and cons you can see in this architecture? Would you use it? What parts would you improve? What do you think about Arrow? Do you use functional programming patterns in your code? I’m eager to hear about your opinions!

--

--