Skip to main content

Modularizing your Android app, breaking the monolith (Part 1)


Inspired by a Martin Fowlers post about Micro Frontends, I decided to break my monolithic app into a modular app. I tried to read a little more about breaking monolithic apps in Android, and as far as I got, I felt confident to share my experience with you. This will be some series of blog posts where we actually try to break a simple app into a modularized Android app.

Note: You should know that I am no expert in this, so if there are false statements or mistakes please feel free to criticize, for the sake of a better development. 

What do you benefit from this approach:
  • Well, people are moving pretty fast nowadays and delivery is required faster and faster. So, in order to achieve this, modularising Android apps is really necessary.
  • You can share features across different apps.
  •  Independent teams and less problems per each.
  • Conditional features update.
  • Quicker debugging and fixing.
  • A feature delay doesn't delay the whole app.
As per writing tests, there is not too much difference about being in a monolith or having a modularized Android app. So we will skip tests on this series. Just make sure that each test stands in the right module which corresponds to the chosen feature.
 Now, there is a small benefit if you are thinking Android specifically:
  • Significantly reduces APK size. Which means more installs (according to some 🌚)

 A small introduction:
The app actually is pretty simple. Is built with Architecture Components and has only one Activity. Each fragment has some dependencies but they are Singletons, like database reference, or Picasso and the Retrofit API interface.

So basically, this is my application schema:


All my fragments are pulling dependencies from my Application level. Except from one of them. That fragment is totally unrelated to the other part of the app. It just shows some hardcoded values in the RecyclerView. That really looks like a nice way to start.

Note: I also have an AlarmManager and 2 WorkManagers both pulling dependencies from the application layer, which will be covered later.

But first things first, let's set up gradle for a multi module project. What I mean is that there is no need to reimplement dependencies over and over again when I add a new Android module for some new feature. Instead, we can just put all of our external libraries and dependencies inside the project level gradle and let all the other modules gradle inherit from it. That would bring better management when library updates occur. A smart thing I found in this YouTube video:

This would keep all your build.gradle files super clean. Now you don't have to require a new dependency and manage the updates because you deal with it only once. And this is how you call them after:



Note: You must apply plugin : 'kotlin-kapt' when creating a new module.

Basically, this is all what my new module needs. And now let's break something.

Create a new Android Module and just move all your new features classes over there. Don't forget layouts, strings, dimens, drawable and all resources that are unrelated to other fragments. Get them too. For the current case, there will be no errors because I inherit nothing from any of my app components.

All I have to do now, is just apply this feature to my app/build.gradle level.

Note: The nav graph should break because moving fragment outside the module would bring to unresolved element, but not to worry, we will fix this right now:

Done. The application schema now would look like this:


Now, my feature_6_module is installed as an "external library" to my app module.

Conclusion

This was part 1 of breaking a monolithic app into a modularized app in Android. Next part will be all about Dagger and core dependencies.

Stavro Xhardha

Popular posts from this blog

What I learned from Kotlin Flow API

I used to check the docs and just read a lot about flows but didn't implement anything until yesterday. However, the API tasted really cool (even though some operations are still in Experimental state).Prerequisites: If you don't know RxJava it's fine. But a RxJava recognizer would read this faster.Cold vs Hot streamsWell, I really struggled with this concept because it is a little bit tricky. The main difference between cold and hot happened to be pretty simple: Hot streams produce when you don't care while in cold streams, if you don't collect() (or RxJava-s equivalent subscribe()) the stream won't be activated at all. So, Flows are what we call cold streams. Removing the subscriber will not produce data at all, making the Flows one of the most sophisticated asynchronous stream API ever (in the JVM world). I tried to make a illustration of hot and cold streams: Since I mentioned the word asynchronous this implies that they do support coroutines also. Flows vs…

From Gson to Moshi, what I learned

There is no doubt that people are getting away from GSON and I agree with those reasons too. The only advantage GSON has over other parsing libraries is that it takes a really short amount of time to set up. Furthermore, the most important thing is that Moshi is embracing Kotlin support.

First let's implement the dependency:
implementation("com.squareup.moshi:moshi:1.8.0") It's not a struggle to migrate to Moshi. It's really Gson look-a-like. The only thing to do is annotate the object with @field:Json instead of @SerializedName (which is Gsons way for JS representation):

data class User( //GSON way @SerializedName("name") val name: String, @SerializedName("user_name") val userName: String, @SerializedName("last_name") val lastName: String, @SerializedName("email") val email: String ) data class User( //Moshi way @field:Json(name = "name") val name: String, @field:Json(name = "user_name…