· 8 min read Posted by Kevin Galligan

Piloting Kotlin Multiplatform is Easy. Scaling is Hard.

When native mobile teams explore KMP, getting started is pretty easy. The difficulty emerges when they try to scale. To get the most out of KMP for your team, and for KMP as a platform to succeed, we need to make scaling easy as well.
https://www.flickr.com/photos/37467370@N08/7686480546/
Credit: https://www.flickr.com/photos/37467370@N08/7686480546/

Overview

Many native mobile teams struggle to scale their KMP efforts. The reasons why are easy to understand, certainly for any team that has attempted to do so. Solutions to scaling problems also aren’t very complex, but they require an approach specific to the native mobile team context.

The major themes to take away from this post are the following:

  • To get value from KMP, you team needs to use it where they do most of their work. For native mobile, that means feature development.
  • A KMP library publishing model doesn’t work with feature dev.
  • Merging app code and KMP code into a single repo creates new problems.
  • Scaling KMP doesn’t have to be hard, but a plan and approach specific to native mobile teams is required.

Our post series KMP For Native Mobile Teams presents a full overview of our current approach for native mobile teams evaluating and adopting KMP. To understand how teams adopt KMP, and how to address the challenges teams often face, make sure to read through that series.

Context is important

KMP is a big topic. There are many platform targets, and especially for native mobile, no default “layer” at which native code stops and KMP starts.

For professional guidance to be useful, it needs to be context-specific. When discussing shared KMP for native mobile teams, this is the context:

  • You’re building native mobile apps, with a team of native mobile specialists (Android and iOS developers).
  • “Team” is at least 2-3 devs, who mostly stick to their platform specialty.
  • Your goal is to have the team write KMP at scale. That means more than just a few modules. You want KMP to represent a significant volume of your code going forward.

Scaling KMP means KMP will need to live where most of your work happens. Obviously that will impact the whole team. Scaling is hard because current approaches to KMP don’t apply well to native mobile teams.

What is “scaling”?

Scaling Kotlin Multiplatform means:

  • Using KMP for a significant portion of your code
  • Developing KMP efficiently

Scaling is the most critical phase of adoption because scaling is when you have the opportunity to actually get some benefit from KMP.

Prior to scaling, your team is mostly experimenting with KMP, and the team’s day-to-day development workflow is largely unaffected. There won’t be much KMP code, and you’re probably not writing it very efficiently.

KMP is only valuable if you get some benefit from using it. The amount of KMP you write, and how efficiently you write it, determines that value.

Most teams introduce KMP with a library dev model. In a library dev model, KMP code lives in a separate repo. Versions are published from that repo. The library dev model is simple to set up, and doesn’t really impact the day-to-day workflow of the teams. We also recommend the library dev model when teams are evaluating KMP.

Cloud publishing diagram

Library dev usually means publishing binary dependencies, but we no longer recommend this approach.

Share Kotlin source instead of binaries

We no longer recommend publishing libraries with binary dependencies. Share Kotlin source code instead.

While library dev is easy to implement, you can’t use this model to scale your KMP usage. For native mobile teams, scaling means feature dev.

Library dev vs Feature dev

Let’s clearly define what library and feature dev mean. These are intuitive concepts, but to understand issues with KMP scaling, we should make sure we’re all talking about the same thing.

Library dev is what it sounds like. You’re writing some library code. The important characteristics of library code are:

  • Modules of logic not directly tied to your day-to-day app dev
  • Published with (roughly) linear versions
  • Generally housed in a separate repo

Exactly “what” logic is in your library doesn’t matter. What matters is that the library code lives on its own schedule, separate from your day-to-day app development. Examples include networking code, various logic (tax calculation, etc.) Anything that isn’t related to your direct, day-to-day app development.

The external schedule and versioned publishing means you’re essentially not writing library code at the same time as your app code. They are loosely connected.

Feature dev is your day-to-day development. You’re adding a feature to the app. You write the UI and underlying logic of that feature, as a unit, in the same repo. Code for that feature is reviewed and merged as a unit.

Scaling means Feature Dev

When working in library dev mode, the code generally doesn’t change often, and the total volume of code is small. That’s great when you’re piloting KMP and learning how KMP works. However, you’re not getting much value from KMP. There’s simply not enough code being shared for KMP to have a positive overall impact.

