Based on NowInAndroid's multi-module architecture strategy.
- Scalability: Changes in one module don't cascade to others
- Parallel development: Teams work independently on different modules
- Build speed: Incremental builds only recompile changed modules
- Reusability: Core modules can be shared across apps
- Encapsulation: Clear boundaries and visibility controls
- Testability: Each module can be tested in isolation
Entry point that brings everything together.
Contains:
MainActivityNiaAppcomposableNiaNavHost- app-level navigationTopLevelDestination- bottom nav setup
Dependencies: All feature modules, required core modules
Self-contained features with two submodules:
feature/
└── topic/
├── api/ # Public navigation keys
└── impl/ # Internal implementation
api module contains:
- Navigation keys and route definitions
- Public navigation functions
impl module contains:
- Screen composables
- ViewModels
- UI state classes
- Hilt modules
- Internal navigation setup
Shared library code used across features.
| Module | Purpose | Key Classes |
|---|---|---|
core:model |
Domain models (pure Kotlin) | Topic, NewsResource, UserData |
core:data |
Repositories, data coordination | TopicsRepository, NewsRepository |
core:database |
Room database, DAOs, entities | NiaDatabase, TopicDao, TopicEntity |
core:network |
Retrofit API, network models | RetrofitNiaNetwork, NetworkTopic |
core:datastore |
Proto DataStore preferences | NiaPreferencesDataSource |
core:common |
Shared utilities | NiaDispatchers, Result |
core:ui |
Reusable UI components | NewsFeed, NewsResourceCard |
core:designsystem |
Theme, icons, base components | NiaTheme, NiaIcons, NiaButton |
core:testing |
Test utilities, test doubles | TestDispatcherRule, TestRepository |
| Module | Purpose |
|---|---|
sync |
WorkManager sync implementation |
benchmark |
Performance testing |
app-nia-catalog |
Design system showcase app |
feature/settings/
├── api/
│ ├── build.gradle.kts
│ └── src/main/kotlin/
│ └── com/example/feature/settings/api/
│ └── SettingsNavigation.kt # Navigation keys
└── impl/
├── build.gradle.kts
└── src/main/kotlin/
└── com/example/feature/settings/impl/
├── SettingsScreen.kt # UI
├── SettingsViewModel.kt # State holder
├── SettingsUiState.kt # State model
├── SettingsNavigation.kt # Route setup
└── di/
└── SettingsModule.kt # Hilt bindings
core/data/
├── build.gradle.kts
└── src/
├── main/kotlin/
│ └── com/example/core/data/
│ ├── repository/
│ │ ├── TopicsRepository.kt # Interface
│ │ └── OfflineFirstTopicsRepository.kt # Implementation
│ ├── model/
│ │ └── Topic.kt # If needed
│ └── di/
│ └── DataModule.kt # Hilt bindings
└── test/kotlin/
└── com/example/core/data/
└── repository/
└── TestTopicsRepository.kt # Test double
app ──────────────► feature:*:impl
feature:*:api
core:*
feature:*:impl ───► feature:*:api (other features)
core:*
feature:*:api ────► core:model (only)
core:data ────────► core:database
core:network
core:model
core:datastore
core:database ────► core:model
core:network ─────► core:model
core:ui ──────────► core:model
core:designsystem
core:designsystem ► (no core dependencies)
core:model ───────► (no dependencies - pure Kotlin)
- Feature → Feature impl: Features must not depend on other feature implementations
- Core → Feature: Core modules never depend on features
- Core → App: Core modules never depend on app module
- Model → Anything: Model module has no dependencies
feature/myfeature/api/build.gradle.kts:
plugins {
alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.hilt)
}
android {
namespace = "com.example.feature.myfeature.api"
}
dependencies {
api(projects.core.model)
}SettingsNavigation.kt:
package com.example.feature.settings.api
import androidx.navigation.NavController
const val SETTINGS_ROUTE = "settings"
fun NavController.navigateToSettings() {
navigate(SETTINGS_ROUTE)
}feature/myfeature/impl/build.gradle.kts:
plugins {
alias(libs.plugins.nowinandroid.android.feature)
alias(libs.plugins.nowinandroid.android.library.compose)
}
android {
namespace = "com.example.feature.myfeature.impl"
}
dependencies {
api(projects.feature.myfeature.api)
implementation(projects.core.data)
implementation(projects.core.ui)
implementation(projects.core.designsystem)
}core/mycore/build.gradle.kts:
plugins {
alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.hilt)
}
android {
namespace = "com.example.core.mycore"
}
dependencies {
implementation(projects.core.model)
implementation(projects.core.common)
}include(":feature:myfeature:api")
include(":feature:myfeature:impl")
include(":core:mycore")graph TB
subgraph :core
:core:data
:core:database
:core:model
:core:network
:core:ui
end
subgraph :feature
:feature:topic:impl
:feature:topic:api
:feature:foryou:impl
:feature:foryou:api
end
:app --> :feature:topic:impl
:app --> :feature:foryou:impl
:feature:topic:impl -.-> :core:data
:feature:topic:impl -.-> :core:ui
:feature:topic:impl --> :feature:topic:api
:feature:foryou:impl -.-> :feature:topic:api
:core:data --> :core:database
:core:data --> :core:network
:core:database --> :core:model
:core:network --> :core:model
:core:ui --> :core:model
- Low coupling, high cohesion: Similar code in same module, minimal dependencies between modules
- Use convention plugins: Extract common build logic to
build-logic/ - Avoid over-modularization: Start simple, split when needed
- Keep core:model pure: No Android dependencies, just data classes
- Feature isolation: Features communicate only through navigation, not direct calls
- Test each module: Every module should have its own test suite