Your First Clean MVI Android Project

My favorite way to set up all of my projects.

MohammedSaif Kordia
7 min readAug 15, 2021
Photo by gdtography from Pexels

Well, you already heard about clean architecture and MVI, you searched about it, and you ended up landing here, welcome aboard.

So, why clean architecture and MVI?

what’s the issue with the normal code style and the normal random design pattern, and why should I consider moving to clean architecture and MVI?

hmm, let’s start with the clean arch…

Imagin that you are a hangry paper books reader, and you are storing all of your books in a dark room with a random boxes, how hard it is to find your book,
and how hard it is to track what books you already read and how to plan for your next reading session, I think it clicked now, it’s all about organizing.

It’s all about organizing your code, I won’t go deep into clean architecture in this article, what I’m presenting here is clean architecture from my perspective without any complex and heavy talk.

Why should I move to clean architecture?

it's:
- Maintainable.
- Expandable.
- Clean.
- Follows SOLID principles.
- Fits well with large teams and projects.

OK, now I’m convinced that clean architecture is my best choice, how to implement it in my Android project?

Actually, it might look a bit overhead and a lot of steps to set it up, but once you did it for the first time your life as a developer is never the same.

I remember back in the time when I joined a team who were working on a huge project, none of them was following any rules and the app was a total mess, the code was floating everywhere, about a month later the app was hitting walls, so we had to redo the whole project from zero, imagine the waste of time and money.

Now you might ask, why MVI?

Again, without any heavy technical talk, it’s all about organizing, MVI is basically MVVM but with added features, you have an intent for every action, nothing random, we will get into it shortly.

Let’s get into the fun part…

We will start with an empty basic Android project with a single activity.

Now you have your new awesome project, the next step is to create the layers,
every layer here is a module, but wait, what layers/modules do we need?

Well, the main approach was suggesting three main layers (app, domain, and data).
- Data is responsible for all the connections with the internal and external storage.
- Domain is the middle man, it’s responsible for the repository and use cases.
- App is responsible for presenting the data to the user, all your UIs lays here.

What’s the issue with this approach!!

As I keep saying, it’s all about organizing, and this is what will make the difference.

The presentation layer, tadaaa.

If you dealt with dependency injection before, you’ll know that the di layer should see all the other layers, so with putting the UI logic in the same place,
we are risking having the data logic used directly without passing the repository rules and limitations, everything should be intended.

So, the new approach is like the following:
- App for the di — can see all layers
- Data for the internal and external storage — can see domain layer
- Domain for the repository and use-case — no access to any layer
- Presentation for the UI logic — can see domain
check the Gradle files in the example project for more details.

what well we benefit from this:

- As you are dealing with Kotlin, you can easily switch to KMM (Kotlin Multiplatform Mobile).
- You are able to update your logic from one place and it will automatically be applied to all the app parts without messing with every block of code.
- Every member of the team will focus on his owned code without minding the other changes.
- Your code is expandable more than you can imagine.

Let’s get into code

If you take a look at your Gradle, you’ll find it like this:

We can do one more step to organize our dependencies even more,
create a new file named dependencies.gradle in your main directory.
What is this file?
It’s a Gradle file where we will put all our dependencies and reference to it
this way we can make sure that all our dependencies are consistent and following the same libraries and versions.

Finally, add this line to the top of your project-level Gradle file to tell your project where to reference to
apply from: ‘dependencies.gradle’

This is how our dependencies look like:

And this is how our app module Gradle file looks like:

It might look like a long process, but once you feel the benefits of it, you won’t regrate it, and you’ll love to repeat it with every project you start.
Actually, I’ve already moved some of my dead apps to this just to test the water and see how it goes, and it went well.

I think you are getting it now, what’s next?

As we moved the clean architecture part away, we are good to go with our code.

Assuming that you know how to di using Hilt and know the basics of navigation components and Room db, I’ll start with the base and basic classes like:
- DataState
- BaseViewModel
- BaseIntent
- BaseFragment

let’s break them down one by one:

DataState.kt
A sealed class that will wrap our data response, contains 4 status types
- Success — we wrap the response with Success when we get the data successfully.
- Error — we wrap the response with an Error when we get an error fetching the data.
- Loading — a Nothing object to represent the loading status.
- Empty — a Nothing object to tell the UI that the response succeded but no data found.

BaseIntent.kt
It’s literally an empty class just to parent the intent classes,
then we will use polymorphism with the child intent classes to generate the fragments, don’t worry, we are coming to this.

BaseViewModel.kt
This is the base of all view models, it requires a generic class of BaseIntent it made it easy to abstract the onTriggerEvent function, where we will direct all the coming intents from the fragment.

BaseFragment.kt
OK, here is the fat stuff, we will put here the functions that we will use regularly, the class will take a generic type of ViewBinding,
meaning that I’m able to do all the binding set up in the parent class with fewer steps from initiating to destroying.

Also, we have methods to launch a block of code in IO and UI using Coroutines easily.

Another interesting thing is the host activity, you might ask here, what’s wrong with requireActivity()?
In some cases, the host activity might not be available by requireActivity(), like when you are doing a process in the background/IO and you navigate
outside the fragment before the process finishes then asked for requireActivity(), you guess it “Exception”,
the hostActivity from the BaseFragment will return the context of the MainActivity if the fragment is attached else it will return a context as MainActivity.

Now let’s dive into the MVI part:

To show you the right way to set and get the data from the local storage, I’ve set a Room database with one table and two columns “names table” (id, name).
The app will take the name input from the user, store it, and show all the names from the table.
After that, I’ve set the repository interface and placed it in the domain module so the presentation layer can see it,
then we get to the repository logic “NameRepositoryImpl.kt” this one should lay in the data layer so the presentation layer is completely blind about the logic.

Now it’s the presentation turn, we will create the three main classes fragment, view model, and intent:
- NameFragment.kt — inherits from BaseFragment.kt
- NameIntent.kt — inherits from BaseIntent.kt
- NameViewModel.kt — inherits from BaseViewModel

Starting with the intent:

NameIntent.kt

NameViewModel.kt

NameFragment.kt

And that’s it, now when you request data from the data layer you know the exact state of the response and you know how to deal with every type of state.
Also, you are not limited to what is mentioned in the DataState class, you can add any state that fits your logic.

Bonus…

Here are some tricks that you might face on your trip:
- Add this implementation line to your app module file to support multidex
implementation ‘androidx.multidex:multidex:2.0.1’
- Add this line to gradle.properties to avoid hilt di issues
kapt.use.worker.api=false

Conclusion…

Clean architecture is amazing to architect your project, but always, we can do better, feel free to add your touch but be careful.
App architecture is important but it’s not everything, we need design patterns to make perfect harmony.
It’s all about organizing, it will save you money and time in medium and massive projects, you might think of it as an overhead, but no, it is totally worth it.

Feel free to contact me if you have any questions.

The sample project on github:

References:

--

--