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,241 @@
1
+ ---
2
+ name: retrofit
3
+ description: >
4
+ Retrofit HTTP client setup and usage for Android.
5
+ Load this skill when setting up Retrofit, defining API interfaces,
6
+ configuring converters, adding interceptors, handling responses,
7
+ or integrating Retrofit with Kotlinx Serialization and OkHttp.
8
+ ---
9
+
10
+ # Retrofit
11
+
12
+ ## Overview
13
+
14
+ Retrofit is a type-safe HTTP client for Android that turns API interfaces into callable Kotlin functions. It integrates with OkHttp for the underlying HTTP layer and Kotlinx Serialization for JSON parsing.
15
+
16
+ ---
17
+
18
+ ## Core Principles
19
+
20
+ - Define one `Retrofit` instance per base URL — use DI to provide it
21
+ - API interfaces return `Result<T>` or a sealed type — never raw response objects
22
+ - Never call Retrofit from UI or ViewModel directly — go through Repository
23
+ - Use `suspend` functions for all API calls — no callbacks or `Call<T>`
24
+ - Handle HTTP errors explicitly — don't let them surface as exceptions silently
25
+
26
+ ---
27
+
28
+ ## Setup
29
+
30
+ ```toml
31
+ # libs.versions.toml
32
+ [versions]
33
+ retrofit = "2.11.0"
34
+ okhttp = "4.12.0"
35
+
36
+ [libraries]
37
+ retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
38
+ retrofit-kotlinx-serialization = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit" }
39
+ okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
40
+ okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
41
+ ```
42
+
43
+ ```kotlin
44
+ // build.gradle.kts
45
+ dependencies {
46
+ implementation(libs.retrofit)
47
+ implementation(libs.retrofit.kotlinx.serialization)
48
+ implementation(libs.okhttp)
49
+ implementation(libs.okhttp.logging)
50
+ }
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Retrofit Instance
56
+
57
+ ```kotlin
58
+ // ✅ Single Retrofit instance provided via DI
59
+ @Provides
60
+ @Singleton
61
+ fun provideRetrofit(okHttpClient: OkHttpClient, json: Json): Retrofit {
62
+ return Retrofit.Builder()
63
+ .baseUrl(BuildConfig.BASE_URL)
64
+ .client(okHttpClient)
65
+ .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
66
+ .build()
67
+ }
68
+
69
+ @Provides
70
+ @Singleton
71
+ fun provideOkHttpClient(
72
+ authInterceptor: AuthInterceptor,
73
+ loggingInterceptor: HttpLoggingInterceptor
74
+ ): OkHttpClient {
75
+ return OkHttpClient.Builder()
76
+ .addInterceptor(authInterceptor)
77
+ .addInterceptor(loggingInterceptor)
78
+ .connectTimeout(30, TimeUnit.SECONDS)
79
+ .readTimeout(30, TimeUnit.SECONDS)
80
+ .writeTimeout(30, TimeUnit.SECONDS)
81
+ .build()
82
+ }
83
+
84
+ @Provides
85
+ @Singleton
86
+ fun provideLoggingInterceptor(): HttpLoggingInterceptor {
87
+ return HttpLoggingInterceptor().apply {
88
+ level = if (BuildConfig.DEBUG)
89
+ HttpLoggingInterceptor.Level.BODY
90
+ else
91
+ HttpLoggingInterceptor.Level.NONE
92
+ }
93
+ }
94
+ ```
95
+
96
+ ---
97
+
98
+ ## API Interface
99
+
100
+ ```kotlin
101
+ // ✅ Suspend functions, typed responses
102
+ interface UserApi {
103
+
104
+ @GET("users")
105
+ suspend fun getUsers(): List<UserDto>
106
+
107
+ @GET("users/{id}")
108
+ suspend fun getUser(@Path("id") id: String): UserDto
109
+
110
+ @POST("users")
111
+ suspend fun createUser(@Body body: CreateUserRequest): UserDto
112
+
113
+ @PUT("users/{id}")
114
+ suspend fun updateUser(
115
+ @Path("id") id: String,
116
+ @Body body: UpdateUserRequest
117
+ ): UserDto
118
+
119
+ @DELETE("users/{id}")
120
+ suspend fun deleteUser(@Path("id") id: String)
121
+
122
+ @GET("users")
123
+ suspend fun searchUsers(
124
+ @Query("q") query: String,
125
+ @Query("page") page: Int = 1,
126
+ @Query("limit") limit: Int = 20
127
+ ): PagedResponse<UserDto>
128
+ }
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Response Handling in Repository
134
+
135
+ ```kotlin
136
+ // ✅ Wrap API calls in runCatching — map to domain Result
137
+ class UserRepositoryImpl @Inject constructor(
138
+ private val api: UserApi,
139
+ private val mapper: UserMapper
140
+ ) : UserRepository {
141
+
142
+ override suspend fun getUser(id: String): Result<User> = runCatching {
143
+ val dto = api.getUser(id)
144
+ mapper.toDomain(dto)
145
+ }
146
+
147
+ override suspend fun getUsers(): Result<List<User>> = runCatching {
148
+ api.getUsers().map { mapper.toDomain(it) }
149
+ }
150
+ }
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Auth Interceptor
156
+
157
+ ```kotlin
158
+ // ✅ Attach token via interceptor — not in each API call
159
+ class AuthInterceptor @Inject constructor(
160
+ private val tokenProvider: TokenProvider
161
+ ) : Interceptor {
162
+
163
+ override fun intercept(chain: Interceptor.Chain): Response {
164
+ val token = tokenProvider.getToken()
165
+ val request = if (token != null) {
166
+ chain.request().newBuilder()
167
+ .addHeader("Authorization", "Bearer $token")
168
+ .build()
169
+ } else {
170
+ chain.request()
171
+ }
172
+ return chain.proceed(request)
173
+ }
174
+ }
175
+ ```
176
+
177
+ ---
178
+
179
+ ## HTTP Error Handling
180
+
181
+ ```kotlin
182
+ // ✅ Custom call adapter or extension for HTTP errors
183
+ suspend fun <T> safeApiCall(call: suspend () -> T): Result<T> = runCatching {
184
+ call()
185
+ }.recoverCatching { throwable ->
186
+ when (throwable) {
187
+ is HttpException -> throw ApiException(
188
+ code = throwable.code(),
189
+ message = throwable.response()?.errorBody()?.string() ?: "HTTP error"
190
+ )
191
+ is IOException -> throw NetworkException("No internet connection")
192
+ else -> throw throwable
193
+ }
194
+ }
195
+
196
+ // ✅ Usage in repository
197
+ override suspend fun getUser(id: String): Result<User> =
198
+ safeApiCall { api.getUser(id) }
199
+ .map { mapper.toDomain(it) }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Multipart / File Upload
205
+
206
+ ```kotlin
207
+ // ✅ Upload file with multipart
208
+ @Multipart
209
+ @POST("users/{id}/avatar")
210
+ suspend fun uploadAvatar(
211
+ @Path("id") id: String,
212
+ @Part avatar: MultipartBody.Part
213
+ ): AvatarDto
214
+
215
+ // ✅ Build MultipartBody.Part from file
216
+ fun File.toMultipartPart(partName: String): MultipartBody.Part {
217
+ val requestBody = asRequestBody("image/*".toMediaType())
218
+ return MultipartBody.Part.createFormData(partName, name, requestBody)
219
+ }
220
+ ```
221
+
222
+ ---
223
+
224
+ ## Anti-Patterns
225
+
226
+ - Creating Retrofit instance per API call — expensive, use singleton
227
+ - Returning `Response<T>` from API interface — wrap in `Result` at the repository level
228
+ - Using `Call<T>` instead of `suspend` — unnecessary callback complexity
229
+ - Adding auth token manually in each API function — use interceptor
230
+ - Catching exceptions in ViewModel — handle in repository or use case
231
+ - Using `Gson` converter — use Kotlinx Serialization
232
+
233
+ ---
234
+
235
+ ## Related Skills
236
+
237
+ - `okhttp` — OkHttp client, interceptors, and connection config
238
+ - `serialization` — JSON parsing with Kotlinx Serialization
239
+ - `authentication` — token management and refresh
240
+ - `retry-backoff` — retry logic for failed requests
241
+ - `dto-mapping` — mapping API responses to domain models
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: retry-backoff
3
+ description: >
4
+ Retry and exponential backoff strategies for network requests in Android.
5
+ Load this skill when implementing automatic retry for failed requests,
6
+ configuring backoff intervals, handling idempotency, or building
7
+ resilient network layers.
8
+ ---
9
+
10
+ # Retry / Backoff
11
+
12
+ ## Overview
13
+ Retry with exponential backoff improves resilience against transient network failures and temporary server errors. The key constraint is that only **idempotent** requests (GET, PUT, DELETE) should be retried automatically — POST requests must not be retried without explicit idempotency tokens.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Only retry **idempotent** operations automatically — never POST without idempotency token
20
+ - Use **exponential backoff** with jitter — prevents thundering herd on server recovery
21
+ - Set a **maximum retry count** — don't retry indefinitely
22
+ - Do not retry **4xx errors** (except 429) — they indicate a client error, not transient failure
23
+ - Retry **5xx errors** and network errors (`IOException`) — these are transient
24
+
25
+ ---
26
+
27
+ ## Retryable Conditions
28
+
29
+ | Condition | Retry? |
30
+ |-----------|--------|
31
+ | `IOException` (no internet) | ✅ Yes |
32
+ | `500` Server Error | ✅ Yes |
33
+ | `502` Bad Gateway | ✅ Yes |
34
+ | `503` Service Unavailable | ✅ Yes |
35
+ | `429` Too Many Requests | ✅ Yes (after Retry-After) |
36
+ | `401` Unauthorized | ❌ No (handle via token refresh) |
37
+ | `404` Not Found | ❌ No |
38
+ | `400` Bad Request | ❌ No |
39
+
40
+ ---
41
+
42
+ ## Coroutine Retry Extension
43
+
44
+ ```kotlin
45
+ // ✅ Generic retry with exponential backoff
46
+ suspend fun <T> withRetry(
47
+ maxAttempts: Int = 3,
48
+ initialDelay: Long = 500L,
49
+ maxDelay: Long = 10_000L,
50
+ factor: Double = 2.0,
51
+ shouldRetry: (Throwable) -> Boolean = ::isRetryable,
52
+ block: suspend () -> T
53
+ ): T {
54
+ var currentDelay = initialDelay
55
+ repeat(maxAttempts - 1) { attempt ->
56
+ runCatching { block() }
57
+ .onSuccess { return it }
58
+ .onFailure { throwable ->
59
+ if (!shouldRetry(throwable)) throw throwable
60
+ val jitter = (0..200).random()
61
+ delay(currentDelay + jitter)
62
+ currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
63
+ }
64
+ }
65
+ return block() // last attempt — let it throw
66
+ }
67
+
68
+ fun isRetryable(throwable: Throwable): Boolean {
69
+ return when (throwable) {
70
+ is IOException -> true
71
+ is HttpException -> throwable.code() in listOf(500, 502, 503, 504)
72
+ else -> false
73
+ }
74
+ }
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Usage in Repository
80
+
81
+ ```kotlin
82
+ // ✅ Wrap idempotent calls with retry
83
+ override suspend fun getUser(id: String): Result<User> = runCatching {
84
+ withRetry(maxAttempts = 3) {
85
+ api.getUser(id)
86
+ }.let { mapper.toDomain(it) }
87
+ }
88
+
89
+ // ✅ Non-idempotent — no automatic retry
90
+ override suspend fun createUser(user: User): Result<User> = runCatching {
91
+ val dto = api.createUser(mapper.toRequest(user)) // no withRetry
92
+ mapper.toDomain(dto)
93
+ }
94
+ ```
95
+
96
+ ---
97
+
98
+ ## OkHttp Retry Interceptor
99
+
100
+ ```kotlin
101
+ // ✅ Network-level retry for transient failures
102
+ class RetryInterceptor(
103
+ private val maxRetries: Int = 3
104
+ ) : Interceptor {
105
+ override fun intercept(chain: Interceptor.Chain): Response {
106
+ val request = chain.request()
107
+
108
+ // Only retry idempotent methods
109
+ if (request.method !in listOf("GET", "PUT", "DELETE", "HEAD")) {
110
+ return chain.proceed(request)
111
+ }
112
+
113
+ var attempt = 0
114
+ var lastException: IOException? = null
115
+
116
+ while (attempt < maxRetries) {
117
+ try {
118
+ val response = chain.proceed(request)
119
+ if (response.isSuccessful || response.code !in listOf(500, 502, 503)) {
120
+ return response
121
+ }
122
+ response.close()
123
+ } catch (e: IOException) {
124
+ lastException = e
125
+ }
126
+
127
+ attempt++
128
+ if (attempt < maxRetries) {
129
+ val delay = (500L * 2.0.pow(attempt)).toLong().coerceAtMost(10_000L)
130
+ Thread.sleep(delay)
131
+ }
132
+ }
133
+
134
+ throw lastException ?: IOException("Max retries exceeded")
135
+ }
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## 429 Too Many Requests Handling
142
+
143
+ ```kotlin
144
+ // ✅ Respect Retry-After header on 429
145
+ suspend fun <T> withRateLimitRetry(block: suspend () -> T): T {
146
+ while (true) {
147
+ val result = runCatching { block() }
148
+ result.onSuccess { return it }
149
+ result.onFailure { throwable ->
150
+ if (throwable is HttpException && throwable.code() == 429) {
151
+ val retryAfter = throwable.response()
152
+ ?.headers()
153
+ ?.get("Retry-After")
154
+ ?.toLongOrNull()
155
+ ?: 5L
156
+ delay(retryAfter * 1000)
157
+ } else {
158
+ throw throwable
159
+ }
160
+ }
161
+ }
162
+ }
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Anti-Patterns
168
+
169
+ - Retrying POST requests without idempotency keys — creates duplicate resources
170
+ - Retrying 4xx errors — client errors won't resolve on retry
171
+ - Fixed delay between retries — causes thundering herd; use backoff + jitter
172
+ - Infinite retries — always cap with `maxAttempts`
173
+ - Retrying inside the ViewModel — retry belongs in the repository or data source layer
174
+
175
+ ---
176
+
177
+ ## Related Skills
178
+ - `okhttp` — network interceptor for retry at the HTTP level
179
+ - `retrofit` — repository-level error handling
180
+ - `fallback-strategy` — what to do after all retries are exhausted
181
+ - `rate-limiting` — handling 429 responses
@@ -0,0 +1,196 @@
1
+ ---
2
+ name: server-sent-events
3
+ description: >
4
+ Server-Sent Events (SSE) implementation in Android.
5
+ Load this skill when consuming a one-way real-time event stream from
6
+ the server, integrating SSE with AI streaming responses, handling
7
+ reconnection, or modeling SSE as a Flow.
8
+ ---
9
+
10
+ # Server-Sent Events (SSE)
11
+
12
+ ## Overview
13
+ Server-Sent Events (SSE) is a one-way, server-to-client streaming protocol over HTTP. Unlike WebSockets, SSE is unidirectional and uses standard HTTP — making it simpler and more firewall-friendly. It is commonly used for AI streaming responses (LLMs), live notifications, and real-time dashboards.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - SSE is one-way — use WebSocket if bidirectional communication is needed
20
+ - Model the SSE stream as a `Flow` — integrates naturally with coroutines
21
+ - Handle reconnection — SSE streams drop on network changes
22
+ - Parse the `data:` field of each event — ignore comment lines (starting with `:`)
23
+ - Close the connection when the lifecycle owner is destroyed
24
+
25
+ ---
26
+
27
+ ## OkHttp SSE Implementation
28
+
29
+ ```kotlin
30
+ // ✅ SSE client wrapping OkHttp EventSource
31
+ class SseClient @Inject constructor(
32
+ private val okHttpClient: OkHttpClient
33
+ ) {
34
+ fun connect(url: String, headers: Map<String, String> = emptyMap()): Flow<SseEvent> =
35
+ callbackFlow {
36
+ val request = Request.Builder()
37
+ .url(url)
38
+ .apply { headers.forEach { (k, v) -> addHeader(k, v) } }
39
+ .addHeader("Accept", "text/event-stream")
40
+ .addHeader("Cache-Control", "no-cache")
41
+ .build()
42
+
43
+ val listener = object : EventSourceListener() {
44
+ override fun onEvent(
45
+ eventSource: EventSource,
46
+ id: String?,
47
+ type: String?,
48
+ data: String
49
+ ) {
50
+ trySend(SseEvent.Data(id = id, type = type, data = data))
51
+ }
52
+
53
+ override fun onFailure(
54
+ eventSource: EventSource,
55
+ t: Throwable?,
56
+ response: Response?
57
+ ) {
58
+ close(t ?: IOException("SSE connection failed"))
59
+ }
60
+
61
+ override fun onClosed(eventSource: EventSource) {
62
+ trySend(SseEvent.Closed)
63
+ close()
64
+ }
65
+ }
66
+
67
+ val eventSource = EventSources.createFactory(okHttpClient)
68
+ .newEventSource(request, listener)
69
+
70
+ awaitClose { eventSource.cancel() }
71
+ }
72
+ }
73
+
74
+ sealed interface SseEvent {
75
+ data class Data(val id: String?, val type: String?, val data: String) : SseEvent
76
+ data object Closed : SseEvent
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Manual SSE Parsing (Without EventSource)
83
+
84
+ ```kotlin
85
+ // ✅ Parse SSE stream manually from OkHttp response body
86
+ fun parseSSEStream(responseBody: ResponseBody): Flow<String> = flow {
87
+ responseBody.source().use { source ->
88
+ while (!source.exhausted()) {
89
+ val line = source.readUtf8Line() ?: break
90
+ when {
91
+ line.startsWith("data: ") -> emit(line.removePrefix("data: "))
92
+ line.startsWith(":") -> Unit // comment — ignore
93
+ line.isEmpty() -> Unit // event separator — ignore
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ // ✅ Usage with Retrofit streaming endpoint
100
+ @Streaming
101
+ @GET("chat/stream")
102
+ suspend fun streamChat(@Query("prompt") prompt: String): ResponseBody
103
+ ```
104
+
105
+ ---
106
+
107
+ ## AI Streaming Response
108
+
109
+ ```kotlin
110
+ // ✅ Stream LLM token-by-token response
111
+ class ChatRepository @Inject constructor(
112
+ private val sseClient: SseClient,
113
+ private val tokenStorage: TokenStorage
114
+ ) {
115
+ fun streamCompletion(prompt: String): Flow<String> {
116
+ return sseClient.connect(
117
+ url = "${BuildConfig.BASE_URL}chat/completions",
118
+ headers = mapOf(
119
+ "Authorization" to "Bearer ${tokenStorage.getAccessToken()}"
120
+ )
121
+ )
122
+ .filterIsInstance<SseEvent.Data>()
123
+ .filter { it.data != "[DONE]" }
124
+ .mapNotNull { event ->
125
+ runCatching {
126
+ json.decodeFromString<CompletionChunkDto>(event.data)
127
+ .choices.firstOrNull()?.delta?.content
128
+ }.getOrNull()
129
+ }
130
+ }
131
+ }
132
+
133
+ // ✅ Collect in ViewModel — append tokens to build response
134
+ class ChatViewModel @Inject constructor(
135
+ private val chatRepository: ChatRepository
136
+ ) : ViewModel() {
137
+
138
+ private val _response = MutableStateFlow("")
139
+ val response: StateFlow<String> = _response.asStateFlow()
140
+
141
+ private val _isStreaming = MutableStateFlow(false)
142
+ val isStreaming: StateFlow<Boolean> = _isStreaming.asStateFlow()
143
+
144
+ fun sendMessage(prompt: String) {
145
+ viewModelScope.launch {
146
+ _response.value = ""
147
+ _isStreaming.value = true
148
+ chatRepository.streamCompletion(prompt)
149
+ .onCompletion { _isStreaming.value = false }
150
+ .catch { _isStreaming.value = false }
151
+ .collect { token ->
152
+ _response.value += token
153
+ }
154
+ }
155
+ }
156
+ }
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Reconnection
162
+
163
+ ```kotlin
164
+ // ✅ Auto-reconnect on failure with backoff
165
+ fun connectWithReconnect(url: String, scope: CoroutineScope): Flow<SseEvent> = flow {
166
+ var attempt = 0
167
+ while (true) {
168
+ try {
169
+ emitAll(sseClient.connect(url))
170
+ attempt = 0 // reset on clean close
171
+ } catch (e: IOException) {
172
+ val delay = minOf(1_000L * (2.0.pow(attempt)).toLong(), 30_000L)
173
+ delay(delay)
174
+ attempt++
175
+ }
176
+ }
177
+ }.flowOn(Dispatchers.IO)
178
+ ```
179
+
180
+ ---
181
+
182
+ ## Anti-Patterns
183
+
184
+ - Using SSE for bidirectional communication — use WebSocket instead
185
+ - Not closing the EventSource on lifecycle destroy — leaks the connection
186
+ - Accumulating all SSE events in memory — process and discard each event
187
+ - Not handling the `[DONE]` sentinel for AI streams — continues waiting indefinitely
188
+ - Blocking the main thread while reading the stream — always on IO dispatcher
189
+
190
+ ---
191
+
192
+ ## Related Skills
193
+ - `websocket` — bidirectional alternative to SSE
194
+ - `okhttp` — HTTP client for SSE connections
195
+ - `flow` — modeling streams with Kotlin Flow
196
+ - `retrofit` — streaming response body with `@Streaming`