FLIP: Modular Architecture for KMP

FLIP: Modular Architecture for KMP

posted 2 min read

While building haskcore - an IDE for Haskell on Compose Desktop - I extracted rules that allowed me to create an architecture of 40 isolated modules, where each module knows only what it needs.

That's how FLIP - Feature-Layered Isolated Platform - was born.

The main problem I faced during development: horizontal dependencies make the system fragile. The solution - strict vertical hierarchy.

FLIP layers

FLIP consists of the following isolated modules:

  • :common:core - common logic components.
  • :common:presentation - common UI components.
  • :service - UI-less domain module. Provides a service interface.
interface SeedService : AutoCloseable {
    val seed: Flow<Seed>
    suspend fun generateSeed(): Either<Throwable, Unit>
}
  • :feature:core — logic module of a UI-feature. Knows nothing about other features. Uses services from :service. Provides use cases.
class GenerateRemoteRandomSeed(private val seedService: SeedService) : UseCase.Action {
    override suspend fun Raise<Throwable>.action() = seedService.generateSeed().bind()
}
  • :feature:presentation — UI module of a UI-feature. Knows only about its :feature:core and uses its use cases. Provides the view. Knows only about its own :feature:core.

  • :entrypoint — module where dependency configuration and composition of views from :feature:presentation into a single Composable happens.

  • :platform — module that initializes the Composable provided by :entrypoint for each platform.

FLIP dependencies

Arrows show the direction of dependencies.

How to structure your system?

Decide which features your app should have, then determine whether each feature has a UI:

  • If it does — it’s a feature.
  • If it doesn’t — it’s a service.
  • If one feature’s logic is needed by another — extract it into a separate service.

Important: both :service and :feature:core contain domain logic:

  • :service — core domain.
  • :feature:core — presentation domain (use cases).

Wrong:

  • :service:foo
  • :feature:foo:core
  • :feature:foo:presentation

Right:

  • :service:foo
  • :feature:bar:core
  • :feature:bar:presentation

Navigation in FLIP is just another feature. It aggregates and routes views passed as lambda slots, keeping it independent from other features.

NavigationView(
    splash = {
        SplashView(applicationScope = applicationScope)
    },
    profile = {
        ProfileView(applicationScope = applicationScope)
    }
)

This approach lets you add new screens without touching existing features.

A complete example is available in the official GitHub repository.

4 Comments

1 vote
0
0 votes
1

More Posts

JetBrains Just Changed KMP Structure. Here's What They Didn't Tell You.

numq - May 18

Type-safe Kotlin Multiplatform i18n: auto-convert Android strings to cross-platform translations.

Ismoy - Oct 14, 2025

ImagePickerKMP 1.0.23: Controling Camera Launch on iOS with directCameraLaunch

Ismoy - Sep 3, 2025

Optimizing the Clinical Interface: Data Management for Efficient Medical Outcomes

Huifer - Jan 26

The Hidden Program Behind Every SQL Statement

lovestaco - Apr 11
chevron_left

Commenters (This Week)

3 comments
2 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!