Kotlin, platform agnostic, or is it?

Miroslav Radanović
Undabot
Published in
7 min readFeb 8, 2018

--

Initial idea for this blog/tutorial came when I started using Kotlin. Everybody was talking about all the cool stuff that Kotlin has to offer. One of those things is that it can be compiled down to: Java compatible bytecode, JavaScript (ES5.1) and when targeting native, Kotlin will produce platform-specific code (via LLVM).

So when you consider that Kotlin could be used for building backend, mobile, desktop or web apps, and that all those apps could share the same codebase, Kotlin seems like a reasonable option for your next project.

In this blog, I will be making a small showcase app to demonstrate how you could easily develop a client web application as an after-effect of developing an android app.

For development of these apps I will be using the new experimental Kotlin’s multiplatform feature. From my perspective, this is a really cool feature for developing any kind of client side, because mostly all client apps (web, Android, iOS or desktop) will have the same business logic behind the view. This could be done with small app adjustments and combining common features under same modules.

Project architecture

Showcase project will be built with gradle and will contain several gradle modules: common, common-jvm, common-js, android and web app module.

First create an empty gradle project. Project build.gradle file will group all common repositories and dependency versions in one place so other modules can also use them. Here you can check the final version of project build.gradle.

Base project structure

The next step would be to create all other modules:

Common: Contains all common shared code between js and jvm platforms. All code inside this module has to be compilable by both applications (android and kotin-react app). The code in this module has to depend on kotlin-stdlib-common or use any external library that is built upon kotlin multiplatform feature or kotlin-stdlib-common. Modules common-jvm and common-js depend on this module.

common-jvm: Contains general jvm-related code and must contain dependency of jvm libraries used in common module that are not built upon kotlin-stdlib-common. File gradle.build must explicitly declare that this module is expected by common module expectedBy project(":common")

common-js: Contains general js-related code and must contain dependency of javascript library used in common module that are not built upon kotlin-stdlib-common. File gradle.build must explicitly declare that this module is expected by common module expectedBy project(":common")

android: It will depend on common-jvm module and its gradle.build file must declare compile project(:"common-jvm").It will also contain android specific things, like activities, fragments, a GPS provider, Bluetooth provider…

web: It will depend on common-js module and its gradle.build file must declare compile project(:"common-js"). It will also contain web specific things, like ui components…

Modules relation

Application Architecture

Both applications will have similar architecture based on MVP architectural pattern.

The only component that is not commonly used in MVP pattern is a Coordinator and you should consider it as a part of the app that will notify Presenter or Use Case about user actions. Use of Coordinator is just to simplify feature logic flow, readability and feature maintenance.

Android and web app architecture

To share or not to share?

MVP architectural pattern seems as a pretty good approach for dividing specific platform code into chunks that can become platform agnostic.

If we check the image above, we can see that almost every app component could be reused, except views and services/providers, so the only thing we have to do with not reusable components is to make some interfaces and wrap platform-specific code behind platform-specific declaration.

Also our apps could share models, utilities and tests of implemented features.

Let’s see it in action

As a showcase, we will implement login feature for both apps, but before we dive deeper into the feature implementation let’s assume that we have created an Android app module and a web app module that will use Kotlin/JS and React framework.

First step is to list all components our feature will need: MVP (Models, View, Coordinator, Presenter), API service (note that both apps will have specific network calls handling), long running task handling (kotlin coroutines) and platform-specific view components (Android activity/fragment or web React component). Since this will be some kind of a platform agnostic code, most of the code will be placed in a common module, so in further text it will be explicitly mentioned if something is not inside common module.

  • MVP: important thing here is not to use anything that is specifically related to targeted platforms, in our case Android or web app.
  • Specific platform view component: Inside Android and web app module, we need to have view components (Activity or ReactComponent) that will implement the view interface of a feature created in common module scope
  • Platform-specific services: Since I didn’t find any appropriate HTTP client library that could be used for both JVM/JS modules, and feature logic inside common module depends on network calls, this will be an appropriate place to use platform-specific declaration. Shortly, this kind of implementation will provide availability of common module to use network calls of specific platforms. To implement this, common module must declare what it is expecting.
NetworkUtils.kt in common module

Gradle will not be able to build project until we declare actual implementation of expected function/class in modules that declared expectedBy project(":common”)in gradle.build. In our case this is common-js and common-jvm modules.

NetworkUtils.kt in common-js module
NetworkUtils.kt in common-jvm module
Use of expected methods in common module

If you check above class ApiService in common module, you will see that it is using a method declared in NetworkUtils common module, but in compile time it will use actual implementation, depending on specific platform.

common-jvm
compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutine_version"
common-js
compile "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$kotlinx_coroutine_version"
Coroutines implementation in common module

Assembling android app

Before we can see any results, we need to wire common code with an Android app. First we have to setup connection points of login feature implemented through common module and Android app. As we can see on the image bellow, all we need to wire it up is to implement login view interface in LoginActivity and initialise LoginCoordinator that will hold reference on feature view.

Android app wired with common module

The last step before it is ready for testing is to declare the coroutine context that will be used by our app for running long-running tasks. In our app we initialise it in App.kt class.

Initialization of coroutine context in android app

Assembling web app

Process of assembling a web app is same as for Android, implement login view interface in LoginComponent and initialize LoginCoordinator that will hold reference on feature view.

Web app wired with common module

Since I’m a newbie in web programming world, Ihave used kotlin-conf web app as a template for showcase web app.

Pros and cons

Pros

  • Business layer tests wrote just once
  • Pretty cool solution for a one-man development team that is aiming to cover more platforms with one strike
  • Available reusing of components on multiple platforms
  • Relatively easy to add a new platform, for example in our case we could easily add a desktop app
  • Knowledge of only one programming language needed
  • Possibility to develop libraries that have a goal to cover multiple or single platform

Cons

  • Increased project build time
  • Developer needs to have wide knowledge on different platforms
  • Really good team coordination needed if more people are working on the same project but different platforms
  • Project setup can be really painful since module dependency is tightly coupled
  • Still in early phase of use
  • Not a lot of libraries are providing option to be used with kotlin multiplatform feature

Would I use it for my next project?

It depends on the size of a project, for smaller personal projects I would definitely give it a try. Same codebase would probably provide faster development of simple features on various platforms.

For larger and more complex projects I would try to avoid situations where I have to work simultaneously on more than one platform. Or imagine that more people are working on same multi platform project that has a goal of producing an Android, web, iOS and desktop app. Consider that each app will have one developer that is familiar with assigned platform and that same developer has to contribute to shared codebase. That means for almost every new feature all developers should work in synergy to create shared feature logic. This scenario could affect project resource consumption and lead to greater complexity of apps.

If you want to check the whole project, the source code is available here.

Thanks to Sinisa Cvahte for the design.

Thank you for reading. Please comment, like or share it with your friends and we hope to see you soon.

Would you like to join us? Check out the open positions at our Careers page.

Undabot and Trikoder are partner organisations. We analyse, strategise, design, code and develop native mobile apps and complex web systems.

--

--