AN

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.zip

Installs 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.
258 charsno explicit “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

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

You give it
Project requirements
You get back
Architectural structure

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.Provider and inject HiltWorkerFactory so WorkManager uses Hilt.
  • Enqueue background workers and trigger remote-config fetches in onCreate().

3. Dependency Injection (Hilt)

3.1 Hilt Components Used

ComponentWhen to use
SingletonComponentApp-scoped singletons (network, DB, repos)
ActivityComponentActivity-scoped dependencies
ViewModelComponentViewModel-scoped dependencies (via @HiltViewModel)
@EntryPointNon-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

HiltJetpack Compose

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.

SkillInstallsUpdatedSafetyDifficulty
android-architecture (this skill)05moNo flagsAdvanced
kotlin-multiplatform323moReviewAdvanced
android-clean-architecture01moNo flagsIntermediate
mobile-architect-agent01moNo flagsIntermediate

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.

32156

android-clean-architecture

farhankabir133

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns.

00

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.

00

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.

268679

android-kotlin

alinaqi

Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing

729

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.

611

Search skills

Search the agent skills registry