Scaling means writing your feature logic with KMP. Why? Simple. That’s where most of your work is.

Why is scaling hard?

Scaling KMP for native mobile teams is hard. There are several specific reasons, but all of them have the same basic problem.

The default KMP model makes no accommodation for how native mobile teams work. It simply assumes teams will radically change their structure and workflow.

Library dev is the wrong model

The logic you write to support your UI should be edited as a unit. A button click needs to “do” something. While you can write that logic separately, it will be very frustrating and inefficient.

Library dev forces that separation. Of course, you can configure your development setup to edit that library locally with the UI code, but you’re still forced to push KMP code through a separate review and publish cycle. The app code needs to wait for the library code to be published before it can be reviewed and merged. With very small teams, this will be frustrating but functional. For larger teams, it will create serious workflow problems.

Library dev vs feature dev slide

Monorepo to the Rescue?

The obvious answer is to move all code, KMP and both apps, into a single repo¹. While a single repo will solve the workflow issues inherent to the library model, the monorepo model creates its own issues.

Those new issues all fundamentally have the same root cause. Any change to the KMP code impacts both apps. That means every KMP change requires native implementation and testing on both apps. That forces a coupling of your app logic and development teams which would otherwise not exist. The post ”KMP For Native Mobile Teams - Scaling: Why it’s hard?” covers the details of why this is a problem. In summary, you have a slower, more complicated, potentially inefficient development workflow.

Trying to increase development efficiency by introducing an inefficient workflow is counterproductive.

Workflow Friction

With either repo configuration, library or monorepo, there is significant friction introduced to using KMP. That creates incentive for the engineers implementing feature code to skip KMP and implement the feature fully on the native platform. Especially with deadlines looming, skipping KMP can feel more efficient (and possibly is).

Merging teams

When talking with developers and managers interested in KMP, we’ve found the desire to “merge teams” is fairly common. Rather than Android and iOS developers, everybody would learn both platforms and work as one team.

This is a bigger topic than we can cover here. Some of my thoughts are in ”KMP For Native Mobile Teams - Scaling: Why it’s hard?”, and there will be a longer post soon.

In short, it’s a risky plan. Native mobile folks have been talking about “breaking down the silos” since modern native mobile development started. We’ve had ~15 years to figure it out, but still today, most teams have specialists.

Over those 15 years, there have been many people calling for “merging teams”. Blog posts, conference talks, occasionally real efforts. Every few years, the arguments about why now is the time get updated. Modern arguments focus on the similarity of Kotlin and Swift, or Compose and SwiftUI, but I don’t find them very compelling (again, will cover in another post). What the more recent arguments have in common with the older calls for merging teams is a logical case without demonstrable results. There’s no proof it’ll work (but we’ve talked to a few teams giving it a shot…)

I’m not saying you can’t, just that it’s risky.

I’m also saying, maybe specialists exist for a reason. Trying to generalize the teams might actually lead to negative outcomes.

Scaling doesn’t have to be hard

The overriding themes of the ”KMP For Native Mobile Teams” post series is that:

  • Professional approaches to any application of tech need appropriate context and nuance.
  • Native mobile apps with KMP, built by teams, will have very different needs when compared with Compose UI, desktop apps, or any situation where there are only 1-2 developers.
  • Having a plan, that doesn’t require radically altering how native mobile teams currently work, is a better approach.

KMP doesn’t replace the platform it runs it. It embraces it. Along similar lines, native mobile teams shouldn’t try to radically alter how they work. These team patterns have persisted for years, and assuming KMP will render them unnecessary is certainly a risk.

Embrace, don’t replace, how native mobile teams work.


¹ This assumption is pervasive. The default KMP setup involves all code, Android, iOS, and KMP, in a single repo. I made this assumption as well, leading up to my 2023 KotlinConf talk on KMP and teams (start at 25:48). I was trying to figure out workflow mitigations, but in a single repo model. After staring at this problem for several years, my current view seems obvious in retrospect. Let your teams continue working as they have been. There’s a reason native mobile teams mostly don’t work as a single unit. Details on that in a future post.