{"id":"cbb899b6-2e81-49b1-b74e-6c27e96f9f38","shortId":"45nRd5","kind":"skill","title":"android-testing","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 Testing\r\n\r\nNo tests = not production-ready. These rules cover the complete Android testing stack.\r\n\r\n## Setup — all testing dependencies\r\n\r\n```toml\r\n[versions]\r\njunit = \"4.13.2\"\r\njunitExt = \"1.2.1\"\r\ncoroutinesTest = \"1.9.0\"\r\nmockk = \"1.13.12\"\r\nturbine = \"1.1.0\"\r\nroborazzi = \"1.27.0\"\r\nhiltTesting = \"2.52\"\r\n\r\n[libraries]\r\njunit = { group = \"junit\", name = \"junit\", version.ref = \"junit\" }\r\nmockk = { group = \"io.mockk\", name = \"mockk\", version.ref = \"mockk\" }\r\nmockk-android = { group = \"io.mockk\", name = \"mockk-android\", version.ref = \"mockk\" }\r\nkotlinx-coroutines-test = { group = \"org.jetbrains.kotlinx\", name = \"kotlinx-coroutines-test\", version.ref = \"coroutinesTest\" }\r\nturbine = { group = \"app.cash.turbine\", name = \"turbine\", version.ref = \"turbine\" }\r\nandroidx-junit = { group = \"androidx.test.ext\", name = \"junit\", version.ref = \"junitExt\" }\r\nhilt-android-testing = { group = \"com.google.dagger\", name = \"hilt-android-testing\", version.ref = \"hiltTesting\" }\r\nroom-testing = { group = \"androidx.room\", name = \"room-testing\", version.ref = \"room\" }\r\nroborazzi = { group = \"io.github.takahirom.roborazzi\", name = \"roborazzi\", version.ref = \"roborazzi\" }\r\nroborazzi-compose = { group = \"io.github.takahirom.roborazzi\", name = \"roborazzi-compose\", version.ref = \"roborazzi\" }\r\n```\r\n\r\n```kotlin\r\n// build.gradle.kts\r\ntestImplementation(libs.junit)\r\ntestImplementation(libs.mockk)\r\ntestImplementation(libs.kotlinx.coroutines.test)\r\ntestImplementation(libs.turbine)\r\ntestImplementation(libs.room.testing)\r\nandroidTestImplementation(libs.androidx.junit)\r\nandroidTestImplementation(libs.hilt.android.testing)\r\nandroidTestImplementation(libs.mockk.android)\r\nandroidTestImplementation(libs.roborazzi)\r\nandroidTestImplementation(libs.roborazzi.compose)\r\nkspAndroidTest(libs.hilt.compiler)\r\n```\r\n\r\n## Rule 1: ViewModel tests — pure unit tests\r\n\r\n```kotlin\r\nclass HomeViewModelTest {\r\n    // Replace Main dispatcher with test dispatcher\r\n    @get:Rule\r\n    val mainDispatcherRule = MainDispatcherRule()\r\n\r\n    private val fakeRepository = FakeItemRepository()\r\n    private lateinit var viewModel: HomeViewModel\r\n\r\n    @Before\r\n    fun setUp() {\r\n        viewModel = HomeViewModel(\r\n            getItems = GetItemsUseCase(fakeRepository),\r\n            toggleFavorite = ToggleFavoriteUseCase(fakeRepository)\r\n        )\r\n    }\r\n\r\n    @Test\r\n    fun `initial state is Loading`() = runTest {\r\n        viewModel.uiState.test {\r\n            assertEquals(HomeUiState.Loading, awaitItem())\r\n            cancelAndIgnoreRemainingEvents()\r\n        }\r\n    }\r\n\r\n    @Test\r\n    fun `loads items successfully`() = runTest {\r\n        fakeRepository.setItems(listOf(sampleItem))\r\n\r\n        viewModel.uiState.test {\r\n            skipItems(1) // skip Loading\r\n            val state = awaitItem()\r\n            assertTrue(state is HomeUiState.Success)\r\n            assertEquals(1, (state as HomeUiState.Success).items.size)\r\n        }\r\n    }\r\n\r\n    @Test\r\n    fun `shows error when loading fails`() = runTest {\r\n        fakeRepository.setShouldFail(true)\r\n\r\n        viewModel.uiState.test {\r\n            skipItems(1) // skip Loading\r\n            assertTrue(awaitItem() is HomeUiState.Error)\r\n        }\r\n    }\r\n\r\n    @Test\r\n    fun `emits navigation event on item click`() = runTest {\r\n        viewModel.events.test {\r\n            viewModel.onItemClick(\"item-123\")\r\n            assertEquals(HomeEvent.NavigateToDetail(\"item-123\"), awaitItem())\r\n        }\r\n    }\r\n}\r\n\r\n// MainDispatcherRule — required for every ViewModel test\r\nclass MainDispatcherRule(\r\n    private val dispatcher: TestDispatcher = UnconfinedTestDispatcher()\r\n) : TestWatcher() {\r\n    override fun starting(description: Description) {\r\n        Dispatchers.setMain(dispatcher)\r\n    }\r\n    override fun finished(description: Description) {\r\n        Dispatchers.resetMain()\r\n    }\r\n}\r\n```\r\n\r\n## Rule 2: Fake repositories — test doubles over mocks for repositories\r\n\r\n```kotlin\r\n// ✅ Fake repository — controllable, reliable\r\nclass FakeItemRepository : ItemRepository {\r\n    private val items = MutableStateFlow<List<Item>>(emptyList())\r\n    private var shouldFail = false\r\n    private var syncDelay = 0L\r\n\r\n    fun setItems(newItems: List<Item>) { items.value = newItems }\r\n    fun setShouldFail(fail: Boolean) { shouldFail = fail }\r\n\r\n    override fun getItemsStream(): Flow<List<Item>> = items\r\n\r\n    override suspend fun getItems(): Result<List<Item>> {\r\n        if (syncDelay > 0) delay(syncDelay)\r\n        return if (shouldFail) Result.failure(IOException(\"Network error\"))\r\n        else Result.success(items.value)\r\n    }\r\n\r\n    override suspend fun toggleFavorite(id: String): Result<Unit> {\r\n        if (shouldFail) return Result.failure(IOException(\"Network error\"))\r\n        items.update { current ->\r\n            current.map { if (it.id == id) it.copy(isFavorite = !it.isFavorite) else it }\r\n        }\r\n        return Result.success(Unit)\r\n    }\r\n}\r\n```\r\n\r\n## Rule 3: Repository tests with in-memory Room\r\n\r\n```kotlin\r\n@RunWith(AndroidJUnit4::class)\r\nclass ItemRepositoryTest {\r\n    private lateinit var db: AppDatabase\r\n    private lateinit var dao: ItemDao\r\n    private lateinit var mockApi: ItemApiService\r\n    private lateinit var repository: ItemRepositoryImpl\r\n\r\n    @Before\r\n    fun setUp() {\r\n        db = Room.inMemoryDatabaseBuilder(\r\n            ApplicationProvider.getApplicationContext(),\r\n            AppDatabase::class.java\r\n        ).allowMainThreadQueries().build()\r\n\r\n        dao = db.itemDao()\r\n        mockApi = mockk()\r\n        repository = ItemRepositoryImpl(mockApi, dao, UnconfinedTestDispatcher())\r\n    }\r\n\r\n    @After\r\n    fun tearDown() { db.close() }\r\n\r\n    @Test\r\n    fun `sync saves items to local database`() = runTest {\r\n        val remoteItems = listOf(ItemDto(\"1\", \"Test Item\", \"Description\"))\r\n        coEvery { mockApi.getItems() } returns remoteItems\r\n\r\n        repository.sync()\r\n\r\n        repository.getItemsStream().test {\r\n            val items = awaitItem()\r\n            assertEquals(1, items.size)\r\n            assertEquals(\"Test Item\", items.first().title)\r\n        }\r\n    }\r\n\r\n    @Test\r\n    fun `returns cached data when network fails`() = runTest {\r\n        dao.upsertAll(listOf(ItemEntity(\"1\", \"Cached Item\", \"Description\")))\r\n        coEvery { mockApi.getItems() } throws IOException(\"Network error\")\r\n\r\n        val result = repository.getItems()\r\n        assertTrue(result.isSuccess)\r\n        assertEquals(\"Cached Item\", result.getOrNull()?.first()?.title)\r\n    }\r\n}\r\n```\r\n\r\n## Rule 4: Compose UI tests\r\n\r\n```kotlin\r\n@RunWith(AndroidJUnit4::class)\r\nclass HomeScreenTest {\r\n    @get:Rule\r\n    val composeTestRule = createComposeRule()\r\n\r\n    @Test\r\n    fun `shows loading indicator initially`() {\r\n        composeTestRule.setContent {\r\n            MyAppTheme {\r\n                HomeContent(uiState = HomeUiState.Loading, onItemClick = {}, onRetry = {})\r\n            }\r\n        }\r\n        composeTestRule.onNodeWithContentDescription(\"Loading\").assertIsDisplayed()\r\n    }\r\n\r\n    @Test\r\n    fun `shows items when loaded`() {\r\n        val items = listOf(Item(\"1\", \"Test Item\", \"Description\"))\r\n        composeTestRule.setContent {\r\n            MyAppTheme {\r\n                HomeContent(\r\n                    uiState = HomeUiState.Success(items),\r\n                    onItemClick = {},\r\n                    onRetry = {}\r\n                )\r\n            }\r\n        }\r\n        composeTestRule.onNodeWithText(\"Test Item\").assertIsDisplayed()\r\n    }\r\n\r\n    @Test\r\n    fun `retry button calls onRetry`() {\r\n        var retryClicked = false\r\n        composeTestRule.setContent {\r\n            MyAppTheme {\r\n                HomeContent(\r\n                    uiState = HomeUiState.Error(\"Network error\"),\r\n                    onItemClick = {},\r\n                    onRetry = { retryClicked = true }\r\n                )\r\n            }\r\n        }\r\n        composeTestRule.onNodeWithText(\"Try again\").performClick()\r\n        assertTrue(retryClicked)\r\n    }\r\n\r\n    @Test\r\n    fun `clicking item triggers navigation`() {\r\n        var clickedId = \"\"\r\n        val items = listOf(Item(\"item-1\", \"Test Item\", \"Description\"))\r\n        composeTestRule.setContent {\r\n            MyAppTheme {\r\n                HomeContent(\r\n                    uiState = HomeUiState.Success(items),\r\n                    onItemClick = { clickedId = it },\r\n                    onRetry = {}\r\n                )\r\n            }\r\n        }\r\n        composeTestRule.onNodeWithText(\"Test Item\").performClick()\r\n        assertEquals(\"item-1\", clickedId)\r\n    }\r\n}\r\n```\r\n\r\n## Rule 5: Screenshot tests with Roborazzi\r\n\r\n```kotlin\r\n@RunWith(AndroidJUnit4::class)\r\n@GraphicsMode(GraphicsMode.Mode.NATIVE)\r\n@Config(sdk = [34], qualifiers = RobolectricDeviceQualifiers.Pixel6)\r\nclass HomeScreenScreenshotTest {\r\n    @get:Rule\r\n    val composeTestRule = createComposeRule()\r\n\r\n    @Test\r\n    fun homeScreen_loading() {\r\n        composeTestRule.setContent {\r\n            MyAppTheme { HomeContent(HomeUiState.Loading, {}, {}) }\r\n        }\r\n        composeTestRule.onRoot().captureRoboImage()\r\n    }\r\n\r\n    @Test\r\n    fun homeScreen_success() {\r\n        composeTestRule.setContent {\r\n            MyAppTheme { HomeContent(HomeUiState.Success(sampleItems), {}, {}) }\r\n        }\r\n        composeTestRule.onRoot().captureRoboImage()\r\n    }\r\n\r\n    @Test\r\n    fun homeScreen_dark() {\r\n        composeTestRule.setContent {\r\n            MyAppTheme(darkTheme = true) { HomeContent(HomeUiState.Success(sampleItems), {}, {}) }\r\n        }\r\n        composeTestRule.onRoot().captureRoboImage()\r\n    }\r\n}\r\n```\r\n\r\n## Rule 6: Hilt integration tests\r\n\r\n```kotlin\r\n@HiltAndroidTest\r\n@RunWith(AndroidJUnit4::class)\r\nclass HomeViewModelIntegrationTest {\r\n    @get:Rule(order = 0)\r\n    val hiltRule = HiltAndroidRule(this)\r\n\r\n    @get:Rule(order = 1)\r\n    val mainDispatcherRule = MainDispatcherRule()\r\n\r\n    @BindValue\r\n    val fakeRepository: ItemRepository = FakeItemRepository()\r\n\r\n    @Inject\r\n    lateinit var getItems: GetItemsUseCase\r\n\r\n    @Before\r\n    fun setUp() { hiltRule.inject() }\r\n\r\n    @Test\r\n    fun `full integration: ViewModel loads items via UseCase`() = runTest {\r\n        (fakeRepository as FakeItemRepository).setItems(listOf(sampleItem))\r\n        val viewModel = HomeViewModel(getItems)\r\n\r\n        viewModel.uiState.test {\r\n            skipItems(1) // Loading\r\n            assertTrue(awaitItem() is HomeUiState.Success)\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Common Mistakes\r\n\r\n❌ Testing without `MainDispatcherRule` — coroutine tests hang or produce wrong results\r\n❌ Mocking Repository in ViewModel tests — use Fake instead for reliability\r\n❌ Missing `@After` to close in-memory DB — causes test pollution\r\n❌ `runBlocking` in tests — use `runTest` from coroutines-test\r\n❌ Testing ViewModel with real network calls — always fake/mock external dependencies\r\n❌ Missing `allowMainThreadQueries()` for Room in tests — crash on test thread\r\n\r\n## Deep-dive references\r\n\r\n- `references/testing-strategies.md` — testing pyramid, what to unit vs integration test\r\n- `references/mockk-patterns.md` — MockK for complex scenarios","tags":["android","testing","agent","skills","piyushverma0","agent-skills","ai-agent","antigravity","claude-code","codex","cursor","gemini-cli"],"capabilities":["skill","source-piyushverma0","skill-android-testing","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-testing","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 (11,055 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.872Z","embedding":null,"createdAt":"2026-05-18T13:14:48.293Z","updatedAt":"2026-05-18T19:09:08.872Z","lastSeenAt":"2026-05-18T19:09:08.872Z","tsv":"'-1':660,680 '-123':305,309 '0':396,755 '0l':369 '1':195,258,269,286,508,523,542,605,763,803 '1.1.0':68 '1.13.12':66 '1.2.1':62 '1.27.0':70 '1.9.0':64 '18':35 '2':339 '2.52':72 '27':4 '3':438 '34':696 '4':564 '4.13.2':60 '5':30,683 '6':741 'agent':9 'ai':8,32 'allowmainthreadqueri':480,862 'alway':857 'android':2,5,37,50,90,96,130,137 'android-test':1 'androidjunit4':448,570,690,748 'androidtestimplement':182,184,186,188,190 'androidx':120 'androidx-junit':119 'androidx.room':145 'androidx.test.ext':123 'app.cash.turbine':114 'appdatabas':456,478 'applicationprovider.getapplicationcontext':477 'assertequ':243,268,306,522,525,557,678 'assertisdisplay':594,620 'asserttru':264,289,555,645,805 'auth':16 'awaititem':245,263,290,310,521,806 'bill':29 'bindvalu':767 'boolean':379 'build':481 'build.gradle.kts':171 'button':624 'cach':533,543,558 'call':625,856 'cancelandignoreremainingev':246 'captureroboimag':715,726,739 'caus':839 'class':202,317,353,449,450,571,572,691,699,749,750 'class.java':479 'claud':10 'click':300,649 'clickedid':654,671,681 'close':834 'code':11 'codex':12 'coeveri':512,546 'com.google.dagger':133 'common':809 'complet':49 'complex':887 'compos':161,167,565 'composetestrul':577,704 'composetestrule.onnodewithcontentdescription':592 'composetestrule.onnodewithtext':617,641,674 'composetestrule.onroot':714,725,738 'composetestrule.setcontent':585,609,630,664,710,720,731 'config':694 'control':351 'coroutin':101,108,814,849 'coroutines-test':848 'coroutinestest':63,111 'cover':47 'crash':867 'createcomposerul':578,705 'current':424 'current.map':425 'cursor':13 'dao':460,482,489 'dao.upsertall':539 'dark':730 'darkthem':733 'data':534 'databas':502 'day':36 'db':455,475,838 'db.close':494 'db.itemdao':483 'deep':872 'deep-div':871 'delay':397 'depend':56,860 'descript':328,329,335,336,511,545,608,663 'design':19 'dispatch':206,209,321,331 'dispatchers.resetmain':337 'dispatchers.setmain':330 'dive':873 'doubl':343 'els':406,432 'emit':295 'emptylist':361 'error':18,277,405,422,551,636 'event':297 'everi':314 'extern':859 'fail':280,378,381,537 'fake':340,349,827 'fake/mock':858 'fakeitemrepositori':218,354,771,793 'fakerepositori':217,231,234,769,791 'fakerepository.setitems':253 'fakerepository.setshouldfail':282 'fals':365,629 'finish':334 'first':561 'fitgenz':31 'fix':14 'flow':385 'full':783 'fun':225,236,248,275,294,326,333,370,376,383,390,411,473,492,496,531,580,596,622,648,707,717,728,778,782 'get':210,574,701,752,760 'getitem':229,391,775,800 'getitemsstream':384 'getitemsusecas':230,776 'graphicsmod':692 'graphicsmode.mode.native':693 'group':75,82,91,103,113,122,132,144,153,162 'hang':816 'hilt':17,129,136,742 'hilt-android-test':128,135 'hiltandroidrul':758 'hiltandroidtest':746 'hiltrul':757 'hiltrule.inject':780 'hilttest':71,140 'homecont':587,611,632,666,712,722,735 'homeevent.navigatetodetail':307 'homescreen':708,718,729 'homescreenscreenshottest':700 'homescreentest':573 'homeuistate.error':292,634 'homeuistate.loading':244,589,713 'homeuistate.success':267,272,613,668,723,736,808 'homeviewmodel':223,228,799 'homeviewmodelintegrationtest':751 'homeviewmodeltest':203 'id':413,428 'in-memori':442,835 'inconsist':20 'indic':583 'initi':237,584 'inject':772 'instead':828 'integr':743,784,882 'io.github.takahirom.roborazzi':154,163 'io.mockk':83,92 'ioexcept':403,420,549 'isfavorit':430 'it.copy':429 'it.id':427 'it.isfavorite':431 'item':250,299,304,308,358,387,499,510,520,527,544,559,598,602,604,607,614,619,650,656,658,659,662,669,676,679,787 'itemapiservic':466 'itemdao':461 'itemdto':507 'itement':541 'itemrepositori':355,770 'itemrepositoryimpl':471,487 'itemrepositorytest':451 'items.first':528 'items.size':273,524 'items.update':423 'items.value':374,408 'junit':59,74,76,78,80,121,125 'junitext':61,127 'kapt':21 'kotlin':170,201,348,446,568,688,745 'kotlinx':100,107 'kotlinx-coroutines-test':99,106 'ksp':22 'kspandroidtest':192 'lateinit':220,453,458,463,468,773 'librari':73 'libs.androidx.junit':183 'libs.hilt.android.testing':185 'libs.hilt.compiler':193 'libs.junit':173 'libs.kotlinx.coroutines.test':177 'libs.mockk':175 'libs.mockk.android':187 'libs.roborazzi':189 'libs.roborazzi.compose':191 'libs.room.testing':181 'libs.turbine':179 'list':360,373,386,393 'listof':254,506,540,603,657,795 'load':240,249,260,279,288,582,593,600,709,786,804 'local':501 'main':205 'maindispatcherrul':213,214,311,318,765,766,813 'memori':444,837 'miss':23,831,861 'mistak':810 'mock':345,821 'mockapi':465,484,488 'mockapi.getitems':513,547 'mockk':65,81,85,87,89,95,98,485,885 'mockk-android':88,94 'mutablestateflow':359 'myappthem':586,610,631,665,711,721,732 'name':77,84,93,105,115,124,134,146,155,164 'navig':296,652 'network':404,421,536,550,635,855 'newitem':372,375 'onitemclick':590,615,637,670 'onretri':591,616,626,638,673 'order':754,762 'org.jetbrains.kotlinx':104 'overrid':325,332,382,388,409 'performclick':644,677 'pollut':841 'privat':215,219,319,356,362,366,452,457,462,467 'produc':818 'product':43 'production-readi':42 'pure':198 'pyramid':877 'qualifi':697 'readi':44 'real':854 'reduc':26 'refer':874 'references/mockk-patterns.md':884 'references/testing-strategies.md':875 'reliabl':352,830 'remoteitem':505,515 'replac':204 'repositori':341,347,350,439,470,486,822 'repository.getitems':554 'repository.getitemsstream':517 'repository.sync':516 'requir':312 'result':392,415,553,820 'result.failure':402,419 'result.getornull':560 'result.issuccess':556 'result.success':407,435 'retri':623 'retryclick':628,639,646 'return':399,418,434,514,532 'robolectricdevicequalifiers.pixel6':698 'roborazzi':69,152,156,158,160,166,169,687 'roborazzi-compos':159,165 'room':142,148,151,445,864 'room-test':141,147 'room.inmemorydatabasebuilder':476 'rule':46,194,211,338,437,563,575,682,702,740,753,761 'runblock':842 'runtest':241,252,281,301,503,538,790,846 'runwith':447,569,689,747 'sampleitem':255,724,737,796 'save':498 'scenario':888 'screenshot':684 'sdk':695 'setitem':371,794 'setshouldfail':377 'setup':53,226,474,779 'ship':33 'shouldfail':364,380,401,417 'show':276,581,597 'skill':6 'skill-android-testing' 'skip':259,287 'skipitem':257,285,802 'source-piyushverma0' 'stack':52 'start':327 'state':25,238,262,265,270 'string':414 'success':251,719 'supabas':15 'suspend':389,410 'sync':497 'syncdelay':368,395,398 'teardown':493 'test':3,38,40,51,55,102,109,131,138,143,149,197,200,208,235,247,274,293,316,342,440,495,509,518,526,530,567,579,595,606,618,621,647,661,675,685,706,716,727,744,781,811,815,825,840,844,850,851,866,869,876,883 'testdispatch':322 'testimplement':172,174,176,178,180 'testwatch':324 'thread':870 'throw':548 'titl':529,562 'togglefavorit':232,412 'togglefavoriteusecas':233 'token':28 'toml':57 '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' 'tri':642 'trigger':651 'true':283,640,734 'turbin':67,112,116,118 'ui':566 'uistat':24,588,612,633,667 'unconfinedtestdispatch':323,490 'unit':199,436,880 'use':826,845 'usecas':789 'val':212,216,261,320,357,504,519,552,576,601,655,703,756,764,768,797 'var':221,363,367,454,459,464,469,627,653,774 'version':58 'version.ref':79,86,97,110,117,126,139,150,157,168 'via':788 'viewmodel':196,222,227,315,785,798,824,852 'viewmodel.events.test':302 'viewmodel.onitemclick':303 'viewmodel.uistate.test':242,256,284,801 'vs':881 'without':812 'wrong':819","prices":[{"id":"f0b81020-9349-4e82-9b5c-5392612c3402","listingId":"cbb899b6-2e81-49b1-b74e-6c27e96f9f38","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.293Z"}],"sources":[{"listingId":"cbb899b6-2e81-49b1-b74e-6c27e96f9f38","source":"github","sourceId":"piyushverma0/android-agent-skills/android-testing","sourceUrl":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/android-testing","isPrimary":false,"firstSeenAt":"2026-05-18T13:14:48.293Z","lastSeenAt":"2026-05-18T19:09:08.872Z"}],"details":{"listingId":"cbb899b6-2e81-49b1-b74e-6c27e96f9f38","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"piyushverma0","slug":"android-testing","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":"9b90cf780cd5108120a27e2c33b59c6f80f9c08b","skill_md_path":"skills/android-testing/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/android-testing"},"layout":"multi","source":"github","category":"android-agent-skills","frontmatter":{},"skills_sh_url":"https://skills.sh/piyushverma0/android-agent-skills/android-testing"},"updatedAt":"2026-05-18T19:09:08.872Z"}}