A full-stack expense splitting application built with Kotlin Multiplatform
KesiCollection is an expense management and bill-splitting application with support for Android, iOS, Desktop, and a backend server. The project uses Clean Architecture, modular design, and Kotlin Multiplatform to maximize code sharing across all platforms.
- π° Expense Splitting - Create groups, track payments, and automatically calculate who owes what
- π₯ Group Management - Create and manage expense groups with multiple members
- π Settlement Tracking - View detailed payment history and outstanding balances
- π Authentication - Secure JWT-based authentication system
- π« Friend Management - Connect with friends to simplify group creation
- π± Multi-Platform - Runs on Android, iOS, and Desktop with shared business logic
The application follows Clean Architecture principles with clear separation of concerns:
βββ domain/ # Business logic and use cases (platform-agnostic)
βββ data/ # Data sources, repositories, and API clients
βββ feature/ # Feature-specific UI and ViewModels
βββ core/ # Shared utilities and design system
βββ server/ # Backend REST API
The codebase is organized into 55+ focused modules, each with a single responsibility. The architecture uses umbrella modules to enable multiple applications to share common infrastructure:
- Core Modules: Shared across all applications (design system, infrastructure, models, Redux state management)
- Feature Modules: Self-contained features that can be composed into different applications
- Data Modules: API clients, local database, repositories
- Domain Modules: Business logic and use cases
- Application Modules: Umbrella modules that compose features and core modules into complete applications
The project is designed to support multiple applications sharing the same core infrastructure:
kesiSplit/ # Expense splitting application (Android/iOS/Desktop)
kesiSplitUmbrella/ # Umbrella module for KesiSplit
feature/ # Generic reusable features
featurekesisplit/ # Expense splitting specific features
core/ # Shared across ALL applications
βββ designsystem/ # UI components, theme, typography
βββ infrastructure/ # Network, storage, utilities
βββ model/ # Shared data models
βββ redux/ # State management framework
This umbrella approach allows:
- β Multiple apps with shared UI/UX - Different apps can use the same design system
- β Code reuse at scale - Core modules are shared across all applications
- β Independent feature development - Features can be developed and tested in isolation
- β Flexible composition - Applications cherry-pick which features to include
- β Consistent architecture - All apps follow the same patterns and structures
Frontend (Mobile & Desktop)
- Kotlin Multiplatform Mobile (KMM) - Share 80%+ code between Android, iOS, and Desktop
- Compose Multiplatform - Modern declarative UI framework
- Redux Pattern - Predictable unidirectional state management
- Voyager - Type-safe navigation
- Room Database - Local persistence with SQLite
- Koin - Dependency injection
- Coroutines & Flow - Asynchronous programming
- Ktor Client - HTTP networking
Backend (Server)
- Ktor - Asynchronous web framework
- Exposed ORM - Type-safe SQL with Kotlin DSL
- PostgreSQL - Production database
- JWT Authentication - Secure token-based auth
- Koin - Dependency injection
Build & DevOps
- Gradle Convention Plugins - Standardized build configuration across 55+ modules
- Kotlin DSL - Type-safe build scripts
- Module Graph Plugin - Visualize module dependencies
- Docker Compose - Local development environment
- β Android (API 24+)
- β iOS (iOS 14+)
- β Desktop (JVM - macOS, Windows, Linux)
- β Server (JVM)
- JDK 17 or higher
- Android Studio Hedgehog (2023.1.1) or newer
- Xcode 15+ (for iOS development)
- Docker & Docker Compose (for backend)
./gradlew :kesiSplit:assembleDebug
# Or open in Android Studio and run# Build the Kotlin framework
./gradlew :kesiSplit:linkDebugFrameworkIosSimulatorArm64
# Open iosApp/iosApp.xcodeproj in Xcode and run./gradlew :kesiSplit:rundocker-compose up db # Start PostgreSQL database
./gradlew :server:run # Run the server- Start a PostgreSQL database
- Configure connection in
server/src/main/resources/application.conf - Run:
./gradlew :server:run
The server will start at http://localhost:8080
# Run all tests
./gradlew test
# Run specific module tests
./gradlew :featurekesisplit:home:testDebugUnitTest
./gradlew :server:testkesicollection/
β
βββ build-logic/ # Custom Gradle convention plugins
β βββ convention/ # Reusable build configuration
β
βββ core/ # Core shared modules
β βββ designsystem/ # UI components, theme, typography
β βββ infrastructure/ # Network, storage, utilities
β βββ model/ # Shared data models
β βββ redux/ # State management framework
β
βββ domain/ # Business logic (platform-agnostic)
β βββ authentication/ # Auth use cases
β βββ datetime/ # Date/time utilities
β βββ misc/ # Shared domain logic
β
βββ data/ # Data layer
β βββ api/ # API models
β βββ authentication/ # Auth repository
β βββ exposed/ # Database ORM
β βββ friendship/ # Friend management data
β βββ ktor/ # HTTP client setup
β βββ repository/ # Data repositories
β βββ room/ # Local database
β βββ server/ # Server data layer
β
βββ feature/ # Generic features
β βββ authentication/ # Login/register screens
β βββ friendlookup/ # Friend search
β βββ getstarted/ # Onboarding
β βββ nomms/ # Additional features
β
βββ featurekesisplit/ # Expense splitting features
β βββ home/ # Main dashboard
β βββ splits/ # List of splits
β βββ split/ # Split detail view
β βββ createsplit/ # Create new split
β βββ creategroup/ # Create expense group
β βββ addpayment/ # Add payment to split
β βββ submitpayment/ # Submit payment confirmation
β βββ paymentdetail/ # Payment details view
β βββ splitdetail/ # Split details view
β βββ splitbalance/ # Balance calculations
β βββ deletesettlement/ # Delete settlement
β βββ groupmembersselection/ # Select group members
β βββ createanonymousmember/ # Add non-registered members
β
βββ kesiSplit/ # Main mobile application (umbrella for Android/iOS/Desktop)
βββ kesiSplitUmbrella/ # Umbrella module composing core + features
βββ iosApp/ # iOS-specific code
βββ server/ # Backend Ktor application
βββ docker-server/ # Docker configuration
The build system uses custom convention plugins to standardize configuration across all 55+ modules:
KMPLibraryConventionPlugin- Base configuration for Kotlin Multiplatform librariesKMPLibraryComposeConventionPlugin- Compose Multiplatform UI librariesFeatureConventionPlugin- Feature module configuration with common dependenciesKMPApplicationConventionPlugin- Application module setupKtorConventionPlugin- Server and client Ktor configuration
This approach eliminates duplication in build files and ensures consistency across the entire codebase.
Each module explicitly declares its dependencies in build.gradle.kts, creating a directed acyclic graph (DAG) of dependencies. The module graph visualization below shows how core modules are depended upon by all features, while features remain independent of each other.
Key architectural constraints:
- Core modules have no dependencies on feature or data modules
- Domain modules are pure Kotlin with no platform dependencies
- Feature modules depend only on core and domain modules
- Data modules bridge domain and platform-specific implementations
The following diagram shows the dependency relationships between core modules:
%%{
init: {
'theme': 'neutral'
}
}%%
graph LR
subgraph :common
:common:component["component"]
end
subgraph :core
:core:infrastructure["infrastructure"]
:core:designsystem["designsystem"]
:core:redux["redux"]
:core:model["model"]
end
subgraph :data
:data:api["api"]
:data:server["server"]
end
subgraph :data:client
:data:client:friendship["friendship"]
:data:client:authentication["authentication"]
:data:client:ktor["ktor"]
end
subgraph :datakesisplit
:datakesisplit:transaction["transaction"]
:datakesisplit:split["split"]
:datakesisplit:splitgroup["splitgroup"]
:datakesisplit:api["api"]
end
subgraph :domain
:domain:authentication["authentication"]
:domain:misc["misc"]
end
subgraph :feature
:feature:authentication["authentication"]
:feature:friendlookup["friendlookup"]
:feature:getstarted["getstarted"]
:feature:nomms["nomms"]
end
subgraph :featurekesisplit
:featurekesisplit:deletesettlement["deletesettlement"]
:featurekesisplit:groupmembersselection["groupmembersselection"]
:featurekesisplit:addpayment["addpayment"]
:featurekesisplit:creategroup["creategroup"]
:featurekesisplit:paymentdetail["paymentdetail"]
:featurekesisplit:split["split"]
:featurekesisplit:splits["splits"]
:featurekesisplit:createanonymousmember["createanonymousmember"]
:featurekesisplit:createsplit["createsplit"]
:featurekesisplit:home["home"]
:featurekesisplit:splitbalance["splitbalance"]
:featurekesisplit:splitdetail["splitdetail"]
:featurekesisplit:submitpayment["submitpayment"]
end
:domain:authentication --> :core:infrastructure
:featurekesisplit:deletesettlement --> :core:designsystem
:featurekesisplit:deletesettlement --> :core:redux
:featurekesisplit:groupmembersselection --> :core:designsystem
:featurekesisplit:groupmembersselection --> :core:redux
:common:component --> :core:model
:common:component --> :core:designsystem
:feature:authentication --> :core:designsystem
:feature:authentication --> :core:redux
:featurekesisplit:addpayment --> :core:designsystem
:featurekesisplit:addpayment --> :core:redux
:data:client:friendship --> :core:infrastructure
:datakesisplit:transaction --> :core:infrastructure
:featurekesisplit:creategroup --> :core:designsystem
:featurekesisplit:creategroup --> :core:redux
:featurekesisplit:paymentdetail --> :core:designsystem
:featurekesisplit:paymentdetail --> :core:redux
:kesiSplitUmbrella --> :core:designsystem
:datakesisplit:split --> :core:infrastructure
:featurekesisplit:split --> :core:designsystem
:featurekesisplit:split --> :core:redux
:datakesisplit:splitgroup --> :core:infrastructure
:data:api --> :core:model
:featurekesisplit:splits --> :core:designsystem
:featurekesisplit:splits --> :core:redux
:datakesisplit:api --> :core:model
:feature:friendlookup --> :core:designsystem
:feature:friendlookup --> :core:redux
:data:client:authentication --> :core:infrastructure
:featurekesisplit:createanonymousmember --> :core:designsystem
:featurekesisplit:createanonymousmember --> :core:redux
:feature:getstarted --> :core:designsystem
:feature:getstarted --> :core:redux
:featurekesisplit:createsplit --> :core:designsystem
:featurekesisplit:createsplit --> :core:redux
:featurekesisplit:home --> :core:designsystem
:featurekesisplit:home --> :core:redux
:data:server --> :core:infrastructure
:core:infrastructure --> :domain:misc
:feature:nomms --> :core:designsystem
:feature:nomms --> :core:redux
:featurekesisplit:splitbalance --> :core:designsystem
:featurekesisplit:splitbalance --> :core:redux
:featurekesisplit:splitdetail --> :core:designsystem
:featurekesisplit:splitdetail --> :core:redux
:featurekesisplit:submitpayment --> :core:designsystem
:featurekesisplit:submitpayment --> :core:redux
:data:client:ktor --> :core:model
classDef focus fill:#769566,stroke:#fff,stroke-width:2px,color:#fff;
class :core:infrastructure focus
class :core:designsystem focus
class :core:redux focus
class :core:model focus
- Module Graph Plugin - Visualizes module dependencies (see graph above)
- Gradle Convention Plugins - Standardizes build configuration
- Type-Safe Project Accessors - Compile-time safe module references
- Gradle Build Cache - Speeds up incremental builds
Suggestions and feedback are welcome! Feel free to:
- Open issues for bugs or suggestions
- Submit pull requests
- Provide architectural feedback
This project is open source and available for reference and learning purposes.
Built with β€οΈ using Kotlin Multiplatform