117

Extracting multiplatform common modules in Android – Kotlin Academy

 6 years ago
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.

1*VQp61jlzCCQhUpqQqfH45A.png

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.

1*FLfFp81mxH1UsjIrtt_4_w.gif

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:

1*KZiwo9eqwvCzMsr1aW0ObA.png

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):

1*2sBGMpRi7ScUi4UvHyr1sQ.png

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:

1*2Q4oz9Spi_f78br9Eplz8w.png

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:

1*18hYb3wBN06YCG4fSRr2FQ.png

And we can specify actual declaration in common-jvm this way:

1*uzdaq_3NikT5nksxYM_-zg.png

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:

1*XzeoMtTtgGTAI_k0cjmWcA.png

While Java date has getDate and getMonth methods, we can just add them to expected declaration and everything will work fine:

1*afuGgATJNIJv1XNl6lLobg.png

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.

1*K-f1laplrjQQAMlYKfLgHw.png

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.

1*_5fSMIbrfUv662o7jz5EFw.png

build.gradle in common-client module

1*MGBEDQCNqILhEW70E4lwUg.png

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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK