Kotlin Multiplatform Guidelines. Architecture.

Photo of Samuel Urbanowicz

Samuel Urbanowicz

Updated Feb 27, 2024 • 7 min read
john-barkiple-539580-unsplash

Just like in the other hybrid solutions Kotlin Multiplatform allows to easily share the business logic between all the platforms.

Moreover, the business logic can be compiled to many different platforms like JVM, Android, JavaScript, iOS, Linux, Windows, Mac, and embedded systems using Kotlin JVM, Kotlin2JS or Kotlin Native.

Because of that, there is no need for a bridge between shared code and native platforms which gives us the best possible performance. Unlike other popular solutions, UI has to be implemented natively which gives us the opportunity to create pixel-perfect layouts and doesn’t affect performance either. For this reason, multiplatform projects created in Kotlin requires a slightly different approach comparing to other cross-platform solutions in case of software development process or even team structure. This review of Kotlin Multiplatform Guidelines is going to focus on architectural aspects of multiplatform projects developed in Kotlin.

Choosing architecture

Choosing the right architecture is the most important step in the planning phase of software development. In Kotlin Multiplatform project it is even more important. The biggest advantage of multiplatform solutions over the native ones is that we can share the same code between different platforms. We have to keep in mind that in Kotlin Multiplatform, we are able to share only the business logic layer and that the UI code will be implemented natively. Because of that, architecture should be chosen very wisely and it has to be the best possible option for all the platforms that will be sharing the common code.

After a deep research, in our case, we’ve decided to use MVP, because:

  • We were able to easily separate the common code from the platform-specific one

  • We were able to easily place the whole presentation and data layer in common modules

  • We were able to easily connect the presentation layer to the native views

Screenshot 2019-02-22 at 09.20.27

Using MVP in Kotlin Multiplatform projects has many advantages:

  • We can easily adapt it to different platforms like Android, iOS or even frontend

  • We can share the whole presenter and model layers across every platform

  • We can easily test the common code

  • We can easily define the common View interfaces and implement them painlessly on the native side

  • We can easily integrate the common code with the native one - we just need to add one presenter per view and implement the proper interfaces

Of course, there are some disadvantages:

  • Presenter needs to keep a reference to the view, so we have to remember to attach it and detach depending on the view lifecycle to avoid memory leaks!

  • Requires some boilerplate code

  • MVP is not a first option architecture on some platforms

Organizing the common code

Kotlin Multiplatform gives us a possibility to share the code between different platforms. Using Model View Presenter architecture allows us to easily share the whole Presenter and Model layers between different clients, but what about the backend? Yes, we can easily share the code even with the backend! How to share different code with different platforms without any redundant code? We can use project modularization.

Let’s assume that we are creating a project that consists of Android, iOS and the backend which will be written in Spring. We already know that we have to write the whole clients UI natively, so we have to create two separate modules which will be responsible for handling clients views. We also know that we can easily share the whole Presenter and Model layers between clients, so we have to add one more module for that. During the planning, we discovered that we can also share some common code between backend and clients common module (e.g. data models) - we need an extra module for that. :tada: we have our final project structure:

  • Android module - Android app UI

  • iOS module - iOS app UI

  • Backend module - backend specific code

  • Clients-shared module - the code that will be shared across clients (Presenter + Model layers)

  • Common module - the code that will be shared between backend and clients-shared module (e.g. data models)

Project structure presented above allows us to clearly separate the code between different platforms. It’s crucial to think with which platform we can share the code and create separate module if our current project structure doesn’t fit our requirements (e.g. we don’t need to share data models with the clients).

Platform-specific declarations

There is no possibility to share the whole business logic between different platforms. There are some features that have to implemented natively because they behave differently or they use different components on each platform (e.g. taking photos or key-value storage). We can deal with it in two ways:

  • Create an interface in common module, implement it in platform-specific modules and pass it through presenter constructor to the Presentation layer

  • Define a common interface for those features using expect/actual mechanism

The first option is quite easy to implement and use. However, this approach is not ideal in cases when you have a library on one of the platforms that implement the functionality you need, and you'd like to use the API of this library directly without extra wrappers. Also, it requires common declarations to be expressed as interfaces, which doesn't cover all possible cases.

With expect/actual mechanism, we can implement platform-specific features in a common module. To do that we need to place the expected class definition in common package and after that, the compiler ensures that every expected declaration has actual declarations in all platform-specific packages, and reports an error if any actual declarations are missing.

Platform-specific package has access to classes from native platform. Because of that, we can easily implement almost every feature that we need.

Using external dependencies

Using external dependencies speeds up the development process. Our app needs a Dependency Injection - we can use one of many existing libraries. We need an HTTP client - we can do the same

There are thousands of platform-specific libraries which we can use to handle different kinds of features, but it’s not so easy to find proper library when it comes to Kotlin Multiplatform. Why? Because technology is quite fresh and there aren’t many existing libraries. Moreover, the external dependency that you need, has to support every platform from your project. When adding an external dependency to the project be sure that:

  • It supports every platform that you need. The library should contain a common artifact and one artifact per each platform that you need

  • Library supports all the platform versions/devices that you need

  • External dependency can be easily integrated

Photo of Samuel Urbanowicz

More posts by this author

Samuel Urbanowicz

Samuel is a pragmatic software engineer skilled in Android app development. A fan of modern...
Lost with AI?  Get the most important news weekly, straight to your inbox, curated by our CEO  Subscribe to AI'm Informed

We're Netguru

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency.

Let's talk business