Project architecture
Skill thetruong1099/android-mvi-base-code/.claude/skills/project-architecture
Module structure, dependency rules, and naming conventions for the Android MVI Template. Use when creating new feature or domain modules, understanding which modules can depend on which, choosing convention plugins for build.gradle.kts, following naming conventions for ViewModels/UseCases/Repositories/Screens, or adding screens to the navigation graph.From its SKILL.md
npx -y skills add thetruong1099/android-mvi-base-code --skill project-architectureAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.0 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it
Project Architecture
Module Dependency Graph
app
├── feature:navigation-graph
│ ├── feature:sample
│ └── feature:core (shared by all features)
├── domain:usecase
│ ├── domain:repository (interfaces only)
│ ├── domain:model (pure data classes)
│ └── domain:core (BaseUseCase, DataState, AppError)
└── data:remote-data / data:local-data
└── data:core (BaseDataSource, BaseMapper, ExceptionMapper)
└── domain:model
Strict Dependency Rules
- Feature modules depend on:
feature:core,domain:model,domain:usecase - Feature modules NEVER depend on:
data:*, otherfeature:*modules - Domain modules are 100% pure Kotlin (NO
android.*imports) - Data modules depend on:
domain:repository,domain:model,domain:core,data:core feature:coreis the ONLY feature module that provides shared base classes
Convention Plugins (build_logic/)
| Plugin ID | Purpose | Apply to |
|---|---|---|
android.application | App module config | :app |
android.application.compose | Compose for app | :app |
android.feature | Base feature module | feature:* |
android.feature.compose | Feature + Compose + Hilt | feature:* |
android.library | Library module | domain:*, data:* |
android.hilt | Hilt DI setup | modules needing DI |
android.firebase | Firebase config | modules using Firebase |
android.test.unit | JUnit + MockK + Coroutine test | all modules |
android.test.instrumentation | AndroidJUnit4 + Compose test | feature modules |
android.test.robolectric | Robolectric setup | domain/data modules |
Creating a New Feature Module
- Copy
feature/sample/as a template - Add to
settings.gradle.kts:include(":feature:new-feature") - Create
build.gradle.kts:
plugins {
alias(libs.plugins.android.feature.compose)
// alias(libs.plugins.android.test.unit) // Add if tests needed
}
dependencies {
implementation(projects.domain.model)
implementation(projects.domain.usecase)
}
- Create files:
XxxViewModel.kt,XxxScreen.kt,XxxNavigation.kt - Register
NavGraphBuilder.xxxScreen()infeature:navigation-graph - Add
implementation(projects.feature.newFeature)inapp/build.gradle.kts
Creating a New UseCase
domain/usecase/src/main/java/com/template/domain/
├── usecase/newfeature/
│ ├── GetNewFeatureUseCase.kt (interface)
│ └── GetNewFeatureUseCaseImpl.kt (implementation)
└── di/
└── NewFeatureUseCaseModule.kt (Hilt @Module with @Binds)
// Interface - extends appropriate base
interface GetXxxUseCase : BaseFlowUseCaseNoParams<Flow<PagingData<Xxx>>>
// Implementation - @Inject constructor
class GetXxxUseCaseImpl @Inject constructor(
private val repository: XxxRepository
) : GetXxxUseCase {
override fun invoke(): Flow<PagingData<Xxx>> = repository.getXxx()
}
// DI Module - @Binds interface -> impl
@Module
@InstallIn(SingletonComponent::class)
interface XxxUseCaseModule {
@Binds fun bindGetXxxUseCase(impl: GetXxxUseCaseImpl): GetXxxUseCase
}
UseCase Base Types
| Type | When to use |
|---|---|
BaseSuspendUseCase<T: IParams, R> | One-shot with params |
BaseSuspendUseCaseNoParams<R> | One-shot without params |
BaseFlowUseCase<T: IParams, R> | Streaming with params |
BaseFlowUseCaseNoParams<R> | Streaming without params |
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| ViewModel | XxxViewModel | SampleViewModel |
| State | XxxViewState | SampleViewState |
| Event | XxxViewEvent | SampleViewEvent |
| Effect | XxxViewEffect | SampleViewEffect |
| Screen | XxxScreen / XxxScreenInternal | SampleScreen |
| Navigation | XxxNavigation.kt + NavGraphBuilder.xxxScreen() | sampleScreen() |
| UseCase | VerbNounUseCase + VerbNounUseCaseImpl | GetSampleItemsUseCase |
| Mapper | XxxMapper | ItemMapper |
| Repository | XxxRepository (interface) + XxxRepositoryImpl | ItemRepository |
Hilt DI Organization
domain/usecase/di/ -> UseCaseModules (@Binds interface -> impl)
data/remote-data/di/ -> RepositoryModules, DataSourceModules, MapperModules
data/local-data/di/ -> DatabaseModule, DaoModule, DataStoreModule
app/di/ -> AppModule (singletons, app-level bindings)
Data Layer Strategy Pattern
BaseDataSource
├── fetchData() -> FirebaseDataSourceStrategy (Firebase Task<T> -> Flow<DataState<T>>)
├── fetchRestData() -> RetrofitDataSourceStrategy (Response<BaseDto<T>> -> Flow<DataState<T>>)
├── fetchPagingData() -> PagingDataSourceStrategy (Pager -> Flow<PagingData<T>>)
└── requestData() -> Direct sync wrapper
Error Flow
Infrastructure Exception
↓ ExceptionMapper (data:core)
AppError sealed class (domain:core)
↓ DataState.Error or caught in FlowCollectionManager
ErrorHandler.getErrorMessageResId() (feature:core)
↓
String resource ID -> Toast
Build Flavors
Three product flavors for environment configuration:
| Flavor | Purpose | Properties file |
|---|---|---|
dev | Development | env/dev.properties |
staging | QA / Staging | env/staging.properties |
production | Production release | env/production.properties |
Build variant format: assembleDevDebug, assembleStagingRelease, assembleProductionRelease
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.