Architecture

Thanks to this architecture, we not only gained enormous code reusability but also everything has its own place. It contains following modules:

common — contains elements shared among all other modules. Generally, it is Data Model and helper functions. This module might also contain other elements if they are used both in clients and in backend.

— contains elements shared among all other modules. Generally, it is Data Model and helper functions. This module might also contain other elements if they are used both in clients and in backend. common-js and common-jvm — contain all platform specific types for common module.

and — contain all platform specific types for module. common-client — contains business logic for all the clients. Generally, it is placed in Presenters and Use Cases.

— contains business logic for all the clients. Generally, it is placed in Presenters and Use Cases. common-client-js and common-client-jvm — contain platform specific parts of common-client . In this implementation, I decided to place client repositories there, since we want to inject them into presenters and they are specific to platform (the same Java networking libraries can be used both on Android and desktop).

and — contain platform specific parts of . In this implementation, I decided to place client repositories there, since we want to inject them into presenters and they are specific to platform (the same Java networking libraries can be used both on Android and desktop). web , desktop and other clients modules — contain views for every client. This views implement view interfaces from common-client and pass events to presenters.

, and other clients modules — contain views for every client. This views implement view interfaces from and pass events to presenters. backend — contains backend implementation.

— contains backend implementation. android and other common modules for clients built for single platform — contain shared elements like resources or helper functions.

With this architecture, we can achieve pinnacle of code reuse. Thanks to that we don’t have to implement business logic for every client separately. Similarly, changes in business logic can be applied in the single place. When we check that some business logic works in one client, we can be sure that it works in all other clients too. In this way, not only manual testing is simplified, but also unit testing now can be implemented only once, for the common-client module.

Enough theory, let’s see some code and discus in practice key elements of this different modules. It is not going to be deep analysis because this article is already long enough. We will present deeper analysis of different aspects in next articles. They will be presented one after another in next Mondays on Kt. Academy. But still, this article should give enough explanation to start your own project or contribution into the example project (feel welcomed ;)).

Common module

common module includes following Kotlin files:

common module source set

Data Model in subpackage data includes classes that:

Represent data used in the project

Represent API DTO (model of objects passed via API)

For example, this is a News class:

News class in common module

Have you noticed annotation? It is kotlinx.serialization annotation. We need it to deserialize an object in Kotlin/JS. List of News is passed via API packed in NewsData class:

NewsData class in common module

The reason behind it can be found here.

Another thing to notice is that News includes parameter with DateTime type. It is a class that represents point in time. It is defined in common module using expected declaration:

DateTime expected declaration in common module

Its declaration is minimalistic because we currently don’t need more. We use it just to order the news on the clients. This is why it implements Comparable<DateTime> interface. Method toDateFormatString and extension function parseDate are needed to be used in the serializes. DATE_FORMAT specifies format for API. Its actual declarations are defined common-js and common-jvm :

DateTimeJs in common-js

DateTimeJVM in common-jvm

DateTime is custom type so we need to specify serializes for it to be able to pass News via API. All Kotlin/JVM projects are using Gson to serialize and deserialize objects, so we can define Gson instance in common-jvm . We need to add converter for DateTime class there:

Gson instance defines in common-jvm

Note that both backend and common-client-jvm depend on common-jvm , so we are able to use gson in both of them.

Similarly, in common-js we define kotlinx.serialization JSON instance used to serialize and deserialize JSONs and we define it with DateTime serializer:

kotlinx.serialization JSON instance defined in common-js

Now we can pass News via API and it can be correctly deserialized in the clients.

Another thing that we can find in common module are properties with names of parts of endpoints:

When they are defined in the single place, then in case of a change is needed, we wouldn’t have to search for all the places where we reference them. We can just change the value of the single property.

Common-client module

common-client module includes presenters and use cases with clients business logic. Let’s briefly describe one of the presenters to understand how it works. We will describe NewsPresenter which controls views displaying news. Its business logic rules are following:

After view is created, it loads and displays list of news. During that news loading there is loader being displayed.

When user request refreshes, news are loaded. During that refresh there is refresh indicator being displayed.

News are refreshed quietly every 60 seconds.

News are displayed in the descending occurrence order.

When any news loading returns error, it is displayed.

This is how we are representing view that is controlled by this presenter:

NewsView in common-client

BaseView also specifies methods to show and log error:

BaseView in common-client

Here is the presenter implementation:

NewsPresenter in common-client

Presenter lifecycle

NewsPresenter extends BasePresenter class that takes care of cancelling all started jobs during presenter destroy (to prevent data leaks).

BasePresenter in common-client

It also implements Presenter interface which specifies basic presenter lifecycle methods:

Presenter in common-client

Views need to call this methods during view creation and destroy. We will see later how it is ensured for Android.

Kotlin coroutines in presenters

Presenters are using Kotlin coroutines for concurrence. Coroutines are not yet supported in common modules, so we have to specify expected declarations with methods we need:

This is how actual declarations for them are implemented in common-client-jvm :

Expected declarations from common-client-jvm for actual declarations in common-client coroutines functions

Lightweight alternative to dependency injection

NewsPresenter uses NewsRepository and PeriodicCaller . Both of them are provided using lightweight alternative to dependency injection. It uses following class:

Provider class used as a lightweight alternative to dependency injection. Defined in common module because used both in backend and in common-client.

When we have a class we want to inject, we need to make its companion objects extend Provider and we override create method:

PeriodicCaller class defined in common-client module

After that we can get instance lazily using:

We can also easily override this instance for unit testing purposes (check out unit tests).

NewsRepository is an interface defined in common-client:

NewsRepository class defined in common-client module

Repositories implementation

News repository implementation needs to use network libraries that currently cannot be defined in common module. All client repositories are specified in platform modules common-client-js and common-client-jvm and we provide them using RepositoriesProvider expected declaration:

ReporitoriesProvider in common-client

Actual declaration and implementation of NewsRepository from common-client-jvm :

ReposirotyProvider from common-client-jvm

Implementation of NewsRepository defined in common-client-jvm

This is all we need to understand how NewsPresenter works. Its behavior can be also viewed from unit testing perspective (its unit tests can be found here, and soon I’m going to publish the article concentrating only on unit testing common modules). To really understand presenters, we need to see them used in the clients, so let’s see how NewsPresenter is used in Android and web clients.

Clients

Detail description how different clients use presenters will be presented in articles about specific clients (we will publish this articles next weeks, on Mondays). For now, let’s discuss simplified NewsActivity (the below implementation contains only elements connected to the presenter).

Simplified version of Android NewsActivity. You can find full class here.

Activity implements NewsView interface and it overrides all its members. loading is bound to the ProgressView visibility, and refresh is bound to the swipe refresh of the list. It is possible using KotlinAndroidViewBindings library. showList is mapping news into the adapters and displaying them on the list. We don’t need to call presenter lifecycle methods explicitly because they are called in BaseActivity :

BaseActivity in Android

BaseActivity also implements BaseView and overrides its methods, thanks to that we don’t have to define showError and logError in every Activity. More about Android implementation and other tricks that are supporting multiplatform Kotlin project I am going to describe in separate article about Android.

Similar approach was applied in web module, even though React works radically different than Android:

NewsComponent in web

You can check it out on repository example or you can wait for the article about this part.

Next steps

As I have already mentioned, there are still lots of plans behind this example project:

We want to make it massively multiplatform and implement as much different native clients as possible

Not everything is finished in the project (check out todo comments in the project)

There are also some improvements that are currently hard to implement, but we want to have them because they might be really helpful for other multiplatform Kotlin projects:

Network API should be implemented once for backend and all platforms repositories. For that, we need special networking library that would support that. Although it is possible. I will present the idea in the separate article.

and all platforms repositories. For that, we need special networking library that would support that. Although it is possible. I will present the idea in the separate article. Elements like colors or translations could be extracted. It is hard because in most platforms there are different mechanisms to define them. Currently, it might be done using scripts or Gradle plugin. Alternatively if all views would be implemented in Kotlin DSL (in this project only Android is not) then there might be some mechanism introduced that returns strings for tag and specific language.

What is the primacy over other solutions like React Native or Flutter?

The big point here is that we are actually creating native application in native frameworks. If you check out this different applications then you will easily notice that their view and how they act is specific to the platform. In Android, we use CoordinatorLayout from Android Support Library. In web, design is typical to modern websites. In desktop, we see new windows.

We can use all tools and advantages of every platform. In Android, there is Crashlythics used to track errors. In web, there are share buttons for Twitter and Facebook. In the desktop, we are showing commenting in new window.

Applications are native and we do not depend on any bindings or bridges. We can do everything we could do before and our multiplatform architecture is supporting us to make it as efficient as possible.