{"id":"7f197fcf-526b-4514-8eb6-2cb3eb220108","shortId":"vt3AxB","kind":"skill","title":"android-architecture","tagline":"27 Android skills for AI agents (Claude Code, Codex, Cursor). Fixes Supabase auth, Hilt errors, design inconsistency, kapt→ksp, missing UiState states. Reduced my token bills 5×. FitGenZ AI shipped in 18 days.","description":"# Android Architecture\r\n\r\nMVVM + Clean Architecture with Unidirectional Data Flow. These patterns prevent the most common\r\nstructural mistakes AI agents make when building Android apps.\r\n\r\n## The three layers — strict separation\r\n\r\n```\r\nPresentation (UI)          →  only depends on Domain\r\n    ViewModel              →  calls UseCases, exposes UiState + Events\r\n    Composables            →  observes ViewModel, sends user actions\r\n\r\nDomain (Business Logic)    →  pure Kotlin, zero Android dependencies\r\n    UseCases               →  orchestrate one business operation\r\n    Repository interfaces  →  contracts, implemented in Data layer\r\n    Domain models          →  pure data classes\r\n\r\nData                       →  implements Domain interfaces\r\n    Repository impls       →  coordinate local + remote sources\r\n    Remote DataSource      →  Retrofit API calls\r\n    Local DataSource       →  Room DAO calls\r\n    DTOs / Mappers         →  never expose DTOs to Domain\r\n```\r\n\r\n**Rule:** Domain layer must never import `android.*`. If it does, the architecture is broken.\r\n\r\n## Package structure\r\n\r\n```\r\ncom.company.app/\r\n├── di/                        ← Hilt modules only\r\n│   ├── AppModule.kt\r\n│   └── NetworkModule.kt\r\n├── ui/\r\n│   ├── theme/\r\n│   ├── navigation/\r\n│   │   └── AppNavGraph.kt\r\n│   └── feature/\r\n│       └── home/\r\n│           ├── HomeScreen.kt      ← Composable\r\n│           ├── HomeViewModel.kt\r\n│           └── HomeUiState.kt\r\n├── domain/\r\n│   ├── model/\r\n│   │   └── Item.kt               ← pure data class\r\n│   ├── repository/\r\n│   │   └── ItemRepository.kt     ← interface\r\n│   └── usecase/\r\n│       └── GetItemsUseCase.kt\r\n└── data/\r\n    ├── repository/\r\n    │   └── ItemRepositoryImpl.kt\r\n    ├── remote/\r\n    │   ├── ItemApiService.kt\r\n    │   └── dto/\r\n    │       └── ItemDto.kt\r\n    └── local/\r\n        ├── ItemDao.kt\r\n        └── entity/\r\n            └── ItemEntity.kt\r\n```\r\n\r\n## ViewModel — complete pattern\r\n\r\n```kotlin\r\n@HiltViewModel\r\nclass HomeViewModel @Inject constructor(\r\n    private val getItems: GetItemsUseCase,\r\n    private val toggleFavorite: ToggleFavoriteUseCase\r\n) : ViewModel() {\r\n\r\n    private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)\r\n    val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()\r\n\r\n    private val _events = MutableSharedFlow<HomeEvent>()\r\n    val events: SharedFlow<HomeEvent> = _events.asSharedFlow()\r\n\r\n    init {\r\n        loadItems()\r\n    }\r\n\r\n    fun loadItems() {\r\n        viewModelScope.launch {\r\n            _uiState.value = HomeUiState.Loading\r\n            getItems()\r\n                .onSuccess { items ->\r\n                    _uiState.value = if (items.isEmpty()) HomeUiState.Empty\r\n                    else HomeUiState.Success(items)\r\n                }\r\n                .onFailure { error ->\r\n                    _uiState.value = HomeUiState.Error(error.message ?: \"Unknown error\")\r\n                }\r\n        }\r\n    }\r\n\r\n    fun onItemClick(itemId: String) {\r\n        viewModelScope.launch {\r\n            _events.emit(HomeEvent.NavigateToDetail(itemId))\r\n        }\r\n    }\r\n\r\n    fun onFavoriteClick(itemId: String) {\r\n        viewModelScope.launch {\r\n            toggleFavorite(itemId)\r\n                .onFailure { _events.emit(HomeEvent.ShowError(\"Failed to update favorite\")) }\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## UiState + UiEvent — define them together\r\n\r\n```kotlin\r\nsealed interface HomeUiState {\r\n    data object Loading : HomeUiState\r\n    data object Empty : HomeUiState\r\n    data class Success(val items: List<Item>) : HomeUiState\r\n    data class Error(val message: String) : HomeUiState\r\n}\r\n\r\nsealed interface HomeEvent {\r\n    data class NavigateToDetail(val itemId: String) : HomeEvent\r\n    data class ShowError(val message: String) : HomeEvent\r\n    data object NavigateToCreate : HomeEvent\r\n}\r\n```\r\n\r\n## UseCase — single responsibility\r\n\r\n```kotlin\r\n// ✅ One UseCase = one business operation\r\nclass GetItemsUseCase @Inject constructor(\r\n    private val repository: ItemRepository\r\n) {\r\n    suspend operator fun invoke(): Result<List<Item>> =\r\n        repository.getItems()\r\n}\r\n\r\n// ✅ UseCases return Result<T> — never throw exceptions to ViewModel\r\nclass ToggleFavoriteUseCase @Inject constructor(\r\n    private val repository: ItemRepository\r\n) {\r\n    suspend operator fun invoke(itemId: String): Result<Unit> =\r\n        repository.toggleFavorite(itemId)\r\n}\r\n\r\n// ❌ UseCase doing too much — split into separate UseCases\r\nclass ItemUseCase @Inject constructor(val repo: ItemRepository) {\r\n    suspend fun getItems() = repo.getItems()\r\n    suspend fun createItem(item: Item) = repo.create(item)\r\n    suspend fun deleteItem(id: String) = repo.delete(id)\r\n    // This is a service, not a use case\r\n}\r\n```\r\n\r\n## Repository — interface in Domain, implementation in Data\r\n\r\n```kotlin\r\n// domain/repository/ItemRepository.kt\r\ninterface ItemRepository {\r\n    suspend fun getItems(): Result<List<Item>>\r\n    suspend fun getItem(id: String): Result<Item>\r\n    suspend fun toggleFavorite(id: String): Result<Unit>\r\n    fun getItemsStream(): Flow<List<Item>>  // for real-time updates\r\n}\r\n\r\n// data/repository/ItemRepositoryImpl.kt\r\nclass ItemRepositoryImpl @Inject constructor(\r\n    private val remoteSource: ItemRemoteDataSource,\r\n    private val localSource: ItemLocalDataSource,\r\n    private val dispatcher: CoroutineDispatcher = Dispatchers.IO\r\n) : ItemRepository {\r\n\r\n    override suspend fun getItems(): Result<List<Item>> = withContext(dispatcher) {\r\n        runCatching {\r\n            val remote = remoteSource.getItems()\r\n            localSource.saveItems(remote.map { it.toEntity() })\r\n            remote.map { it.toDomain() }\r\n        }.recoverCatching {\r\n            localSource.getItems().map { it.toDomain() }  // fallback to cache\r\n        }\r\n    }\r\n\r\n    override fun getItemsStream(): Flow<List<Item>> =\r\n        localSource.getItemsFlow().map { entities -> entities.map { it.toDomain() } }\r\n}\r\n```\r\n\r\n## Mappers — DTOs never cross the data layer boundary\r\n\r\n```kotlin\r\n// ✅ Map at the data layer boundary\r\nfun ItemDto.toDomain() = Item(\r\n    id = id,\r\n    title = title,\r\n    description = description,\r\n    isFavorite = isFavorite\r\n)\r\n\r\nfun ItemEntity.toDomain() = Item(\r\n    id = id,\r\n    title = title,\r\n    description = description,\r\n    isFavorite = isFavorite\r\n)\r\n\r\nfun Item.toEntity() = ItemEntity(\r\n    id = id,\r\n    title = title,\r\n    description = description,\r\n    isFavorite = isFavorite\r\n)\r\n\r\n// ❌ Never expose DTOs or Entities to ViewModel or Composable\r\nclass HomeViewModel(val repo: ItemRepositoryImpl) {\r\n    val items: StateFlow<List<ItemDto>> = ...  // DTO leaking into ViewModel!\r\n}\r\n```\r\n\r\n## Screen — ViewModel wiring\r\n\r\n```kotlin\r\n@Composable\r\nfun HomeScreen(\r\n    viewModel: HomeViewModel = hiltViewModel(),\r\n    navController: NavController\r\n) {\r\n    val uiState by viewModel.uiState.collectAsStateWithLifecycle()\r\n\r\n    LaunchedEffect(Unit) {\r\n        viewModel.events.collect { event ->\r\n            when (event) {\r\n                is HomeEvent.NavigateToDetail -> navController.navigate(DetailRoute(event.itemId))\r\n                is HomeEvent.ShowError -> { /* show snackbar */ }\r\n                is HomeEvent.NavigateToCreate -> navController.navigate(CreateRoute)\r\n            }\r\n        }\r\n    }\r\n\r\n    HomeContent(\r\n        uiState = uiState,\r\n        onItemClick = viewModel::onItemClick,\r\n        onFavoriteClick = viewModel::onFavoriteClick,\r\n        onRetry = viewModel::loadItems\r\n    )\r\n}\r\n\r\n// ✅ Separate content composable — testable without ViewModel\r\n@Composable\r\nprivate fun HomeContent(\r\n    uiState: HomeUiState,\r\n    onItemClick: (String) -> Unit,\r\n    onFavoriteClick: (String) -> Unit,\r\n    onRetry: () -> Unit\r\n) {\r\n    when (uiState) {\r\n        is HomeUiState.Loading -> LoadingScreen()\r\n        is HomeUiState.Empty -> EmptyScreen()\r\n        is HomeUiState.Success -> ItemList(uiState.items, onItemClick, onFavoriteClick)\r\n        is HomeUiState.Error -> ErrorScreen(uiState.message, onRetry)\r\n    }\r\n}\r\n```\r\n\r\n## Common Mistakes\r\n\r\n❌ ViewModel calling Room DAO directly — always go through Repository\r\n❌ Repository in Domain layer — interface in Domain, implementation in Data\r\n❌ Domain model with `@Entity` or `@SerialName` annotations — pure Kotlin only\r\n❌ ViewModel holding Context — use Application context via Hilt if needed\r\n❌ StateFlow for navigation events — use SharedFlow\r\n❌ Try/catch in ViewModel — handle in Repository, return Result<T>\r\n❌ Multiple ViewModels per screen — one ViewModel per screen\r\n❌ Business logic in Composable — always in ViewModel or UseCase\r\n\r\n## Deep-dive references\r\n\r\n- `references/multi-module-arch.md` — structuring feature and core modules\r\n- `references/flow-patterns.md` — stateIn, flatMapLatest, combine patterns","tags":["android","architecture","agent","skills","piyushverma0","agent-skills","ai-agent","antigravity","claude-code","codex","cursor","gemini-cli"],"capabilities":["skill","source-piyushverma0","skill-android-architecture","topic-agent-skills","topic-ai-agent","topic-android","topic-antigravity","topic-claude-code","topic-codex","topic-cursor","topic-gemini-cli","topic-hilt","topic-jetpack-compose","topic-kotlin","topic-material3"],"categories":["android-agent-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/piyushverma0/android-agent-skills/android-architecture","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add piyushverma0/android-agent-skills","source_repo":"https://github.com/piyushverma0/android-agent-skills","install_from":"skills.sh"}},"qualityScore":"0.454","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 8 github stars · SKILL.md body (8,733 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:09:08.634Z","embedding":null,"createdAt":"2026-05-18T13:14:48.063Z","updatedAt":"2026-05-18T19:09:08.634Z","lastSeenAt":"2026-05-18T19:09:08.634Z","tsv":"'18':35 '27':4 '5':30 'action':83 'agent':9,55 'ai':8,32,54 'alway':667,727 'android':2,5,37,59,90,142 'android-architectur':1 'annot':687 'api':122 'app':60 'applic':695 'appmodule.kt':157 'appnavgraph.kt':162 'architectur':3,38,41,147 'auth':16 'bill':29 'boundari':511,518 'broken':149 'build':58 'busi':85,95,331,723 'cach':493 'call':73,123,128,663 'case':413 'class':108,174,196,290,297,307,314,333,356,381,452,561 'claud':10 'clean':40 'code':11 'codex':12 'com.company.app':152 'combin':745 'common':51,660 'complet':192 'compos':78,166,560,578,623,627,726 'constructor':199,336,359,384,455 'content':622 'context':693,696 'contract':99 'coordin':115 'core':740 'coroutinedispatch':467 'createitem':394 'createrout':608 'cross':507 'cursor':13 'dao':127,665 'data':44,102,107,109,173,180,281,285,289,296,306,313,320,420,509,516,680 'data/repository/itemrepositoryimpl.kt':451 'datasourc':120,125 'day':36 'deep':733 'deep-div':732 'defin':274 'deleteitem':401 'depend':69,91 'descript':526,527,537,538,548,549 'design':19 'detailrout':599 'di':153 'direct':666 'dispatch':466,477 'dispatchers.io':468 'dive':734 'domain':71,84,104,111,135,137,169,417,673,677,681 'domain/repository/itemrepository.kt':422 'dto':185,570 'dtos':129,133,505,554 'els':240 'empti':287 'emptyscreen':648 'entiti':189,501,556,684 'entities.map':502 'error':18,244,249,298 'error.message':247 'errorscreen':657 'event':77,220,223,593,595,704 'event.itemid':600 'events.assharedflow':225 'events.emit':255,266 'except':353 'expos':75,132,553 'fail':268 'fallback':491 'favorit':271 'featur':163,738 'fitgenz':31 'fix':14 'flatmaplatest':744 'flow':45,444,497 'fun':228,250,258,343,366,389,393,400,426,431,437,442,472,495,519,530,541,579,629 'getitem':202,233,390,427,432,473 'getitemsstream':443,496 'getitemsusecas':203,334 'getitemsusecase.kt':179 'go':668 'handl':710 'hilt':17,154,698 'hiltviewmodel':195,583 'hold':692 'home':164 'homecont':609,630 'homeev':305,312,319,323 'homeevent.navigatetocreate':606 'homeevent.navigatetodetail':256,597 'homeevent.showerror':267,602 'homescreen':580 'homescreen.kt':165 'homeuist':280,284,288,295,302,632 'homeuistate.empty':239,647 'homeuistate.error':246,656 'homeuistate.kt':168 'homeuistate.loading':213,232,644 'homeuistate.success':241,650 'homeviewmodel':197,562,582 'homeviewmodel.kt':167 'id':402,405,433,439,522,523,533,534,544,545 'impl':114 'implement':100,110,418,678 'import':141 'inconsist':20 'init':226 'inject':198,335,358,383,454 'interfac':98,112,177,279,304,415,423,675 'invok':344,367 'isfavorit':528,529,539,540,550,551 'it.todomain':486,490,503 'it.toentity':484 'item':235,242,293,395,396,398,521,532,567 'item.kt':171 'item.toentity':542 'itemapiservice.kt':184 'itemdao.kt':188 'itemdto.kt':186 'itemdto.todomain':520 'itement':543 'itementity.kt':190 'itementity.todomain':531 'itemid':252,257,260,264,310,368,372 'itemlist':651 'itemlocaldatasourc':463 'itemremotedatasourc':459 'itemrepositori':340,363,387,424,469 'itemrepository.kt':176 'itemrepositoryimpl':453,565 'itemrepositoryimpl.kt':182 'items.isempty':238 'itemusecas':382 'kapt':21 'kotlin':88,194,277,327,421,512,577,689 'ksp':22 'launchedeffect':590 'layer':63,103,138,510,517,674 'leak':571 'list':294,346,429,445,475,498,569 'load':283 'loadingscreen':645 'loaditem':227,229,620 'local':116,124,187 'localsourc':462 'localsource.getitems':488 'localsource.getitemsflow':499 'localsource.saveitems':482 'logic':86,724 'make':56 'map':489,500,513 'mapper':130,504 'messag':300,317 'miss':23 'mistak':53,661 'model':105,170,682 'modul':155,741 'much':376 'multipl':715 'must':139 'mutablesharedflow':221 'mutablestateflow':212 'mvvm':39 'navcontrol':584,585 'navcontroller.navigate':598,607 'navig':161,703 'navigatetocr':322 'navigatetodetail':308 'need':700 'networkmodule.kt':158 'never':131,140,351,506,552 'object':282,286,321 'observ':79 'one':94,328,330,719 'onfailur':243,265 'onfavoriteclick':259,615,617,636,654 'onitemclick':251,612,614,633,653 'onretri':618,639,659 'onsuccess':234 'oper':96,332,342,365 'orchestr':93 'overrid':470,494 'packag':150 'pattern':47,193,746 'per':717,721 'present':66 'prevent':48 'privat':200,204,209,218,337,360,456,460,464,628 'pure':87,106,172,688 'real':448 'real-tim':447 'recovercatch':487 'reduc':26 'refer':735 'references/flow-patterns.md':742 'references/multi-module-arch.md':736 'remot':117,119,183,480 'remote.map':483,485 'remotesourc':458 'remotesource.getitems':481 'repo':386,564 'repo.create':397 'repo.delete':404 'repo.getitems':391 'repositori':97,113,175,181,339,362,414,670,671,712 'repository.getitems':347 'repository.togglefavorite':371 'respons':326 'result':345,350,370,428,435,441,474,714 'retrofit':121 'return':349,713 'room':126,664 'rule':136 'runcatch':478 'screen':574,718,722 'seal':278,303 'send':81 'separ':65,379,621 'serialnam':686 'servic':409 'sharedflow':224,706 'ship':33 'show':603 'showerror':315 'singl':325 'skill':6 'skill-android-architecture' 'snackbar':604 'sourc':118 'source-piyushverma0' 'split':377 'state':25 'stateflow':216,568,701 'statein':743 'strict':64 'string':253,261,301,311,318,369,403,434,440,634,637 'structur':52,151,737 'success':291 'supabas':15 'suspend':341,364,388,392,399,425,430,436,471 'testabl':624 'theme':160 'three':62 'throw':352 'time':449 'titl':524,525,535,536,546,547 'togeth':276 'togglefavorit':206,263,438 'togglefavoriteusecas':207,357 'token':28 'topic-agent-skills' 'topic-ai-agent' 'topic-android' 'topic-antigravity' 'topic-claude-code' 'topic-codex' 'topic-cursor' 'topic-gemini-cli' 'topic-hilt' 'topic-jetpack-compose' 'topic-kotlin' 'topic-material3' 'try/catch':707 'ui':67,159 'uievent':273 'uistat':24,76,211,215,272,587,610,611,631,642 'uistate.asstateflow':217 'uistate.items':652 'uistate.message':658 'uistate.value':231,236,245 'unidirect':43 'unit':591,635,638,640 'unknown':248 'updat':270,450 'use':412,694,705 'usecas':74,92,178,324,329,348,373,380,731 'user':82 'val':201,205,210,214,219,222,292,299,309,316,338,361,385,457,461,465,479,563,566,586 'via':697 'viewmodel':72,80,191,208,355,558,573,575,581,613,616,619,626,662,691,709,716,720,729 'viewmodel.events.collect':592 'viewmodel.uistate.collectasstatewithlifecycle':589 'viewmodelscope.launch':230,254,262 'wire':576 'withcontext':476 'without':625 'zero':89","prices":[{"id":"8d7df2d9-5642-4285-b377-6f39b91911d7","listingId":"7f197fcf-526b-4514-8eb6-2cb3eb220108","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"piyushverma0","category":"android-agent-skills","install_from":"skills.sh"},"createdAt":"2026-05-18T13:14:48.063Z"}],"sources":[{"listingId":"7f197fcf-526b-4514-8eb6-2cb3eb220108","source":"github","sourceId":"piyushverma0/android-agent-skills/android-architecture","sourceUrl":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/android-architecture","isPrimary":false,"firstSeenAt":"2026-05-18T13:14:48.063Z","lastSeenAt":"2026-05-18T19:09:08.634Z"}],"details":{"listingId":"7f197fcf-526b-4514-8eb6-2cb3eb220108","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"piyushverma0","slug":"android-architecture","github":{"repo":"piyushverma0/android-agent-skills","stars":8,"topics":["agent-skills","ai-agent","android","antigravity","claude-code","codex","cursor","gemini-cli","hilt","jetpack-compose","kotlin","material3","open-source","skills","supabase"],"license":"mit","html_url":"https://github.com/piyushverma0/android-agent-skills","pushed_at":"2026-04-27T09:15:31Z","description":"27 Android skills for AI agents (Claude Code, Codex, Cursor). Fixes Supabase auth, Hilt errors, design inconsistency, kapt→ksp, missing UiState states. Reduced my token bills 5×. FitGenZ AI shipped in 18 days.","skill_md_sha":"7e65ba39f820fb30671cd7c73216c7b0b498fdb4","skill_md_path":"skills/android-architecture/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/android-architecture"},"layout":"multi","source":"github","category":"android-agent-skills","frontmatter":{},"skills_sh_url":"https://skills.sh/piyushverma0/android-agent-skills/android-architecture"},"updatedAt":"2026-05-18T19:09:08.634Z"}}