{"id":"ee355433-cfe2-4624-a7ec-9ae8042ff713","shortId":"jgDnUT","kind":"skill","title":"kotlin-patterns","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":"# Kotlin Patterns for Android\r\n\r\n12 rules for idiomatic, production-safe Kotlin on Android.\r\n\r\n## Rule 1: Coroutine scope — always use structured concurrency\r\n\r\n```kotlin\r\n// ✅ viewModelScope — auto-cancelled when ViewModel is cleared\r\nclass MyViewModel : ViewModel() {\r\n    fun load() {\r\n        viewModelScope.launch {\r\n            val result = fetchData()   // suspend, cancellable\r\n        }\r\n    }\r\n}\r\n\r\n// ✅ lifecycleScope — tied to Activity/Fragment lifecycle\r\nclass MyActivity : ComponentActivity() {\r\n    override fun onStart() {\r\n        super.onStart()\r\n        lifecycleScope.launch {\r\n            viewModel.events.collect { handleEvent(it) }\r\n        }\r\n    }\r\n}\r\n\r\n// ❌ GlobalScope — not structured, leaks, not cancellable\r\nGlobalScope.launch { fetchData() }\r\n\r\n// ❌ CoroutineScope(Dispatchers.IO) without proper lifecycle binding\r\nval scope = CoroutineScope(Dispatchers.IO)\r\nscope.launch { fetchData() }  // never cancelled\r\n```\r\n\r\n## Rule 2: Dispatcher discipline — always switch off Main\r\n\r\n```kotlin\r\n// ✅ IO-bound work: switch to IO dispatcher\r\nsuspend fun fetchUser(id: String): User = withContext(Dispatchers.IO) {\r\n    api.getUser(id)\r\n}\r\n\r\n// ✅ CPU-intensive work: use Default dispatcher\r\nsuspend fun processLargeList(items: List<Item>): List<Result> = withContext(Dispatchers.Default) {\r\n    items.map { processItem(it) }\r\n}\r\n\r\n// ✅ Inject dispatcher for testability\r\nclass UserRepository @Inject constructor(\r\n    private val api: UserApi,\r\n    @IoDispatcher private val dispatcher: CoroutineDispatcher\r\n) {\r\n    suspend fun getUser(id: String): User = withContext(dispatcher) {\r\n        api.getUser(id)\r\n    }\r\n}\r\n\r\n// ❌ Network call on Main thread — crashes with NetworkOnMainThreadException\r\nsuspend fun fetchUser(): User = api.getUser()  // on Main, wrong\r\n```\r\n\r\n## Rule 3: StateFlow — expose, never expose MutableStateFlow\r\n\r\n```kotlin\r\n// ✅ Mutable private, immutable public\r\nprivate val _uiState = MutableStateFlow(HomeUiState.Loading)\r\nval uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()\r\n\r\n// ✅ Update state correctly\r\n_uiState.value = HomeUiState.Success(items)          // from coroutine on Main\r\n_uiState.update { current -> current.copy(isLoading = false) }  // thread-safe update\r\n\r\n// ❌ Exposing MutableStateFlow — external code can change state\r\nval uiState = MutableStateFlow(HomeUiState.Loading)  // anyone can set this\r\n```\r\n\r\n## Rule 4: stateIn — convert cold Flow to StateFlow\r\n\r\n```kotlin\r\n// ✅ stateIn with correct parameters\r\nval items: StateFlow<List<Item>> = repository.getItemsFlow()\r\n    .stateIn(\r\n        scope = viewModelScope,\r\n        started = SharingStarted.WhileSubscribed(5_000),  // 5s timeout before cancelling\r\n        initialValue = emptyList()\r\n    )\r\n\r\n// ✅ WhileSubscribed(5000) — keeps upstream alive 5s after last subscriber\r\n// This handles configuration changes without re-fetching\r\n```\r\n\r\n## Rule 5: runCatching — safe error handling without try/catch\r\n\r\n```kotlin\r\n// ✅ runCatching wraps any exception in Result<T>\r\nsuspend fun getItems(): Result<List<Item>> = runCatching {\r\n    api.getItems().map { it.toDomain() }\r\n}\r\n\r\n// ✅ Chain Result transformations\r\nsuspend fun getActiveItems(): Result<List<Item>> =\r\n    getItems()\r\n        .map { items -> items.filter { it.isActive } }\r\n        .onFailure { error -> logger.e(error, \"Failed to get items\") }\r\n\r\n// ✅ In ViewModel\r\nviewModelScope.launch {\r\n    getItems()\r\n        .onSuccess { items -> _uiState.value = HomeUiState.Success(items) }\r\n        .onFailure { error -> _uiState.value = HomeUiState.Error(error.message ?: \"Error\") }\r\n}\r\n\r\n// ❌ Try/catch scattered through ViewModel — inconsistent, hard to compose\r\ntry {\r\n    val items = api.getItems()\r\n    _uiState.value = HomeUiState.Success(items)\r\n} catch (e: Exception) {\r\n    _uiState.value = HomeUiState.Error(e.message ?: \"Error\")\r\n}\r\n```\r\n\r\n## Rule 6: Sealed interfaces — prefer over sealed classes for state/events\r\n\r\n```kotlin\r\n// ✅ sealed interface — no constructor, lighter, more composable\r\nsealed interface LoginResult {\r\n    data object Success : LoginResult\r\n    data class Error(val code: Int, val message: String) : LoginResult\r\n    data object NetworkError : LoginResult\r\n}\r\n\r\n// ✅ sealed interface allows a class to implement multiple sealed hierarchies\r\nclass AuthError : LoginResult.Error(401, \"Unauthorized\"), ProfileResult.Unauthorized\r\n\r\n// ❌ sealed class — requires a superclass constructor\r\nsealed class LoginResult {\r\n    object Success : LoginResult()\r\n    data class Error(val message: String) : LoginResult()\r\n}\r\n```\r\n\r\n## Rule 7: Scope functions — use the right one\r\n\r\n```kotlin\r\n// ✅ let — transform nullable value, chain operations\r\nval length = name?.let { it.trim().length } ?: 0\r\n\r\n// ✅ apply — configure an object, return the object\r\nval intent = Intent(context, MainActivity::class.java).apply {\r\n    putExtra(\"id\", itemId)\r\n    flags = Intent.FLAG_ACTIVITY_NEW_TASK\r\n}\r\n\r\n// ✅ also — side effects, return the same object\r\nval items = repository.getItems().also { logger.d(\"Loaded ${it.size} items\") }\r\n\r\n// ✅ run — execute block, return result (combine let + with)\r\nval message = user.run {\r\n    if (isPremium) \"Welcome, Premium $name!\" else \"Welcome, $name!\"\r\n}\r\n\r\n// ✅ with — call multiple methods on an object, return result\r\nval summary = with(order) {\r\n    \"Order #$id: $itemCount items, total: $$total\"\r\n}\r\n\r\n// ❌ Nested let/run — unreadable, use named functions instead\r\nval result = a?.let { b?.let { c?.run { ... } } }  // pyramid of doom\r\n```\r\n\r\n## Rule 8: No !! operator in production code\r\n\r\n```kotlin\r\n// ✅ Safe alternatives to !!\r\nval name = user?.name ?: \"Anonymous\"          // Elvis operator\r\nval id = savedStateHandle.get<String>(\"id\") ?: return  // early return\r\nval file = getFile() ?: throw IllegalStateException(\"File required\")  // explicit throw\r\n\r\n// ✅ requireNotNull with message\r\nval config = requireNotNull(buildConfig) { \"BuildConfig must be initialized before use\" }\r\n\r\n// ❌ !! crashes with NullPointerException — never use in production\r\nval name = user!!.name   // NPE if user is null\r\nval id = args!!.getString(\"id\")  // NPE in production\r\n```\r\n\r\n## Rule 9: flatMapLatest — cancel previous on new emission\r\n\r\n```kotlin\r\n// ✅ flatMapLatest cancels in-flight request when new search query arrives\r\nval searchResults: StateFlow<List<Item>> = searchQuery\r\n    .debounce(300L)\r\n    .flatMapLatest { query ->\r\n        if (query.isBlank()) flowOf(emptyList())\r\n        else repository.search(query)\r\n    }\r\n    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())\r\n\r\n// ❌ flatMapMerge — all emissions run concurrently, results arrive out of order\r\nval searchResults = searchQuery.flatMapMerge { repository.search(it) }\r\n```\r\n\r\n## Rule 10: Data classes — correct usage\r\n\r\n```kotlin\r\n// ✅ data class for value objects with meaningful equality\r\ndata class Item(\r\n    val id: String,\r\n    val title: String,\r\n    val description: String,\r\n    val createdAt: Instant\r\n)\r\n\r\n// ✅ copy() for immutable updates\r\nval updated = item.copy(title = \"New Title\", description = \"Updated\")\r\n\r\n// ✅ data object for singletons in sealed hierarchies\r\nsealed interface AuthState {\r\n    data object Unauthenticated : AuthState\r\n    data object Loading : AuthState\r\n    data class Authenticated(val user: User) : AuthState\r\n}\r\n\r\n// ❌ Regular class for domain models — loses equals/hashCode/copy\r\nclass Item(val id: String, val title: String)  // no equals, no copy\r\n\r\n// ❌ Mutable data class — breaks StateFlow comparison, causes missed updates\r\ndata class UiState(var isLoading: Boolean = false)  // var in data class\r\n```\r\n\r\n## Rule 11: Lazy initialization\r\n\r\n```kotlin\r\n// ✅ by lazy — compute once, reuse, thread-safe by default\r\nval regex: Regex by lazy { Regex(\"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$\") }\r\n\r\n// ✅ lateinit var — for dependency injection and test setup\r\n@Inject lateinit var repository: ItemRepository\r\n\r\n// ✅ lateinit with isInitialized check when needed\r\nif (::repository.isInitialized) repository.close()\r\n\r\n// ❌ lazy without thread-safety consideration in concurrent contexts\r\nval cache by lazy(LazyThreadSafetyMode.NONE) { mutableMapOf<String, Item>() }\r\n// LazyThreadSafetyMode.NONE is only safe if accessed from single thread\r\n```\r\n\r\n## Rule 12: Extension functions — don't abuse them\r\n\r\n```kotlin\r\n// ✅ Extension for adding behavior to existing types\r\nfun String.isValidEmail(): Boolean =\r\n    android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()\r\n\r\nfun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {\r\n    Toast.makeText(this, message, duration).show()\r\n}\r\n\r\nfun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = flow {\r\n    var lastEmission = 0L\r\n    collect { value ->\r\n        val now = System.currentTimeMillis()\r\n        if (now - lastEmission >= windowDuration) {\r\n            lastEmission = now\r\n            emit(value)\r\n        }\r\n    }\r\n}\r\n\r\n// ❌ Extension that should be a UseCase — logic belongs in domain\r\nfun List<Item>.filterAndSort(): List<Item> {\r\n    // business logic in extension = untestable, not reusable across modules\r\n    return filter { it.isActive }.sortedBy { it.title }\r\n}\r\n```\r\n\r\n## Common Mistakes Quick Reference\r\n\r\n| ❌ Wrong | ✅ Right |\r\n|---|---|\r\n| `GlobalScope.launch` | `viewModelScope.launch` |\r\n| `user!!.name` | `user?.name ?: \"default\"` |\r\n| `collectAsState()` | `collectAsStateWithLifecycle()` |\r\n| `MutableStateFlow` exposed | `_private.asStateFlow()` |\r\n| Try/catch in ViewModel | `runCatching` in Repository |\r\n| `var` in `data class` | `val` — immutable |\r\n| `flatMapMerge` for search | `flatMapLatest` |\r\n| Computation on Main | `withContext(Dispatchers.IO)` |","tags":["kotlin","patterns","android","agent","skills","piyushverma0","agent-skills","ai-agent","antigravity","claude-code","codex","cursor"],"capabilities":["skill","source-piyushverma0","skill-kotlin-patterns","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/kotlin-patterns","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 (9,644 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:10.045Z","embedding":null,"createdAt":"2026-05-18T13:14:49.698Z","updatedAt":"2026-05-18T19:09:10.045Z","lastSeenAt":"2026-05-18T19:09:10.045Z","tsv":"'0':485 '000':284,690 '0l':941 '1':52 '10':708 '11':814 '12':41,898 '18':35 '2':118,848 '27':4 '3':206 '300l':676 '4':261 '401':442 '5':30,283,309,689 '5000':292 '5s':285,296 '6':391 '7':465 '8':580 '9':651,838,843 'a-za-z':844 'a-za-z0':834,839 'abus':903 'access':893 'across':976 'activ':505 'activity/fragment':82 'ad':908 'agent':9 'ai':8,32 'aliv':295 'allow':431 'also':508,518 'altern':588 'alway':55,121 'android':5,40,50 'android.util.patterns.email_address.matcher':916 'anonym':594 'anyon':256 'api':172 'api.getitems':329,379 'api.getuser':142,187,201 'appli':486,499 'arg':644 'arriv':669,698 'auth':16 'authent':769 'autherror':440 'authstat':758,762,766,773 'auto':62 'auto-cancel':61 'b':572 'behavior':909 'belong':962 'bill':29 'bind':108 'block':525 'boolean':807,915 'bound':128 'break':796 'buildconfig':619,620 'busi':969 'c':574 'cach':881 'call':190,543 'cancel':63,78,100,116,288,653,660 'catch':383 'caus':799 'chain':332,477 'chang':250,303 'check':865 'class':68,84,166,397,416,433,439,446,452,458,710,715,723,768,775,781,795,803,812,1010 'class.java':498 'claud':10 'clear':67 'code':11,248,419,585 'codex':12 'cold':264 'collect':942 'collectasst':996 'collectasstatewithlifecycl':997 'combin':528 'common':983 'comparison':798 'componentact':86 'compos':375,407 'comput':820,1017 'concurr':58,696,878 'config':617 'configur':302,487 'consider':876 'constructor':169,404,450 'context':496,879 'context.showtoast':920 'convert':263 'copi':737,792 'coroutin':53,233 'coroutinedispatch':178 'coroutinescop':103,111 'correct':228,271,711 'cpu':145 'cpu-intens':144 'crash':194,626 'createdat':735 'current':237 'current.copy':238 'cursor':13 'data':411,415,425,457,709,714,722,749,759,763,767,794,802,811,1009 'day':36 'debounc':675 'default':149,827,995 'depend':852 'descript':732,747 'design':19 'disciplin':120 'dispatch':119,133,150,163,177,186 'dispatchers.default':158 'dispatchers.io':104,112,141,1021 'domain':777,964 'doom':578 'durat':923,930 'e':384 'e.message':388 'earli':602 'effect':510 'els':539,683 'elvi':595 'emiss':657,694 'emit':953 'emptylist':290,682,691 'equal':721,790 'equals/hashcode/copy':780 'error':18,312,346,348,363,367,389,417,459 'error.message':366 'except':320,385 'execut':524 'exist':911 'explicit':611 'expos':208,210,245,999 'extens':899,906,955,972 'extern':247 'fail':349 'fals':240,808 'fetch':307 'fetchdata':76,102,114 'fetchus':136,199 'file':605,609 'filter':979 'filterandsort':967 'fitgenz':31 'fix':14 'flag':503 'flatmaplatest':652,659,677,1016 'flatmapmerg':692,1013 'flight':663 'flow':265,933,937,938 'flowof':681 'fun':71,88,135,152,180,198,324,336,913,919,932,965 'function':467,566,900 'get':351 'getactiveitem':337 'getfil':606 'getitem':325,340,356 'getstr':645 'getus':181 'globalscop':95 'globalscope.launch':101,989 'handl':301,313 'handleev':93 'hard':373 'hierarchi':438,755 'hilt':17 'homeuistate.error':365,387 'homeuistate.loading':221,255 'homeuistate.success':230,360,381 'id':137,143,182,188,501,556,598,600,643,646,726,784 'idiomat':44 'illegalstateexcept':608 'immut':215,739,1012 'implement':435 'in-flight':661 'inconsist':20,372 'initi':623,816 'initialvalu':289 'inject':162,168,853,857 'instant':736 'instead':567 'int':420,924 'intens':146 'intent':494,495 'intent.flag':504 'interfac':393,402,409,430,757 'io':127,132 'io-bound':126 'iodispatch':174 'isiniti':864 'isload':239,806 'ispremium':535 'it.isactive':344,980 'it.size':521 'it.title':982 'it.todomain':331 'it.trim':483 'item':154,231,274,342,352,358,361,378,382,516,522,558,724,782,887 'item.copy':743 'itemcount':557 'itemid':502 'itemrepositori':861 'items.filter':343 'items.map':159 'kapt':21 'keep':293 'kotlin':2,37,48,59,125,212,268,316,400,472,586,658,713,817,905 'kotlin-pattern':1 'ksp':22 'last':298 'lastemiss':940,949,951 'lateinit':849,858,862 'lazi':815,819,832,871,883 'lazythreadsafetymode.none':884,888 'leak':98 'length':480,484 'let':473,482,529,571,573 'let/run':562 'lifecycl':83,107 'lifecyclescop':79 'lifecyclescope.launch':91 'lighter':405 'list':155,156,276,327,339,673,966,968 'load':72,520,765 'logger.d':519 'logger.e':347 'logic':961,970 'loginresult':410,414,424,428,453,456,463 'loginresult.error':441 'long':936 'lose':779 'main':124,192,203,235,1019 'mainact':497 'map':330,341 'match':918 'meaning':720 'messag':422,461,532,615,921,929 'method':545 'miss':23,800 'mistak':984 'model':778 'modul':977 'multipl':436,544 'must':621 'mutabl':213,793 'mutablemapof':885 'mutablestateflow':211,220,246,254,998 'myactiv':85 'myviewmodel':69 'name':481,538,541,565,591,593,634,636,992,994 'need':867 'nest':561 'network':189 'networkerror':427 'networkonmainthreadexcept':196 'never':115,209,629 'new':506,656,666,745 'npe':637,647 'null':641 'nullabl':475 'nullpointerexcept':628 'object':412,426,454,489,492,514,548,718,750,760,764 'one':471 'onfailur':345,362 'onstart':89 'onsuccess':357 'oper':478,582,596 'order':554,555,701 'overrid':87 'paramet':272 'pattern':3,38 'prefer':394 'premium':537 'previous':654 'privat':170,175,214,217 'private.asstateflow':1000 'processitem':160 'processlargelist':153 'product':46,584,632,649 'production-saf':45 'profileresult.unauthorized':444 'proper':106 'public':216 'putextra':500 'pyramid':576 'queri':668,678,685 'query.isblank':680 'quick':985 're':306 're-fetch':305 'reduc':26 'refer':986 'regex':829,830,833 'regular':774 'repositori':860,1006 'repository.close':870 'repository.getitems':517 'repository.getitemsflow':277 'repository.isinitialized':869 'repository.search':684,705 'request':664 'requir':447,610 'requirenotnul':613,618 'result':75,322,326,333,338,527,550,569,697 'return':490,511,526,549,601,603,978 'reus':822 'reusabl':975 'right':470,988 'rule':42,51,117,205,260,308,390,464,579,650,707,813,897 'run':523,575,695 'runcatch':310,317,328,1004 'safe':47,243,311,587,825,891 'safeti':875 'savedstatehandle.get':599 'scatter':369 'scope':54,110,279,466 'scope.launch':113 'seal':392,396,401,408,429,437,445,451,754,756 'search':667,1015 'searchqueri':674 'searchquery.flatmapmerge':704 'searchresult':671,703 'set':258 'setup':856 'sharingstarted.whilesubscribed':282,688 'ship':33 'short':926 'show':931 'side':509 'singl':895 'singleton':752 'skill':6 'skill-kotlin-patterns' 'sortedbi':981 'source-piyushverma0' 'start':281 'state':25,227,251 'state/events':399 'stateflow':207,224,267,275,672,797 'statein':262,269,278,686 'string':138,183,423,462,727,730,733,785,788,886,922 'string.isvalidemail':914 'structur':57,97 'subscrib':299 'success':413,455 'summari':552 'supabas':15 'super.onstart':90 'superclass':449 'suspend':77,134,151,179,197,323,335 'switch':122,130 'system.currenttimemillis':946 'task':507 'test':855 'testabl':165 'thread':193,242,824,874,896 'thread-saf':241,823 'thread-safeti':873 'throttlefirst':934 'throw':607,612 'tie':80 'timeout':286 'titl':729,744,746,787 'toast.length':925 'toast.maketext':927 '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' 'total':559,560 'transform':334,474 'tri':376 'try/catch':315,368,1001 'type':912 'uistat':24,219,223,253,804 'uistate.asstateflow':225 'uistate.update':236 'uistate.value':229,359,364,380,386 'unauthent':761 'unauthor':443 'unread':563 'untest':973 'updat':226,244,740,742,748,801 'upstream':294 'usag':712 'use':56,148,468,564,625,630 'usecas':960 'user':139,184,200,592,635,639,771,772,991,993 'user.run':533 'userapi':173 'userrepositori':167 'val':74,109,171,176,218,222,252,273,377,418,421,460,479,493,515,531,551,568,590,597,604,616,633,642,670,702,725,728,731,734,741,770,783,786,828,880,944,1011 'valu':476,717,943,954 'var':805,809,850,859,939,1007 'viewmodel':65,70,354,371,1003 'viewmodel.events.collect':92 'viewmodelscop':60,280,687 'viewmodelscope.launch':73,355,990 'welcom':536,540 'whilesubscrib':291 'windowdur':935,950 'withcontext':140,157,185,1020 'without':105,304,314,872 'work':129,147 'wrap':318 'wrong':204,987 'z':847 'z0':837,842 'za':836,841,846","prices":[{"id":"63da86b5-06e5-4d1a-b96a-3f81314da9b5","listingId":"ee355433-cfe2-4624-a7ec-9ae8042ff713","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:49.698Z"}],"sources":[{"listingId":"ee355433-cfe2-4624-a7ec-9ae8042ff713","source":"github","sourceId":"piyushverma0/android-agent-skills/kotlin-patterns","sourceUrl":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/kotlin-patterns","isPrimary":false,"firstSeenAt":"2026-05-18T13:14:49.698Z","lastSeenAt":"2026-05-18T19:09:10.045Z"}],"details":{"listingId":"ee355433-cfe2-4624-a7ec-9ae8042ff713","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"piyushverma0","slug":"kotlin-patterns","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":"54f53e79b4c55827754e555d44b4cd08145e2bde","skill_md_path":"skills/kotlin-patterns/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/kotlin-patterns"},"layout":"multi","source":"github","category":"android-agent-skills","frontmatter":{},"skills_sh_url":"https://skills.sh/piyushverma0/android-agent-skills/kotlin-patterns"},"updatedAt":"2026-05-18T19:09:10.045Z"}}