Extracting multiplatform common modules in Android – Kotlin Academy
source link: https://blog.kotlin-academy.com/extracting-multiplatform-common-modules-in-android-4a564cc03e0a?gi=4ed540ae34f6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Extracting multiplatform common modules in Android
You know how to make Android projects, but now you want to make them multiplatform. You heard about new possibilities that Kotlin gives and you want to check them out. Time to learn how to make an Android part with solid multiplatform architecture.
To see an example, check out the Kt. Academy application. It is a part of the bigger open-source multiplatform Kotlin project that includes more clients for other platforms.
Kotlin
The first step is to move Android project into Kotlin. Here is an example how it can be done. Don’t worry, it won’t affect the way application works. Instead it will help you to make code more expressive, readable and reliable.
Create common module
Next, you need to extract common module. Common module is Kotlin module with code not specific to any platform. Its can be compiled to Kotlin/JVM, Kotlin/JS or Kotlin/Native. Thanks to that it can be reused in all other modules. (You can read more about it in Kotlin docs)
To create common module, add empty folder named common
in the root of project. Inside it, you should create the build.gradle
configuration file. This is the base configuration you can start with:
build.gradle in common module
You should also make a folder common/src/main/kotlin
for your source. You will place all common
module code in there. You also need to include 'common'
in your settings.gradle
. If you synchronize now then you will have empty common module in your project. This is the module where you will be placing code that will be shared among all other modules. Problem is that you cannot use it in your Android project yet, because you still need platform module.
Create platform module
You cannot use common module in your Android, iOS or web module directly because it is not specific to any platform (by platform we mean JVM, JS or Native). First we need platform specific variants of common module that are specifying small things that differentiate the platforms (if any).
In the moment we need only Kotlin/JVM variant, because we have only Android implemented, so need to define common-jvm
. Add following minimalistic build.gradle
file (it uses Java 8):
build.gradle in common-jvm module
This project will include all code that is specific to Kotlin/JVM. It can depend on JVM libraries like Gson, Retrofit or JUnit. You should also make folder src/main/kotlin
for its sources.
Now you can go back to your Android and add dependency on common-jvm
:
When you sync your project, you should have all set up. Let’s finally extract data model.
Extract data model
Data model is a set of classes that represents how we see data in our application. Often there are also DTO objects which represents how API sees data. Both of them should not include any logic, but only properties and functions that are used to hold and get representation of data. They should not be platform dependent so they can be easily moved to common
module. To move them, we only need to move files from android
source to common
module source. As long as packages didn’t change, everything else should work well in your project. If your data model don’t depend on any platform dependent classes then you don’t need to add anything to common-jvm
and everything will work fine.
Bigger problem is when some classes moved to common
module depended on platform specific classes or libraries. Often example is when they use Calendar
or Date
from Java standard library. Such classes will throw error because common modules cannot use platform specific features. Simple workaround is to hide such declarations behind interfaces or replace them with primitives (like Long
with millis in this case). Although we would then have to change in whole project how we use them. Better approach is to implement for such classes expected declaration in common
module, and actual declarations in common-jvm
. Generally expected class declaration is a definition of class and its methods that needs to be implemented in every platform module (currently only common-jvm
).
As long as we are not using any methods on common module, we can replace Date
declaration with DateTime
defined as follow in common
:
And we can specify actual declaration in common-jvm
this way:
We can also add some expected methods to this expected class if we need to use them in common module. Let’s say that we need to get year:
While Java date has getDate
and getMonth
methods, we can just add them to expected declaration and everything will work fine:
Although we might not like Java Date
interface and instead we may want to specify new class that will cover it. Check out my description of such implementations here or here. This way we can make whole data model clean and platform independent on platform classes.
Why all of this?
Now you can easily add other modules, make them depended on common
, and profit from the fact that you have shared data model. For instance, you can make backend
or you can make more clients (web, desktop, iOS, browsers plugins and nearly every other clients you can imagine). Although it is just data model and you might have an appetite to extract much more. Especially different clients are sharing the same business logic which is really important and needs to be unit tested. Let’s extract it now.
Create module common to clients
Similarly as we’ve created common
and common-jvm
, we should now create common-client
and common-client-jvm
. They will be used to hold business logic. Android project should depend on common-client-jvm
, common-client
should depend on common
, and common-client-jvm
should depend on both common-client
and on common-jvm
.
build.gradle in common-client module
build.gradle in common-client-jvm module
If you refresh project now, you will have two additional modules configured.
There are a lot of different development architectures, but most of them are not making parts where business logic is implemented independently from platform. Although it is one of most important postulate of Clean Architecture. It is also one of the most important ideas behind MVP architecture. This is why I think it is the best candidate for multiplatform Kotlin project with shared clients logic. If you are not familiar with it then check out this presentation or some of the articles about it. General idea in context of multiplatform Kotlin development is also described here.
Extract business logic
In MVP, all business logic should be placed in presenters or use-cases. They should depend only view and repositories interfaces, so this three elements can be moved together to common-client
.
If you used some supporting libraries that are platform dependent, like RxJava or Guava, then you need to get rid of such dependencies or you need to create expected declarations for every kind of usage. Good multiplatform replacement for RxJava is Kotlin coroutines (check out here how it can he used in common modules).
You can see how presenters and views are implemented in code of the demo application. Here you can find detailed description of one of the presenters and how lifecycle events are handled (I don’t want to repeat myself). General idea is that every Activity, Service or Fragment should use one or more presenters. It should call its lifecycle methods or other methods when some event occurs. It should also expose some of its own methods using interface. This way view and presenter can communicate.
Bigger problem is with repositories. If you passed them to presenters via constructor hidden behind interface then everything is all right. If you provided them using dependency injection then you have bigger problem. There are no dependency injection libraries yet that support common modules (although Kodein might start supporting it soon). In the demo application, we made lightweight dependency injection alternative (also described here). To use it we need to define expected declarations with functions that provide repositories. Therefore repositories need to be defined in common-client-jvm
module.
Summary
This article presented how we can extract common elements in Android using Kotlin common modules. It strongly refers to the article that presented an architecture for effective multiplatform development in Kotlin. All this steps makes sense when we start implementing another clients or modules and we reuse this elements. This is going to be described in next articles on Kt. Academy. Subscribe to be up-to-date. If you want to check it out right now, then you can find web, desktop and Wear clients implemented on demo project. You can also install Android app or visit web page.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK