android-sdd 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/dist/index.js +143 -0
  2. package/package.json +27 -0
  3. package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
  4. package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
  5. package/skills/Android Platform/Configuration/SKILL.md +201 -0
  6. package/skills/Android Platform/Filesystem/SKILL.md +216 -0
  7. package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
  8. package/skills/Android Platform/Manifest/SKILL.md +226 -0
  9. package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
  10. package/skills/Android Platform/Resources/SKILL.md +234 -0
  11. package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
  12. package/skills/Android Platform/State Restoration/SKILL.md +210 -0
  13. package/skills/Architecture/Bounded Context/SKILL.md +207 -0
  14. package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
  15. package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
  16. package/skills/Architecture/Entity Design/SKILL.md +243 -0
  17. package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
  18. package/skills/Architecture/MVI/SKILL.md +224 -0
  19. package/skills/Architecture/MVVM/SKILL.md +198 -0
  20. package/skills/Architecture/Modularization/SKILL.md +194 -0
  21. package/skills/Architecture/Offline First/SKILL.md +249 -0
  22. package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
  23. package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
  24. package/skills/Architecture/State Management/SKILL.md +229 -0
  25. package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
  26. package/skills/Architecture/Use Case Design/SKILL.md +244 -0
  27. package/skills/Architecture/Value Object/SKILL.md +226 -0
  28. package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
  29. package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
  30. package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
  31. package/skills/Build System/Build Cache/SKILL.md +233 -0
  32. package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
  33. package/skills/Build System/Build Variant/SKILL.md +215 -0
  34. package/skills/Build System/Convention Plugin/SKILL.md +288 -0
  35. package/skills/Build System/Dependency Management/SKILL.md +261 -0
  36. package/skills/Build System/Gradle/SKILL.md +284 -0
  37. package/skills/Build System/Incremental Build/SKILL.md +199 -0
  38. package/skills/Build System/KAPT/SKILL.md +198 -0
  39. package/skills/Build System/KSP/SKILL.md +263 -0
  40. package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
  41. package/skills/Build System/Specialized/C++/SKILL.md +308 -0
  42. package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
  43. package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
  44. package/skills/Build System/Version Catalog/SKILL.md +304 -0
  45. package/skills/Concurrency/Background Processing/SKILL.md +185 -0
  46. package/skills/Concurrency/Channel/SKILL.md +207 -0
  47. package/skills/Concurrency/Coroutine/SKILL.md +200 -0
  48. package/skills/Concurrency/Flow/SKILL.md +179 -0
  49. package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
  50. package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
  51. package/skills/Concurrency/StateFlow/SKILL.md +175 -0
  52. package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
  53. package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
  54. package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
  55. package/skills/Core Language/DSL/SKILL.md +186 -0
  56. package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
  57. package/skills/Core Language/Immutability/SKILL.md +156 -0
  58. package/skills/Core Language/KMP/SKILL.md +182 -0
  59. package/skills/Core Language/Kotlin/SKILL.md +187 -0
  60. package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
  61. package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
  62. package/skills/Core Language/Serialization/SKILL.md +191 -0
  63. package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
  64. package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
  65. package/skills/Data Layer/DAO/SKILL.md +225 -0
  66. package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
  67. package/skills/Data Layer/DataStore/SKILL.md +264 -0
  68. package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
  69. package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
  70. package/skills/Data Layer/File Storage/SKILL.md +247 -0
  71. package/skills/Data Layer/Indexing/SKILL.md +184 -0
  72. package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
  73. package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
  74. package/skills/Data Layer/Migration/SKILL.md +243 -0
  75. package/skills/Data Layer/Paging/SKILL.md +264 -0
  76. package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
  77. package/skills/Data Layer/Room/SKILL.md +244 -0
  78. package/skills/Data Layer/SQLite/SKILL.md +255 -0
  79. package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
  80. package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
  81. package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
  82. package/skills/Dependency Injection/Koin/SKILL.md +282 -0
  83. package/skills/Developer Experience/Detekt/SKILL.md +272 -0
  84. package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
  85. package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
  86. package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
  87. package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
  88. package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
  89. package/skills/Media/Audio/SKILL.md +257 -0
  90. package/skills/Media/Camera/SKILL.md +229 -0
  91. package/skills/Media/CameraX/SKILL.md +295 -0
  92. package/skills/Media/ExoPlayer/SKILL.md +258 -0
  93. package/skills/Media/Video/SKILL.md +228 -0
  94. package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
  95. package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
  96. package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
  97. package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
  98. package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
  99. package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
  100. package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
  101. package/skills/Navigation/Navigation/SKILL.md +215 -0
  102. package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
  103. package/skills/Networking/API Contract/SKILL.md +220 -0
  104. package/skills/Networking/Authentication/SKILL.md +210 -0
  105. package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
  106. package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
  107. package/skills/Networking/Ktor/SKILL.md +219 -0
  108. package/skills/Networking/Multipart Upload/SKILL.md +213 -0
  109. package/skills/Networking/OkHttp/SKILL.md +193 -0
  110. package/skills/Networking/REST/SKILL.md +178 -0
  111. package/skills/Networking/Rate Limiting/SKILL.md +170 -0
  112. package/skills/Networking/Retrofit/SKILL.md +241 -0
  113. package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
  114. package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
  115. package/skills/Networking/WebSocket/SKILL.md +224 -0
  116. package/skills/Observability/Crash Reporting/SKILL.md +219 -0
  117. package/skills/Observability/Logging/SKILL.md +168 -0
  118. package/skills/Observability/Metrics/SKILL.md +227 -0
  119. package/skills/Observability/Structured Logging/SKILL.md +234 -0
  120. package/skills/Performance/ANR Prevention/SKILL.md +192 -0
  121. package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
  122. package/skills/Performance/App Startup/SKILL.md +183 -0
  123. package/skills/Performance/Baseline Profile/SKILL.md +205 -0
  124. package/skills/Performance/Battery Optimization/SKILL.md +192 -0
  125. package/skills/Performance/Benchmark/SKILL.md +182 -0
  126. package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
  127. package/skills/Performance/Compose Optimization/SKILL.md +187 -0
  128. package/skills/Performance/Heap Management/SKILL.md +184 -0
  129. package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
  130. package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
  131. package/skills/Performance/Rendering Performance/SKILL.md +205 -0
  132. package/skills/Performance/Startup Optimization/SKILL.md +219 -0
  133. package/skills/Security/Biometric/SKILL.md +224 -0
  134. package/skills/Security/Certificate Transparency/SKILL.md +158 -0
  135. package/skills/Security/Cryptography/SKILL.md +244 -0
  136. package/skills/Security/Encrypted Storage/SKILL.md +273 -0
  137. package/skills/Security/Frida Detection/SKILL.md +230 -0
  138. package/skills/Security/Hook Detection/SKILL.md +197 -0
  139. package/skills/Security/Keystore/SKILL.md +272 -0
  140. package/skills/Security/Network Security Config/SKILL.md +186 -0
  141. package/skills/Security/Obfuscation/SKILL.md +226 -0
  142. package/skills/Security/Proguard/SKILL.md +202 -0
  143. package/skills/Security/R8/SKILL.md +234 -0
  144. package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
  145. package/skills/Security/Root Detection/SKILL.md +220 -0
  146. package/skills/Security/Secure Networking/SKILL.md +220 -0
  147. package/skills/System Integration/AlarmManager/SKILL.md +182 -0
  148. package/skills/System Integration/App Widget/SKILL.md +182 -0
  149. package/skills/System Integration/Deep Link/SKILL.md +187 -0
  150. package/skills/System Integration/Foreground Service/SKILL.md +212 -0
  151. package/skills/System Integration/Notification/SKILL.md +237 -0
  152. package/skills/System Integration/WorkManager/SKILL.md +256 -0
  153. package/skills/System Integration/clipboard/SKILL.md +155 -0
  154. package/skills/System Integration/share-intent/SKILL.md +182 -0
  155. package/skills/Testing/Compose Testing/SKILL.md +296 -0
  156. package/skills/Testing/Espresso/SKILL.md +292 -0
  157. package/skills/Testing/Fake Data/SKILL.md +245 -0
  158. package/skills/Testing/Integration Testing/SKILL.md +288 -0
  159. package/skills/Testing/Mocking/SKILL.md +229 -0
  160. package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
  161. package/skills/Testing/UI Testing/SKILL.md +293 -0
  162. package/skills/Testing/Unit Testing/SKILL.md +309 -0
  163. package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
  164. package/skills/UI System/Compose/SKILL.md +296 -0
  165. package/skills/UI System/Compose Animation/SKILL.md +281 -0
  166. package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
  167. package/skills/UI System/Compose Navigation/SKILL.md +255 -0
  168. package/skills/UI System/Compose Performance/SKILL.md +274 -0
  169. package/skills/UI System/Design System/SKILL.md +217 -0
  170. package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
  171. package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
  172. package/skills/UI System/Loading Strategy/SKILL.md +254 -0
  173. package/skills/UI System/Material 3/SKILL.md +279 -0
  174. package/skills/UI System/RTL/SKILL.md +179 -0
  175. package/src/index.ts +182 -0
  176. package/tsconfig.json +19 -0
@@ -0,0 +1,237 @@
1
+ ---
2
+ name: notification
3
+ description: >
4
+ Android notification system implementation.
5
+ Load this skill when creating notification channels, building
6
+ notifications, handling notification permissions, showing progress
7
+ notifications, or adding actions and deep links to notifications.
8
+ ---
9
+
10
+ # Notification
11
+
12
+ ## Overview
13
+ Android notifications require a notification channel (API 26+), a POST_NOTIFICATIONS permission request (API 33+), and a properly built `Notification` object. Notifications can contain actions, deep link intents, progress indicators, and custom layouts.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Create notification channels **once** at app startup — not on every notification
20
+ - Request `POST_NOTIFICATIONS` permission on Android 13+ before showing notifications
21
+ - Use `NotificationCompat` — not the platform `Notification` directly
22
+ - Always provide a `contentIntent` — tapping the notification should open the relevant screen
23
+ - Group related notifications with a summary notification
24
+
25
+ ---
26
+
27
+ ## Permissions
28
+
29
+ ```xml
30
+ <!-- AndroidManifest.xml -->
31
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
32
+ ```
33
+
34
+ ```kotlin
35
+ // ✅ Request permission on Android 13+
36
+ class MainActivity : ComponentActivity() {
37
+ private val requestPermissionLauncher = registerForActivityResult(
38
+ ActivityResultContracts.RequestPermission()
39
+ ) { isGranted ->
40
+ if (!isGranted) {
41
+ // Show rationale or disable notification features
42
+ }
43
+ }
44
+
45
+ fun requestNotificationPermission() {
46
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
47
+ if (ContextCompat.checkSelfPermission(
48
+ this, Manifest.permission.POST_NOTIFICATIONS
49
+ ) != PackageManager.PERMISSION_GRANTED
50
+ ) {
51
+ requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
52
+ }
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Notification Channels
61
+
62
+ ```kotlin
63
+ // ✅ Create channels once at app startup
64
+ class NotificationChannelSetup @Inject constructor(
65
+ @ApplicationContext private val context: Context
66
+ ) {
67
+ fun createChannels() {
68
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
69
+
70
+ val manager = context.getSystemService(NotificationManager::class.java)
71
+
72
+ manager.createNotificationChannel(
73
+ NotificationChannel(
74
+ CHANNEL_MESSAGES,
75
+ "Messages",
76
+ NotificationManager.IMPORTANCE_HIGH
77
+ ).apply {
78
+ description = "New message notifications"
79
+ enableVibration(true)
80
+ }
81
+ )
82
+
83
+ manager.createNotificationChannel(
84
+ NotificationChannel(
85
+ CHANNEL_SYNC,
86
+ "Sync",
87
+ NotificationManager.IMPORTANCE_LOW
88
+ ).apply {
89
+ description = "Background sync status"
90
+ }
91
+ )
92
+ }
93
+
94
+ companion object {
95
+ const val CHANNEL_MESSAGES = "channel_messages"
96
+ const val CHANNEL_SYNC = "channel_sync"
97
+ }
98
+ }
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Basic Notification
104
+
105
+ ```kotlin
106
+ // ✅ Simple notification with deep link intent
107
+ fun showMessageNotification(context: Context, message: Message) {
108
+ val deepLinkIntent = Intent(
109
+ Intent.ACTION_VIEW,
110
+ Uri.parse("https://example.com/messages/${message.id}")
111
+ ).apply {
112
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
113
+ }
114
+
115
+ val pendingIntent = TaskStackBuilder.create(context).run {
116
+ addNextIntentWithParentStack(deepLinkIntent)
117
+ getPendingIntent(
118
+ message.id.hashCode(),
119
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
120
+ )
121
+ }
122
+
123
+ val notification = NotificationCompat.Builder(context, CHANNEL_MESSAGES)
124
+ .setSmallIcon(R.drawable.ic_message)
125
+ .setContentTitle(message.senderName)
126
+ .setContentText(message.preview)
127
+ .setStyle(NotificationCompat.BigTextStyle().bigText(message.body))
128
+ .setContentIntent(pendingIntent)
129
+ .setAutoCancel(true)
130
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
131
+ .build()
132
+
133
+ NotificationManagerCompat.from(context)
134
+ .notify(message.id.hashCode(), notification)
135
+ }
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Notification with Actions
141
+
142
+ ```kotlin
143
+ // ✅ Action buttons on notification
144
+ val replyIntent = PendingIntent.getBroadcast(
145
+ context,
146
+ 0,
147
+ Intent(context, ReplyReceiver::class.java).putExtra("message_id", messageId),
148
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
149
+ )
150
+
151
+ val markReadIntent = PendingIntent.getBroadcast(
152
+ context,
153
+ 1,
154
+ Intent(context, MarkReadReceiver::class.java).putExtra("message_id", messageId),
155
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
156
+ )
157
+
158
+ NotificationCompat.Builder(context, CHANNEL_MESSAGES)
159
+ .addAction(R.drawable.ic_reply, "Reply", replyIntent)
160
+ .addAction(R.drawable.ic_check, "Mark Read", markReadIntent)
161
+ .build()
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Progress Notification
167
+
168
+ ```kotlin
169
+ // ✅ Update progress notification
170
+ fun showProgressNotification(context: Context, progress: Int, done: Boolean) {
171
+ val notification = NotificationCompat.Builder(context, CHANNEL_SYNC)
172
+ .setSmallIcon(R.drawable.ic_sync)
173
+ .setContentTitle("Uploading file")
174
+ .setContentText(if (done) "Upload complete" else "$progress%")
175
+ .setProgress(100, progress, false)
176
+ .setOngoing(!done)
177
+ .setOnlyAlertOnce(true) // ✅ don't re-alert on every update
178
+ .build()
179
+
180
+ NotificationManagerCompat.from(context).notify(NOTIFICATION_ID_UPLOAD, notification)
181
+
182
+ if (done) {
183
+ // Remove progress after delay
184
+ Handler(Looper.getMainLooper()).postDelayed({
185
+ NotificationManagerCompat.from(context).cancel(NOTIFICATION_ID_UPLOAD)
186
+ }, 3_000)
187
+ }
188
+ }
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Grouped Notifications
194
+
195
+ ```kotlin
196
+ // ✅ Group multiple notifications with a summary
197
+ val GROUP_KEY = "com.example.MESSAGES"
198
+
199
+ // Individual notifications
200
+ messages.forEach { message ->
201
+ val notification = NotificationCompat.Builder(context, CHANNEL_MESSAGES)
202
+ .setSmallIcon(R.drawable.ic_message)
203
+ .setContentTitle(message.sender)
204
+ .setContentText(message.preview)
205
+ .setGroup(GROUP_KEY)
206
+ .build()
207
+ manager.notify(message.id.hashCode(), notification)
208
+ }
209
+
210
+ // Summary notification
211
+ val summary = NotificationCompat.Builder(context, CHANNEL_MESSAGES)
212
+ .setSmallIcon(R.drawable.ic_message)
213
+ .setStyle(NotificationCompat.InboxStyle()
214
+ .setSummaryText("${messages.size} new messages"))
215
+ .setGroup(GROUP_KEY)
216
+ .setGroupSummary(true)
217
+ .build()
218
+ manager.notify(SUMMARY_ID, summary)
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Anti-Patterns
224
+
225
+ - Creating notification channels on every `notify()` call — create once at startup
226
+ - Not providing a `contentIntent` — tapping the notification does nothing
227
+ - Using `IMPORTANCE_HIGH` for background sync notifications — disrupts the user
228
+ - Not setting `FLAG_IMMUTABLE` on `PendingIntent` — crashes on Android 12+
229
+ - Showing notifications without requesting `POST_NOTIFICATIONS` on Android 13+
230
+
231
+ ---
232
+
233
+ ## Related Skills
234
+ - `deep-navigation` — building deep link intents for notifications
235
+ - `foreground-service` — persistent service notification
236
+ - `alarmmanager` — triggering notifications at a specific time
237
+ - `firebase-messaging` — push notifications via FCM
@@ -0,0 +1,256 @@
1
+ ---
2
+ name: workmanager
3
+ description: >
4
+ WorkManager for guaranteed background task execution in Android.
5
+ Load this skill when scheduling deferrable work, running periodic tasks,
6
+ chaining workers, handling constraints, observing work state,
7
+ or running tasks that must survive app and device restarts.
8
+ ---
9
+
10
+ # WorkManager
11
+
12
+ ## Overview
13
+ WorkManager is the recommended solution for deferrable, guaranteed background work in Android. It handles Doze mode, app death, and device restarts transparently. It supports one-time and periodic work, constraints, chaining, and progress reporting.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Use WorkManager for work that **must complete** even if the app is killed
20
+ - Use `CoroutineWorker` — not `Worker` — for suspend-friendly work
21
+ - Set **constraints** to avoid running on metered networks or low battery
22
+ - Use **unique work** to prevent duplicate tasks
23
+ - Observe work state via `WorkInfo` — never poll manually
24
+
25
+ ---
26
+
27
+ ## Setup
28
+
29
+ ```toml
30
+ # libs.versions.toml
31
+ [versions]
32
+ workmanager = "2.9.0"
33
+
34
+ [libraries]
35
+ workmanager = { module = "androidx.work:work-runtime-ktx", version.ref = "workmanager" }
36
+ ```
37
+
38
+ ---
39
+
40
+ ## CoroutineWorker
41
+
42
+ ```kotlin
43
+ // ✅ Standard worker
44
+ class SyncWorker(
45
+ context: Context,
46
+ params: WorkerParameters
47
+ ) : CoroutineWorker(context, params) {
48
+
49
+ override suspend fun doWork(): Result {
50
+ return try {
51
+ val userId = inputData.getString(KEY_USER_ID)
52
+ ?: return Result.failure()
53
+
54
+ syncRepository.syncUser(userId)
55
+ Result.success()
56
+ } catch (e: Exception) {
57
+ if (runAttemptCount < 3) Result.retry()
58
+ else Result.failure(
59
+ workDataOf("error" to (e.message ?: "Unknown"))
60
+ )
61
+ }
62
+ }
63
+
64
+ companion object {
65
+ const val KEY_USER_ID = "user_id"
66
+ }
67
+ }
68
+ ```
69
+
70
+ ---
71
+
72
+ ## One-Time Work
73
+
74
+ ```kotlin
75
+ // ✅ Enqueue one-time work
76
+ fun scheduleSyncUser(userId: String) {
77
+ val request = OneTimeWorkRequestBuilder<SyncWorker>()
78
+ .setInputData(workDataOf(SyncWorker.KEY_USER_ID to userId))
79
+ .setConstraints(
80
+ Constraints.Builder()
81
+ .setRequiredNetworkType(NetworkType.CONNECTED)
82
+ .build()
83
+ )
84
+ .setBackoffCriteria(
85
+ BackoffPolicy.EXPONENTIAL,
86
+ WorkRequest.MIN_BACKOFF_MILLIS,
87
+ TimeUnit.MILLISECONDS
88
+ )
89
+ .addTag("sync")
90
+ .build()
91
+
92
+ WorkManager.getInstance(context).enqueueUniqueWork(
93
+ "sync_user_$userId",
94
+ ExistingWorkPolicy.KEEP, // don't replace if already queued
95
+ request
96
+ )
97
+ }
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Periodic Work
103
+
104
+ ```kotlin
105
+ // ✅ Periodic sync — minimum interval is 15 minutes
106
+ fun schedulePeriodicSync() {
107
+ val request = PeriodicWorkRequestBuilder<SyncWorker>(
108
+ repeatInterval = 1,
109
+ repeatIntervalTimeUnit = TimeUnit.HOURS
110
+ )
111
+ .setConstraints(
112
+ Constraints.Builder()
113
+ .setRequiredNetworkType(NetworkType.CONNECTED)
114
+ .setRequiresBatteryNotLow(true)
115
+ .build()
116
+ )
117
+ .build()
118
+
119
+ WorkManager.getInstance(context).enqueueUniquePeriodicWork(
120
+ "periodic_sync",
121
+ ExistingPeriodicWorkPolicy.KEEP,
122
+ request
123
+ )
124
+ }
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Chaining Workers
130
+
131
+ ```kotlin
132
+ // ✅ Sequential chain
133
+ WorkManager.getInstance(context)
134
+ .beginUniqueWork(
135
+ "upload_pipeline",
136
+ ExistingWorkPolicy.REPLACE,
137
+ OneTimeWorkRequestBuilder<CompressWorker>().build()
138
+ )
139
+ .then(OneTimeWorkRequestBuilder<UploadWorker>().build())
140
+ .then(OneTimeWorkRequestBuilder<NotifyWorker>().build())
141
+ .enqueue()
142
+
143
+ // ✅ Parallel then merge
144
+ val syncA = OneTimeWorkRequestBuilder<SyncAWorker>().build()
145
+ val syncB = OneTimeWorkRequestBuilder<SyncBWorker>().build()
146
+ val merge = OneTimeWorkRequestBuilder<MergeWorker>().build()
147
+
148
+ WorkManager.getInstance(context)
149
+ .beginWith(listOf(syncA, syncB))
150
+ .then(merge)
151
+ .enqueue()
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Progress Reporting
157
+
158
+ ```kotlin
159
+ // ✅ Report progress from worker
160
+ class UploadWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
161
+ override suspend fun doWork(): Result {
162
+ val files = getFilesToUpload()
163
+ files.forEachIndexed { index, file ->
164
+ uploadFile(file)
165
+ val progress = ((index + 1) * 100) / files.size
166
+ setProgress(workDataOf("progress" to progress))
167
+ }
168
+ return Result.success()
169
+ }
170
+ }
171
+
172
+ // ✅ Observe progress in ViewModel
173
+ val workInfo: Flow<WorkInfo?> = workManager
174
+ .getWorkInfosForUniqueWorkFlow("upload_pipeline")
175
+ .map { it.firstOrNull() }
176
+
177
+ // In composable
178
+ val progress = workInfo?.progress?.getInt("progress", 0) ?: 0
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Observing Work State
184
+
185
+ ```kotlin
186
+ // ✅ Observe in ViewModel
187
+ class SyncViewModel @Inject constructor(
188
+ private val workManager: WorkManager
189
+ ) : ViewModel() {
190
+
191
+ val syncState: StateFlow<SyncUiState> = workManager
192
+ .getWorkInfosForUniqueWorkFlow("periodic_sync")
193
+ .map { workInfos ->
194
+ when (workInfos.firstOrNull()?.state) {
195
+ WorkInfo.State.RUNNING -> SyncUiState.Syncing
196
+ WorkInfo.State.SUCCEEDED -> SyncUiState.Success
197
+ WorkInfo.State.FAILED -> SyncUiState.Failed
198
+ else -> SyncUiState.Idle
199
+ }
200
+ }
201
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), SyncUiState.Idle)
202
+ }
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Hilt Integration
208
+
209
+ ```kotlin
210
+ // ✅ Inject dependencies into Worker with Hilt
211
+ @HiltWorker
212
+ class SyncWorker @AssistedInject constructor(
213
+ @Assisted context: Context,
214
+ @Assisted params: WorkerParameters,
215
+ private val syncRepository: SyncRepository
216
+ ) : CoroutineWorker(context, params) {
217
+
218
+ override suspend fun doWork(): Result {
219
+ return try {
220
+ syncRepository.sync()
221
+ Result.success()
222
+ } catch (e: Exception) {
223
+ if (runAttemptCount < 3) Result.retry() else Result.failure()
224
+ }
225
+ }
226
+ }
227
+
228
+ // In Application class
229
+ @HiltAndroidApp
230
+ class App : Application(), Configuration.Provider {
231
+ @Inject lateinit var workerFactory: HiltWorkerFactory
232
+
233
+ override val workManagerConfiguration
234
+ get() = Configuration.Builder()
235
+ .setWorkerFactory(workerFactory)
236
+ .build()
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Anti-Patterns
243
+
244
+ - Using `Worker` instead of `CoroutineWorker` — blocks the thread
245
+ - Not using unique work — creates duplicate background tasks
246
+ - Not setting network constraints for network-dependent workers
247
+ - Doing UI updates directly inside a Worker — use WorkInfo observation
248
+ - Using WorkManager for immediate short tasks — use coroutines instead
249
+
250
+ ---
251
+
252
+ ## Related Skills
253
+ - `background-processing` — choosing the right background tool
254
+ - `foreground-service` — long-running user-visible work
255
+ - `hilt` — injecting dependencies into workers
256
+ - `coroutine` — coroutine fundamentals used inside CoroutineWorker
@@ -0,0 +1,155 @@
1
+ ---
2
+ name: clipboard
3
+ description: >
4
+ Clipboard read and write operations in Android.
5
+ Load this skill when copying text or URIs to the clipboard,
6
+ reading clipboard content, showing copy confirmation feedback,
7
+ handling clipboard access restrictions on Android 10+,
8
+ or implementing paste functionality.
9
+ ---
10
+
11
+ # Clipboard
12
+
13
+ ## Overview
14
+ The Android clipboard allows apps to copy and paste text, URIs, and other data. Since Android 10, apps can only read the clipboard when they are in the foreground. Android 13 shows a visual confirmation when an app reads clipboard content. Best practice is to write to clipboard and show immediate in-app feedback rather than reading silently.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Always show **visual feedback** after copying — toast or snackbar
21
+ - Never read clipboard silently in background — it's restricted on Android 10+
22
+ - Use `ClipData.newPlainText` for text — `ClipData.newUri` for URIs
23
+ - On Android 13+, the system shows a clipboard toast automatically — avoid double-toasting
24
+ - Sensitive content (passwords, tokens) should set `ClipDescription.EXTRA_IS_SENSITIVE`
25
+
26
+ ---
27
+
28
+ ## Writing to Clipboard
29
+
30
+ ```kotlin
31
+ // ✅ Copy text to clipboard
32
+ fun copyToClipboard(context: Context, text: String, label: String = "Copied text") {
33
+ val clipboard = context.getSystemService(ClipboardManager::class.java)
34
+ val clip = ClipData.newPlainText(label, text)
35
+ clipboard.setPrimaryClip(clip)
36
+
37
+ // ✅ Show feedback only on Android 12 and below
38
+ // Android 13+ shows system toast automatically
39
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
40
+ Toast.makeText(context, "Copied", Toast.LENGTH_SHORT).show()
41
+ }
42
+ }
43
+
44
+ // ✅ Copy sensitive content (password, token)
45
+ fun copySensitiveToClipboard(context: Context, text: String) {
46
+ val clipboard = context.getSystemService(ClipboardManager::class.java)
47
+ val clip = ClipData.newPlainText("sensitive", text).apply {
48
+ description.extras = PersistableBundle().apply {
49
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
50
+ putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
51
+ }
52
+ }
53
+ }
54
+ clipboard.setPrimaryClip(clip)
55
+ }
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Reading from Clipboard
61
+
62
+ ```kotlin
63
+ // ✅ Read clipboard — only when app is in foreground
64
+ fun readFromClipboard(context: Context): String? {
65
+ val clipboard = context.getSystemService(ClipboardManager::class.java)
66
+ val clip = clipboard.primaryClip ?: return null
67
+ if (clip.itemCount == 0) return null
68
+ return clip.getItemAt(0).coerceToText(context).toString()
69
+ }
70
+
71
+ // ✅ Check if clipboard has text before reading
72
+ fun hasTextInClipboard(context: Context): Boolean {
73
+ val clipboard = context.getSystemService(ClipboardManager::class.java)
74
+ return clipboard.hasPrimaryClip() &&
75
+ clipboard.primaryClipDescription?.hasMimeType("text/*") == true
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Clipboard in Compose
82
+
83
+ ```kotlin
84
+ // ✅ Copy on long press or button tap
85
+ @Composable
86
+ fun CopyableText(text: String) {
87
+ val context = LocalContext.current
88
+ val clipboardManager = LocalClipboardManager.current
89
+
90
+ Text(
91
+ text = text,
92
+ modifier = Modifier.clickable {
93
+ clipboardManager.setText(AnnotatedString(text))
94
+ // Show feedback on Android 12 and below
95
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
96
+ Toast.makeText(context, "Copied", Toast.LENGTH_SHORT).show()
97
+ }
98
+ }
99
+ )
100
+ }
101
+
102
+ // ✅ Paste button in Compose
103
+ @Composable
104
+ fun PasteButton(onPaste: (String) -> Unit) {
105
+ val clipboardManager = LocalClipboardManager.current
106
+
107
+ Button(onClick = {
108
+ val text = clipboardManager.getText()?.text ?: return@Button
109
+ onPaste(text)
110
+ }) {
111
+ Text("Paste")
112
+ }
113
+ }
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Clipboard Change Listener
119
+
120
+ ```kotlin
121
+ // ✅ Listen for clipboard changes (e.g. autofill OTP)
122
+ class OtpViewModel : ViewModel() {
123
+
124
+ private val clipboardListener = ClipboardManager.OnPrimaryClipChangedListener {
125
+ val text = readFromClipboard(context) ?: return@OnPrimaryClipChangedListener
126
+ if (text.matches(Regex("\\d{6}"))) {
127
+ _otpInput.value = text
128
+ }
129
+ }
130
+
131
+ fun registerClipboardListener(clipboard: ClipboardManager) {
132
+ clipboard.addPrimaryClipChangedListener(clipboardListener)
133
+ }
134
+
135
+ fun unregisterClipboardListener(clipboard: ClipboardManager) {
136
+ clipboard.removePrimaryClipChangedListener(clipboardListener)
137
+ }
138
+ }
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Anti-Patterns
144
+
145
+ - Reading clipboard in the background — restricted on Android 10+, throws SecurityException
146
+ - Not showing feedback after copy — user doesn't know the action succeeded
147
+ - Showing a toast on Android 13+ — the system already shows one, causing double notification
148
+ - Storing passwords in clipboard without `EXTRA_IS_SENSITIVE` — shown in clipboard history
149
+ - Reading clipboard on every resume — intrusive, user sees system clipboard access notification
150
+
151
+ ---
152
+
153
+ ## Related Skills
154
+ - `compose` — `LocalClipboardManager` in Compose
155
+ - `share-intent` — sharing content with other apps instead of clipboard