{"id":"616c1d7b-c458-478e-9b5f-35bf954fb18d","shortId":"qtdXvZ","kind":"skill","title":"permissions","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 Runtime Permissions\r\n\r\n## Rule 1: Always use Accompanist Permissions in Compose\r\n\r\n```kotlin\r\nimplementation(libs.accompanist.permissions)\r\n```\r\n\r\n```kotlin\r\n// ✅ Single permission\r\n@OptIn(ExperimentalPermissionsApi::class)\r\n@Composable\r\nfun CameraScreen() {\r\n    val cameraPermission = rememberPermissionState(Manifest.permission.CAMERA)\r\n\r\n    when {\r\n        cameraPermission.status.isGranted -> {\r\n            CameraPreview()\r\n        }\r\n        cameraPermission.status.shouldShowRationale -> {\r\n            PermissionRationale(\r\n                title = \"Camera required\",\r\n                description = \"This feature requires camera access to scan items.\",\r\n                onRequest = { cameraPermission.launchPermissionRequest() }\r\n            )\r\n        }\r\n        else -> {\r\n            // First time or permanently denied\r\n            LaunchedEffect(Unit) { cameraPermission.launchPermissionRequest() }\r\n        }\r\n    }\r\n}\r\n\r\n// ✅ Multiple permissions\r\n@OptIn(ExperimentalPermissionsApi::class)\r\n@Composable\r\nfun LocationScreen() {\r\n    val locationPermissions = rememberMultiplePermissionsState(\r\n        listOf(\r\n            Manifest.permission.ACCESS_FINE_LOCATION,\r\n            Manifest.permission.ACCESS_COARSE_LOCATION\r\n        )\r\n    )\r\n\r\n    when {\r\n        locationPermissions.allPermissionsGranted -> LocationContent()\r\n        locationPermissions.shouldShowRationale -> PermissionRationale(\r\n            title = \"Location required\",\r\n            description = \"We need your location to show nearby items.\",\r\n            onRequest = { locationPermissions.launchMultiplePermissionRequest() }\r\n        )\r\n        else -> LaunchedEffect(Unit) { locationPermissions.launchMultiplePermissionRequest() }\r\n    }\r\n}\r\n```\r\n\r\n## Rule 2: Rationale dialog — always explain before requesting\r\n\r\n```kotlin\r\n// ✅ Standard permission rationale composable\r\n@Composable\r\nfun PermissionRationale(\r\n    title: String,\r\n    description: String,\r\n    onRequest: () -> Unit,\r\n    onDismiss: () -> Unit = {},\r\n    onOpenSettings: (() -> Unit)? = null\r\n) {\r\n    AlertDialog(\r\n        onDismissRequest = onDismiss,\r\n        icon = { Icon(Icons.Default.Info, contentDescription = null) },\r\n        title = { Text(title) },\r\n        text = { Text(description) },\r\n        confirmButton = {\r\n            TextButton(onClick = onRequest) { Text(\"Grant permission\") }\r\n        },\r\n        dismissButton = {\r\n            if (onOpenSettings != null) {\r\n                TextButton(onClick = onOpenSettings) { Text(\"Open settings\") }\r\n            } else {\r\n                TextButton(onClick = onDismiss) { Text(\"Not now\") }\r\n            }\r\n        }\r\n    )\r\n}\r\n\r\n// ✅ Open app settings when permanently denied\r\nfun Context.openAppSettings() {\r\n    startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {\r\n        data = Uri.fromParts(\"package\", packageName, null)\r\n    })\r\n}\r\n```\r\n\r\n## Rule 3: Declare permissions in AndroidManifest\r\n\r\n```xml\r\n<!-- Required in AndroidManifest.xml before requesting -->\r\n<uses-permission android:name=\"android.permission.CAMERA\" />\r\n<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />\r\n<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" />\r\n<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />\r\n\r\n<!-- Storage — Android 13+ uses granular permissions -->\r\n<uses-permission android:name=\"android.permission.READ_MEDIA_IMAGES\" />\r\n<uses-permission android:name=\"android.permission.READ_MEDIA_VIDEO\" />\r\n<!-- Android 12 and below -->\r\n<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"\r\n    android:maxSdkVersion=\"32\" />\r\n\r\n<!-- Notifications — required for Android 13+ -->\r\n<uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\r\n\r\n<!-- Bluetooth — Android 12+ split permissions -->\r\n<uses-permission android:name=\"android.permission.BLUETOOTH_SCAN\"\r\n    android:usesPermissionFlags=\"neverForLocation\" />\r\n<uses-permission android:name=\"android.permission.BLUETOOTH_CONNECT\" />\r\n```\r\n\r\n## Rule 4: Background location — separate request flow\r\n\r\n```kotlin\r\n// ✅ Request foreground location first, then background separately\r\n@OptIn(ExperimentalPermissionsApi::class)\r\n@Composable\r\nfun BackgroundLocationScreen() {\r\n    val foregroundLocation = rememberMultiplePermissionsState(\r\n        listOf(Manifest.permission.ACCESS_FINE_LOCATION)\r\n    )\r\n    val backgroundLocation = rememberPermissionState(\r\n        Manifest.permission.ACCESS_BACKGROUND_LOCATION\r\n    )\r\n\r\n    when {\r\n        !foregroundLocation.allPermissionsGranted -> {\r\n            // Step 1: request foreground first\r\n            RequestForegroundLocation(onRequest = { foregroundLocation.launchMultiplePermissionRequest() })\r\n        }\r\n        !backgroundLocation.status.isGranted -> {\r\n            // Step 2: only after foreground granted\r\n            RequestBackgroundLocation(onRequest = { backgroundLocation.launchPermissionRequest() })\r\n        }\r\n        else -> BackgroundLocationContent()\r\n    }\r\n}\r\n```\r\n\r\n## Common Mistakes\r\n\r\n❌ Requesting permission without rationale — users deny without explanation\r\n❌ Requesting multiple unrelated permissions at once — request only when needed\r\n❌ Not handling permanently denied state — always offer \"Open Settings\" option\r\n❌ Requesting background location before foreground — Android rejects this\r\n❌ Missing permission in AndroidManifest — request will always be denied\r\n❌ Requesting WRITE_EXTERNAL_STORAGE on Android 10+ — not needed, use scoped storage","tags":["permissions","android","agent","skills","piyushverma0","agent-skills","ai-agent","antigravity","claude-code","codex","cursor","gemini-cli"],"capabilities":["skill","source-piyushverma0","skill-permissions","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/permissions","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 (5,511 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.555Z","embedding":null,"createdAt":"2026-05-18T13:14:50.289Z","updatedAt":"2026-05-18T19:09:10.555Z","lastSeenAt":"2026-05-18T19:09:10.555Z","tsv":"'1':39,260 '10':332 '18':33 '2':132,269 '27':2 '3':217 '4':224 '5':28 'access':75 'accompanist':42 'agent':7 'ai':6,30 'alertdialog':158 'alway':40,135,304,323 'android':3,35,314,331 'androidmanifest':221,320 'app':197 'appli':210 'applic':207 'auth':14 'background':225,236,255,310 'backgroundloc':252 'backgroundlocation.launchpermissionrequest':276 'backgroundlocation.status.isgranted':267 'backgroundlocationcont':278 'backgroundlocationscreen':243 'bill':27 'camera':68,74 'camerapermiss':59 'camerapermission.launchpermissionrequest':80,89 'camerapermission.status.isgranted':63 'camerapermission.status.shouldshowrationale':65 'camerapreview':64 'camerascreen':57 'class':54,94,240 'claud':8 'coars':106 'code':9 'codex':10 'common':279 'compos':45,55,95,143,144,241 'confirmbutton':172 'contentdescript':164 'context.openappsettings':203 'cursor':11 'data':211 'day':34 'declar':218 'deni':86,201,286,302,325 'descript':70,116,149,171 'design':17 'detail':208 'dialog':134 'dismissbutton':179 'els':81,127,189,277 'error':16 'experimentalpermissionsapi':53,93,239 'explain':136 'explan':288 'extern':328 'featur':72 'fine':103,249 'first':82,234,263 'fitgenz':29 'fix':12 'flow':229 'foreground':232,262,272,313 'foregroundloc':245 'foregroundlocation.allpermissionsgranted':258 'foregroundlocation.launchmultiplepermissionrequest':266 'fun':56,96,145,202,242 'grant':177,273 'handl':300 'hilt':15 'icon':161,162 'icons.default.info':163 'implement':47 'inconsist':18 'intent':205 'item':78,124 'kapt':19 'kotlin':46,49,139,230 'ksp':20 'launchedeffect':87,128 'libs.accompanist.permissions':48 'listof':101,247 'locat':104,107,114,120,226,233,250,256,311 'locationcont':110 'locationpermiss':99 'locationpermissions.allpermissionsgranted':109 'locationpermissions.launchmultiplepermissionrequest':126,130 'locationpermissions.shouldshowrationale':111 'locationscreen':97 'manifest.permission.access':102,105,248,254 'manifest.permission.camera':61 'miss':21,317 'mistak':280 'multipl':90,290 'nearbi':123 'need':118,298,334 'null':157,165,182,215 'offer':305 'onclick':174,184,191 'ondismiss':153,160,192 'ondismissrequest':159 'onopenset':155,181,185 'onrequest':79,125,151,175,265,275 'open':187,196,306 'optin':52,92,238 'option':308 'packag':213 'packagenam':214 'perman':85,200,301 'permiss':1,37,43,51,91,141,178,219,282,292,318 'permissionrational':66,112,146 'rational':133,142,284 'reduc':24 'reject':315 'remembermultiplepermissionsst':100,246 'rememberpermissionst':60,253 'request':138,228,231,261,281,289,295,309,321,326 'requestbackgroundloc':274 'requestforegroundloc':264 'requir':69,73,115 'rule':38,131,216,223 'runtim':36 'scan':77 'scope':336 'separ':227,237 'set':188,198,209,307 'settings.action':206 'ship':31 'show':122 'singl':50 'skill':4 'skill-permissions' 'source-piyushverma0' 'standard':140 'startact':204 'state':23,303 'step':259,268 'storag':329,337 'string':148,150 'supabas':13 'text':167,169,170,176,186,193 'textbutton':173,183,190 'time':83 'titl':67,113,147,166,168 'token':26 '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' 'uistat':22 'unit':88,129,152,154,156 'unrel':291 'uri.fromparts':212 'use':41,335 'user':285 'val':58,98,244,251 'without':283,287 'write':327 'xml':222","prices":[{"id":"ce9db9fa-b062-4aaf-85ba-e2966e78f5d3","listingId":"616c1d7b-c458-478e-9b5f-35bf954fb18d","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.289Z"}],"sources":[{"listingId":"616c1d7b-c458-478e-9b5f-35bf954fb18d","source":"github","sourceId":"piyushverma0/android-agent-skills/permissions","sourceUrl":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/permissions","isPrimary":false,"firstSeenAt":"2026-05-18T13:14:50.289Z","lastSeenAt":"2026-05-18T19:09:10.555Z"}],"details":{"listingId":"616c1d7b-c458-478e-9b5f-35bf954fb18d","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"piyushverma0","slug":"permissions","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":"5aca18857ef1e2bc50998ebb9763741a6cca8f48","skill_md_path":"skills/permissions/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/piyushverma0/android-agent-skills/tree/main/skills/permissions"},"layout":"multi","source":"github","category":"android-agent-skills","frontmatter":{},"skills_sh_url":"https://skills.sh/piyushverma0/android-agent-skills/permissions"},"updatedAt":"2026-05-18T19:09:10.555Z"}}