{"id":"e5dec98d-47a9-4138-848c-77e5ebf038ac","shortId":"mk5tmD","kind":"skill","title":"supabase-android","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":"# Supabase Android (supabase-kt)\r\n\r\n## Setup\r\n\r\n```toml\r\n[versions]\r\nsupabase = \"3.0.2\"\r\nktor = \"3.0.1\"\r\n[libraries]\r\nsupabase-bom = { group = \"io.github.jan-tennert.supabase\", name = \"bom\", version.ref = \"supabase\" }\r\nsupabase-postgrest = { group = \"io.github.jan-tennert.supabase\", name = \"postgrest-kt\" }\r\nsupabase-auth = { group = \"io.github.jan-tennert.supabase\", name = \"auth-kt\" }\r\nsupabase-realtime = { group = \"io.github.jan-tennert.supabase\", name = \"realtime-kt\" }\r\nsupabase-storage = { group = \"io.github.jan-tennert.supabase\", name = \"storage-kt\" }\r\nsupabase-functions = { group = \"io.github.jan-tennert.supabase\", name = \"functions-kt\" }\r\nktor-android = { group = \"io.ktor\", name = \"ktor-client-android\", version.ref = \"ktor\" }\r\n```\r\n\r\n```kotlin\r\nimplementation(platform(libs.supabase.bom))\r\nimplementation(libs.supabase.postgrest)\r\nimplementation(libs.supabase.auth)\r\nimplementation(libs.supabase.realtime)\r\nimplementation(libs.supabase.functions)\r\nimplementation(libs.ktor.android)\r\n```\r\n\r\n## Rule 1: Client initialization — the #1 mistake\r\n\r\n```kotlin\r\n// ✅ Correct Supabase client setup\r\n@Module @InstallIn(SingletonComponent::class)\r\nobject SupabaseModule {\r\n    @Provides @Singleton\r\n    fun provideSupabaseClient(): SupabaseClient = createSupabaseClient(\r\n        supabaseUrl = BuildConfig.SUPABASE_URL,\r\n        supabaseKey = BuildConfig.SUPABASE_ANON_KEY\r\n    ) {\r\n        install(Auth) {\r\n            scheme = \"myapp\"\r\n            host = \"callback\"\r\n        }\r\n        install(Postgrest)\r\n        install(Realtime)\r\n        install(Storage)\r\n        install(Functions)\r\n    }\r\n}\r\n```\r\n\r\n## Rule 2: Auth — the UnauthorizedRestException fix\r\n\r\n```kotlin\r\n// THE most common Supabase Android bug — fixed here permanently\r\n\r\n// ❌ Wrong — causes UnauthorizedRestException on Edge Functions\r\nval client = createSupabaseClient(url, anonKey) {\r\n    install(Auth)  // persistSession defaults to true — getUser() returns null\r\n}\r\nval user = client.auth.currentUserOrNull()  // null after hot restart\r\n\r\n// ✅ Correct — when calling Edge Functions with user JWT\r\nsuspend fun callSecureEdgeFunction(jwt: String): MyResponse {\r\n    val userClient = createSupabaseClient(supabaseUrl, supabaseAnonKey) {\r\n        install(Auth) {\r\n            persistSession = false       // ← REQUIRED for JWT passthrough\r\n        }\r\n        install(Functions)\r\n    }\r\n    userClient.auth.getUser(jwt)         // ← pass jwt directly, always\r\n    return userClient.functions.invoke(\"my-function\")\r\n}\r\n\r\n// ✅ Standard auth — sign in and observe session\r\nclass AuthRepositoryImpl @Inject constructor(\r\n    private val supabase: SupabaseClient\r\n) : AuthRepository {\r\n    override val sessionStatus: Flow<SessionStatus>\r\n        get() = supabase.auth.sessionStatus\r\n\r\n    override suspend fun signIn(email: String, password: String): Result<Unit> = runCatching {\r\n        supabase.auth.signInWith(Email) {\r\n            this.email = email\r\n            this.password = password\r\n        }\r\n    }\r\n\r\n    override suspend fun signUp(email: String, password: String): Result<Unit> = runCatching {\r\n        supabase.auth.signUpWith(Email) {\r\n            this.email = email\r\n            this.password = password\r\n        }\r\n    }\r\n\r\n    override suspend fun signOut() { supabase.auth.signOut() }\r\n\r\n    override fun currentUser(): UserInfo? = supabase.auth.currentUserOrNull()\r\n}\r\n```\r\n\r\n## Rule 3: Postgrest — database queries\r\n\r\n```kotlin\r\n// ✅ DTOs must be @Serializable\r\n@Serializable\r\ndata class ItemDto(\r\n    val id: String,\r\n    val title: String,\r\n    val description: String,\r\n    @SerialName(\"user_id\") val userId: String,\r\n    @SerialName(\"is_favorite\") val isFavorite: Boolean = false,\r\n    @SerialName(\"created_at\") val createdAt: String\r\n)\r\n\r\n// ✅ CRUD operations\r\nclass ItemRemoteDataSource @Inject constructor(\r\n    private val supabase: SupabaseClient\r\n) {\r\n    // SELECT\r\n    suspend fun getItems(userId: String): List<ItemDto> =\r\n        supabase.from(\"items\")\r\n            .select {\r\n                filter { eq(\"user_id\", userId) }\r\n                order(\"created_at\", Order.DESCENDING)\r\n                limit(50)\r\n            }\r\n            .decodeList()   // ← always decodeList() for arrays\r\n\r\n    // SELECT single\r\n    suspend fun getItem(id: String): ItemDto =\r\n        supabase.from(\"items\")\r\n            .select { filter { eq(\"id\", id) } }\r\n            .decodeSingle()\r\n\r\n    // INSERT\r\n    suspend fun createItem(item: ItemDto): ItemDto =\r\n        supabase.from(\"items\")\r\n            .insert(item) { select() }\r\n            .decodeSingle()\r\n\r\n    // UPDATE\r\n    suspend fun updateItem(id: String, updates: Map<String, Any>): ItemDto =\r\n        supabase.from(\"items\")\r\n            .update(updates) {\r\n                filter { eq(\"id\", id) }\r\n                select()\r\n            }\r\n            .decodeSingle()\r\n\r\n    // UPSERT\r\n    suspend fun upsertItem(item: ItemDto) {\r\n        supabase.from(\"items\").upsert(item)\r\n    }\r\n\r\n    // DELETE\r\n    suspend fun deleteItem(id: String) {\r\n        supabase.from(\"items\").delete { filter { eq(\"id\", id) } }\r\n    }\r\n}\r\n```\r\n\r\n## Rule 4: Edge Functions — correct invocation\r\n\r\n```kotlin\r\n// config.toml on server side\r\n// [functions.my-function]\r\n// verify_jwt = false    ← set when you handle JWT manually\r\n\r\n// ✅ Invoke with typed request/response\r\n@Serializable data class MyRequest(val itemId: String, val action: String)\r\n@Serializable data class MyResponse(val success: Boolean, val message: String)\r\n\r\nsuspend fun invokeFunction(request: MyRequest): Result<MyResponse> = runCatching {\r\n    supabase.functions.invoke(\r\n        function = \"my-function\",\r\n        body = request\r\n    )\r\n}\r\n```\r\n\r\n## Rule 5: Realtime subscriptions\r\n\r\n```kotlin\r\n// ✅ Subscribe to table changes\r\nfun getItemsRealtime(userId: String): Flow<List<ItemDto>> = flow {\r\n    val channel = supabase.realtime.createChannel(\"items-$userId\")\r\n    \r\n    channel.postgresChangeFlow<PostgresAction>(schema = \"public\") {\r\n        table = \"items\"\r\n        filter = \"user_id=eq.$userId\"\r\n    }.collect { action ->\r\n        // Re-fetch full list on any change\r\n        emit(getItems(userId))\r\n    }\r\n    \r\n    supabase.realtime.connect()\r\n    channel.subscribe()\r\n    awaitCancellation()\r\n}.onCompletion {\r\n    supabase.realtime.removeAllChannels()\r\n}\r\n```\r\n\r\n## Rule 6: Storage — file upload\r\n\r\n```kotlin\r\n// ✅ Upload file to Supabase Storage\r\nsuspend fun uploadImage(bucket: String, path: String, data: ByteArray): String {\r\n    supabase.storage.from(bucket).upload(path, data) {\r\n        upsert = true\r\n        contentType = ContentType.Image.JPEG\r\n    }\r\n    return supabase.storage.from(bucket).publicUrl(path)\r\n}\r\n```\r\n\r\n## Common Mistakes\r\n\r\n❌ Missing `persistSession = false` when calling Edge Functions with user JWT\r\n❌ Using `decodeSingle()` on a list query — use `decodeList()`\r\n❌ Not making DTOs `@Serializable` — runtime crash on decode\r\n❌ Calling Supabase on Main thread — all operations are suspend, call from coroutine\r\n❌ `verify_jwt = true` on Edge Function that handles JWT manually — 401 error\r\n❌ Not setting up Row Level Security — all users can see all data","tags":["supabase","android","agent","skills","piyushverma0","agent-skills","ai-agent","antigravity","claude-code","codex","cursor","gemini-cli"],"capabilities":["skill","source-piyushverma0","skill-supabase-android","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/supabase-android","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 (7,095 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:11.051Z","embedding":null,"createdAt":"2026-05-18T13:14:50.922Z","updatedAt":"2026-05-18T19:09:11.051Z","lastSeenAt":"2026-05-18T19:09:11.051Z","tsv":"'1':131,135 '18':35 '2':176 '27':4 '3':323 '3.0.1':48 '3.0.2':46 '4':474 '401':667 '5':30,534 '50':394 '6':583 'action':507,565 'agent':9 'ai':8,32 'alway':252,396 'android':3,5,38,106,113,186 'anon':159 'anonkey':201 'array':399 'auth':16,70,75,162,177,203,238,259 'auth-kt':74 'authrepositori':273 'authrepositoryimpl':266 'awaitcancel':579 'bill':29 'bodi':531 'bom':52,56 'boolean':356,515 'bucket':596,604,614 'bug':187 'buildconfig.supabase':155,158 'bytearray':601 'call':220,623,645,654 'callback':166 'callsecureedgefunct':228 'caus':192 'chang':541,573 'channel':550 'channel.postgreschangeflow':554 'channel.subscribe':578 'class':145,265,334,366,501,511 'claud':10 'client':112,132,140,198 'client.auth.currentuserornull':213 'code':11 'codex':12 'collect':564 'common':184,617 'config.toml':480 'constructor':268,369 'contenttyp':610 'contenttype.image.jpeg':611 'coroutin':656 'correct':138,218,477 'crash':642 'creat':359,390 'createdat':362 'createitem':419 'createsupabasecli':153,199,234 'crud':364 'currentus':319 'cursor':13 'data':333,500,510,600,607,680 'databas':325 'day':36 'decod':644 'decodelist':395,397,636 'decodesingl':415,428,449,630 'default':205 'delet':460,468 'deleteitem':463 'descript':343 'design':19 'direct':251 'dtos':328,639 'edg':195,221,475,624,661 'email':284,291,293,300,307,309 'emit':574 'eq':385,412,445,470,562 'error':18,668 'fals':240,357,488,621 'favorit':353 'fetch':568 'file':585,589 'filter':384,411,444,469,559 'fitgenz':31 'fix':14,180,188 'flow':277,546,548 'full':569 'fun':150,227,282,298,314,318,376,403,418,431,452,462,520,542,594 'function':97,102,174,196,222,246,257,476,485,527,530,625,662 'functions-kt':101 'functions.my':484 'get':278 'getitem':377,404,575 'getitemsrealtim':543 'getus':208 'group':53,62,71,80,89,98,107 'handl':492,664 'hilt':17 'host':165 'hot':216 'id':337,347,387,405,413,414,433,446,447,464,471,472,561 'implement':117,120,122,124,126,128 'inconsist':20 'initi':133 'inject':267,368 'insert':416,425 'instal':161,167,169,171,173,202,237,245 'installin':143 'invoc':478 'invok':495 'invokefunct':521 'io.github.jan-tennert.supabase':54,63,72,81,90,99 'io.ktor':108 'isfavorit':355 'item':382,409,420,424,426,441,454,457,459,467,552,558 'itemdto':335,407,421,422,439,455 'itemid':504 'itemremotedatasourc':367 'jwt':225,229,243,248,250,487,493,628,658,665 'kapt':21 'key':160 'kotlin':116,137,181,327,479,537,587 'ksp':22 'kt':41,67,76,85,94,103 'ktor':47,105,111,115 'ktor-android':104 'ktor-client-android':110 'level':673 'librari':49 'libs.ktor.android':129 'libs.supabase.auth':123 'libs.supabase.bom':119 'libs.supabase.functions':127 'libs.supabase.postgrest':121 'libs.supabase.realtime':125 'limit':393 'list':380,547,570,633 'main':648 'make':638 'manual':494,666 'map':436 'messag':517 'miss':23,619 'mistak':136,618 'modul':142 'must':329 'my-funct':255,528 'myapp':164 'myrequest':502,523 'myrespons':231,512 'name':55,64,73,82,91,100,109 'null':210,214 'object':146 'observ':263 'oncomplet':580 'oper':365,651 'order':389 'order.descending':392 'overrid':274,280,296,312,317 'pass':249 'passthrough':244 'password':286,295,302,311 'path':598,606,616 'perman':190 'persistsess':204,239,620 'platform':118 'postgrest':61,66,168,324 'postgrest-kt':65 'privat':269,370 'provid':148 'providesupabasecli':151 'public':556 'publicurl':615 'queri':326,634 're':567 're-fetch':566 'realtim':79,84,170,535 'realtime-kt':83 'reduc':26 'request':522,532 'request/response':498 'requir':241 'restart':217 'result':288,304,524 'return':209,253,612 'row':672 'rule':130,175,322,473,533,582 'runcatch':289,305,525 'runtim':641 'schema':555 'scheme':163 'secur':674 'see':678 'select':374,383,400,410,427,448 'serializ':331,332,499,509,640 'serialnam':345,351,358 'server':482 'session':264 'sessionstatus':276 'set':489,670 'setup':42,141 'ship':33 'side':483 'sign':260 'signin':283 'signout':315 'signup':299 'singl':401 'singleton':149 'singletoncompon':144 'skill':6 'skill-supabase-android' 'source-piyushverma0' 'standard':258 'state':25 'storag':88,93,172,584,592 'storage-kt':92 'string':230,285,287,301,303,338,341,344,350,363,379,406,434,437,465,505,508,518,545,597,599,602 'subscrib':538 'subscript':536 'success':514 'supabas':2,15,37,40,45,51,58,60,69,78,87,96,139,185,271,372,591,646 'supabase-android':1 'supabase-auth':68 'supabase-bom':50 'supabase-funct':95 'supabase-kt':39 'supabase-postgrest':59 'supabase-realtim':77 'supabase-storag':86 'supabase.auth.currentuserornull':321 'supabase.auth.sessionstatus':279 'supabase.auth.signinwith':290 'supabase.auth.signout':316 'supabase.auth.signupwith':306 'supabase.from':381,408,423,440,456,466 'supabase.functions.invoke':526 'supabase.realtime.connect':577 'supabase.realtime.createchannel':551 'supabase.realtime.removeallchannels':581 'supabase.storage.from':603,613 'supabaseanonkey':236 'supabasecli':152,272,373 'supabasekey':157 'supabasemodul':147 'supabaseurl':154,235 'suspend':226,281,297,313,375,402,417,430,451,461,519,593,653 'tabl':540,557 'this.email':292,308 'this.password':294,310 'thread':649 'titl':340 'token':28 'toml':43 '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' 'true':207,609,659 'type':497 'uistat':24 'unauthorizedrestexcept':179,193 'updat':429,435,442,443 'updateitem':432 'upload':586,588,605 'uploadimag':595 'upsert':450,458,608 'upsertitem':453 'url':156,200 'use':629,635 'user':212,224,346,386,560,627,676 'usercli':233 'userclient.auth.getuser':247 'userclient.functions.invoke':254 'userid':349,378,388,544,553,563,576 'userinfo':320 'val':197,211,232,270,275,336,339,342,348,354,361,371,503,506,513,516,549 'verifi':486,657 'version':44 'version.ref':57,114 'wrong':191","prices":[{"id":"13600e4e-a9c0-492e-b6b2-384c9f6f8c67","listingId":"e5dec98d-47a9-4138-848c-77e5ebf038ac","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:50.922Z"}],"sources":[{"listingId":"e5dec98d-47a9-4138-848c-77e5ebf038ac","source":"github","sourceId":"piyushverma0/android-agent-skills/supabase-android","sourceUrl":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/supabase-android","isPrimary":false,"firstSeenAt":"2026-05-18T13:14:50.922Z","lastSeenAt":"2026-05-18T19:09:11.051Z"}],"details":{"listingId":"e5dec98d-47a9-4138-848c-77e5ebf038ac","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"piyushverma0","slug":"supabase-android","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":"a4663a137ba35a03fec504fc517e7788623f9809","skill_md_path":"skills/supabase-android/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/supabase-android"},"layout":"multi","source":"github","category":"android-agent-skills","frontmatter":{},"skills_sh_url":"https://skills.sh/piyushverma0/android-agent-skills/supabase-android"},"updatedAt":"2026-05-18T19:09:11.051Z"}}