android-architecture
Architecture blueprint for Android. Enforces strict clean code principles and Hilt-based dependency injection.
Install
mkdir -p .claude/skills/android-architecture-huyhunhngc && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/10033" && unzip -o skill.zip -d .claude/skills/android-architecture-huyhunhngc && rm skill.zipInstalls to .claude/skills/android-architecture-huyhunhngc
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
A complete guide to building Android projects following Clean Architecture principles. Covers Hilt DI, Repository/UseCase patterns, type-safe Navigation, WorkManager, UI state management with StateFlow, CompositionLocals, and network setup with Ktorfit/Ktor.Key capabilities
- →Structure Android projects
- →Implement Hilt DI
- →Manage UI state with StateFlow
- →Setup Ktorfit network
- →Configure Room database
How it works
It follows Clean Architecture principles by separating data, domain, and presentation layers with Hilt DI.
Inputs & outputs
When to use android-architecture
- →Migrating to clean architecture
- →Setting up Hilt for Android
- →Structuring a feature module
About this skill
Android Clean Architecture Skill
This skill documents the exact architectural patterns used in this codebase. Follow these patterns precisely when building a new project or migrating an existing one.
Rules (from GEMINI.md)
- Nearly-clean architecture principles
- Hilt for dependency injection
- Jetpack Compose for the View layer
- No login required to use the app. Login is only required for sync / premium features.
- DO NOT ADD COMMENTS in any code you write.
1. Project Structure
app/src/main/java/com/<org>/<app>/
├── Application.kt ← @HiltAndroidApp entry point
├── MainActivity.kt ← @AndroidEntryPoint, RepositoryProvider.Provide{}
├── MainViewModel.kt ← App-level @HiltViewModel
├── ScannerApp.kt ← Root composable (theme + AppNavHost)
│
├── background/ ← WorkManager workers (@HiltWorker / @AssistedInject)
├── data/ ← Repository implementations, API interfaces, DataStore
├── database/ ← Room database, DAOs, entities
├── di/ ← Hilt modules (common + feature-specific)
│ ├── common/ ← App-wide modules (network, datastore, firebase, …)
│ └── <feature>/ ← Feature-scoped modules (e.g., DocumentRepositoryModule)
├── domain/ ← Repository interfaces + UseCases
│ ├── repository/ ← Kotlin interfaces only
│ └── usecase/ ← UseCases + domain models under usecase/model/
├── features/ ← Feature screens (composable routes + ViewModels)
├── glance/ ← App-widget Glance composables
├── model/ ← Shared data/request/response models
├── navigation/ ← AppNavHost, NavigationMethod extensions, NavHostWithSlideEffect
├── service/ ← NotificationHandler and similar services
├── ui/ ← Design system (theme, components, CompositionLocals)
│ ├── component/ ← Reusable UI components
│ ├── localcomposition/ ← All CompositionLocal definitions
│ └── theme/ ← Material3 theme, colors, typography
└── utils/ ← Extension functions, helpers (including UiStateBuilder.kt)
2. Application Entry Point
@HiltAndroidApp
class Application : Application(), Configuration.Provider {
@Inject lateinit var workerFactory: HiltWorkerFactory
@Inject lateinit var appConfigManager: AppConfigManager
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
override fun onCreate() {
super.onCreate()
SomeWorker.enqueue(this)
appConfigManager.fetchAndActivate()
}
}
Key points:
- Annotate with
@HiltAndroidApp. - Implement
Configuration.Providerand injectHiltWorkerFactoryso WorkManager uses Hilt. - Enqueue background workers and trigger remote-config fetches in
onCreate().
3. Dependency Injection (Hilt)
3.1 Hilt Components Used
| Component | When to use |
|---|---|
SingletonComponent | App-scoped singletons (network, DB, repos) |
ActivityComponent | Activity-scoped dependencies |
ViewModelComponent | ViewModel-scoped dependencies (via @HiltViewModel) |
@EntryPoint | Non-Hilt entry points (e.g., WorkManager workers) |
3.2 Module Convention
- Common modules go in
di/common/:NetworkModule,DataStoreModule,DatabaseModule,FirebaseModule,WorkerModule. - Feature-specific modules go in
di/<feature>/, e.g.,di/document/DocumentRepositoryModule. - Prefer
@InstallIn(SingletonComponent::class)for everything that is stateless or repository-level.
3.3 Qualifier Annotations
Define custom @Qualifier annotations alongside the module when multiple bindings of the same type exist:
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class SummarizationKtorQualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class FileStorageKtorQualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AppSettingsDataStoreQualifier
Use @Named("key") only for simple string bindings (e.g., base URLs).
3.4 Network Module (Ktorfit + Ktor)
@InstallIn(SingletonComponent::class)
@Module
class NetworkModule {
@Named("apiBaseUrl")
@Provides
fun provideBaseUrl(): String = BuildConfig.API_BASE_URL
@Provides
fun provideJson(): Json = defaultJson()
@Provides
fun provideHttpClient(json: Json): HttpClient = HttpClient(OkHttp) {
defaultKtorConfig(json)
}
@MyFeatureKtorQualifier
@Provides
fun provideMyFeatureKtorfit(
json: Json,
authDataStore: AuthDataStore,
@Named("apiBaseUrl") apiBaseUrl: String,
): Ktorfit = Ktorfit.Builder().httpClient(
HttpClient(OkHttp) {
defaultKtorConfig(json)
defaultRequest { contentType(ContentType.Application.Json) }
install(Auth) {
bearer {
loadTokens {
BearerTokens(
accessToken = authDataStore.getAccessToken(),
refreshToken = null,
)
}
}
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.BODY
}
expectSuccess = true
}
).baseUrl(apiBaseUrl).build()
}
Put a defaultKtorConfig(json: Json) extension function in di/common/DefaultKtorConfig.kt
for shared Ktor client configuration (content negotiation, timeouts, etc.).
3.5 DataStore Module
@Qualifier annotation class AppSettingsDataStoreQualifier
@InstallIn(SingletonComponent::class)
@Module
class DataStoreModule {
@AppSettingsDataStoreQualifier
@Provides
@Singleton
fun provideAppSettingsDataStore(
@ApplicationContext context: Context,
): DataStore<Preferences> = createDataStore(
coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
producePath = { context.cacheDir.resolve("app_settings.preferences_pb").path },
context = context,
)
}
3.6 Database Module
@InstallIn(SingletonComponent::class)
@Module
class DatabaseModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context: Context): AppDatabase =
Room.databaseBuilder(context, AppDatabase::class.java, "app.db").build()
@Provides
fun provideDocumentDao(db: AppDatabase): DocumentDao = db.documentDao()
}
3.7 Firebase Module
@InstallIn(SingletonComponent::class)
@Module
object FirebaseModule {
@Provides
@Singleton
fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig = Firebase.remoteConfig
}
3.8 Repository Module with Multibinding (for CompositionLocal)
Use abstract module + @Binds @IntoMap @ClassKey to register every repository in a
Map<Class<out Any>, Any>. This map feeds RepositoryProvider which is used to expose
repositories via CompositionLocal.
@Module
@InstallIn(SingletonComponent::class)
abstract class DocumentRepositoryModule {
@Binds
@RepositoryQualifier
@IntoMap
@ClassKey(DocumentRepository::class)
abstract fun bindDocumentRepository(repository: DocumentRepository): Any
companion object {
@Singleton
@Provides
fun provideDocumentRepository(
@MyFeatureKtorQualifier ktorfit: Ktorfit,
documentDao: DocumentDao,
authDataStore: AuthDataStore,
): DocumentRepository = DefaultDocumentRepository(
documentDao = documentDao,
summarizeApi = ktorfit.createSummarizeApi(),
authDataStore = authDataStore,
)
}
}
4. Domain Layer
4.1 Repository Interface
Located in domain/repository/. Contains only the contract — no implementation.
Each interface file also exports a @Composable accessor using LocalRepositories:
interface DocumentRepository {
suspend fun addDocument(document: Document): Long
fun getAllDocuments(): Flow<List<Document>>
suspend fun deleteDocument(documentId: Long)
fun getAllDocumentsPagingFlow(userId: String?): Flow<PagingData<DomainDocument>>
}
@Composable
fun localDocumentRepository(): DocumentRepository {
return LocalRepositories.current[DocumentRepository::class] as DocumentRepository
}
The @Composable accessor is what the UI layer uses to obtain the repository without
needing Hilt injection in the composable (see §7 for full explanation).
4.2 UseCase
Located in domain/usecase/. Injected with @Inject constructor. No annotations other
than @Inject. Coordinates multiple repositories.
class PersistDocumentUseCase @Inject constructor(
private val documentRepository: DocumentRepository,
private val fileStorageRepository: FileStorageRepository,
private val historyActivityRepository: HistoryActivityRepository,
@ApplicationContext private val context: Context,
private val authDataStore: AuthDataStore,
) {
suspend fun saveDocumentPdf(title: String, pdfFileUri: Uri): Long {
val document = Document(
title = title.ifBlank { "Untitled" },
fileUri = pdfFileUri.toString(),
)
return documentRepository.addDocument(document)
}
}
Rules:
- Only UseCases contain business logic that crosses domain boundaries.
- Repositories are injected into UseCases, never directly into ViewModels (prefer UseCase).
- ViewModels may inject repositories directly only for simple read-only flows.
4.3 Domain Models
Located in domain/usecase/model/. These are lightweight projections of DB entities:
data class DomainDocument(
val id:
---
*Content truncated.*
When not to use it
- →Adding comments to code
Prerequisites
Limitations
- →No login required for core features
- →DO NOT ADD COMMENTS
How it compares
It enforces a strict, comment-free architectural standard for Android projects.
Compared to similar skills
android-architecture side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| android-architecture (this skill) | 0 | 5mo | No flags | Advanced |
| kotlin-multiplatform | 32 | 3mo | Review | Advanced |
| android-clean-architecture | 0 | 1mo | No flags | Intermediate |
| mobile-architect-agent | 0 | 1mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
kotlin-multiplatform
vitorpamplona
Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific, source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues. Triggers on: abstraction decisions ("should I share this?"), source set placement questions, expect/actual creation, build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.
android-clean-architecture
farhankabir133
Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns.
mobile-architect-agent
srednoff888-art
Agent profile for coordinate mobile app architecture across iOS, Android, Expo/React Native, offline states, permissions, and releases. Use when Codex needs a specialist agent perspective for planning, implementation, review, debugging, validation, or handoff in this domain.
android-kotlin-development
aj-geddes
Develop native Android apps with Kotlin. Covers MVVM with Jetpack, Compose for modern UI, Retrofit for API calls, Room for local storage, and navigation architecture.
android-kotlin
alinaqi
Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing
testing-android-code
bitwarden
This skill should be used when writing or reviewing tests for Android code in Bitwarden. Triggered by "BaseViewModelTest", "BitwardenComposeTest", "BaseServiceTest", "stateEventFlow", "bufferedMutableSharedFlow", "FakeDispatcherManager", "expectNoEvents", "assertCoroutineThrows", "createMockCipher", "createMockSend", "asSuccess", "Why is my Bitwarden test failing?", or testing questions about ViewModels, repositories, Compose screens, or data sources in Bitwarden.