EngineeringSeptember 10, 2026

Why we run four marketplace brands on one Kotlin Multiplatform codebase

NailMe, Craft, Hair and Tattoo look and behave like four separate apps. Under the hood, they're four build variants of the same Kotlin Multiplatform + Compose Multiplatform codebase. Here's how that's structured.

One codebase, four variants

Each brand — nails, tattoo, craft, hair — is a build variant with its own package name (qubin.de.{variant}), Firebase project, keystore and theme. Switching between them is a single script: ./ci/set_variant.sh nailsDevelopment copies the right config into place before any build. There's no forked repository per brand and no copy-pasted feature code to keep in sync.

MVVM and a strict unidirectional data flow

Every feature follows the same shape: UiEvent → ViewModel → Repository → UiState. The UI never mutates state directly — it dispatches events, the ViewModel talks to a repository, and a new UI state flows back down. That consistency is what makes it possible for four different product teams' worth of features to live in one codebase without stepping on each other.

Feature modules, not a monolith

Booking, chat, portfolios, reviews — each is its own self-contained module with its own UI, ViewModel, dependency injection and navigation graph, wired together with Koin. When we build Craft's booking flow, it's the same feature-booking module NailMe already ships in production, configured with craft-specific categories instead of nail styles. Improvements to booking reliability made for one brand reach all four the next release.

Compose Multiplatform for one UI layer

UI code lives in commonMain and targets both Android and iOS through Compose Multiplatform, with androidMain/iosMain reserved for the platform APIs that genuinely need it — camera access, keychain storage, push notification wiring. The result is a shared UI layer instead of two parallel native UIs that drift apart over time.

Shipping all four variants safely

A GitLab CI pipeline runs the same three stages — build, UI test, distribute — for every variant. Automated UI tests run against a Firebase emulator suite before anything reaches Firebase App Distribution or TestFlight, so a change made for one brand can't silently break another.

Why this matters beyond engineering

Practically, it means Craft and Hair aren't starting from a blank page — they inherit a booking and chat system that's already been proven in production by NailMe's real users. New product ideas get tested where the risk is lowest and shipped everywhere the platform already runs.

The same discipline applies to how we ship, not just what we ship: our GitLab CI pipeline is where we're introducing AI-assisted steps, under the credential architecture described in how we let AI agents ship our apps without ever seeing a secret.

Building something similar?

We work with teams who want to consolidate Android and iOS into one Kotlin Multiplatform codebase — the same engineering that runs our own product family.

Talk to us