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,193 @@
1
+ ---
2
+ name: okhttp
3
+ description: >
4
+ OkHttp client configuration and interceptor patterns for Android.
5
+ Load this skill when configuring OkHttp, writing custom interceptors,
6
+ setting up caching, managing timeouts, handling connection pooling,
7
+ or debugging HTTP traffic.
8
+ ---
9
+
10
+ # OkHttp
11
+
12
+ ## Overview
13
+ OkHttp is the HTTP engine underneath Retrofit. It manages connection pooling, caching, interceptors, and TLS configuration. Correct OkHttp setup is critical for performance, security, and reliability.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - One `OkHttpClient` instance shared across all Retrofit instances — it manages the connection pool
20
+ - Use **interceptors** for cross-cutting concerns: auth, logging, retry, headers
21
+ - Configure **timeouts** explicitly — never rely on defaults in production
22
+ - Use `Cache` for GET responses to reduce bandwidth and improve offline resilience
23
+ - Never block the OkHttp thread inside an interceptor — use coroutines at the repository level
24
+
25
+ ---
26
+
27
+ ## Client Setup
28
+
29
+ ```kotlin
30
+ // ✅ Full OkHttpClient configuration
31
+ @Provides
32
+ @Singleton
33
+ fun provideOkHttpClient(
34
+ authInterceptor: AuthInterceptor,
35
+ loggingInterceptor: HttpLoggingInterceptor,
36
+ cache: Cache
37
+ ): OkHttpClient {
38
+ return OkHttpClient.Builder()
39
+ // Interceptors (order matters — application interceptors first)
40
+ .addInterceptor(authInterceptor)
41
+ .addInterceptor(loggingInterceptor)
42
+ // Timeouts
43
+ .connectTimeout(30, TimeUnit.SECONDS)
44
+ .readTimeout(30, TimeUnit.SECONDS)
45
+ .writeTimeout(30, TimeUnit.SECONDS)
46
+ .callTimeout(60, TimeUnit.SECONDS)
47
+ // Cache
48
+ .cache(cache)
49
+ // Connection pool
50
+ .connectionPool(ConnectionPool(5, 5, TimeUnit.MINUTES))
51
+ .build()
52
+ }
53
+
54
+ @Provides
55
+ @Singleton
56
+ fun provideCache(@ApplicationContext context: Context): Cache {
57
+ val cacheSize = 10L * 1024 * 1024 // 10 MB
58
+ return Cache(context.cacheDir, cacheSize)
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Interceptor Types
65
+
66
+ ```
67
+ Application Interceptors (addInterceptor)
68
+ → Run before the request hits the network
69
+ → See the original request and final response
70
+ → Good for: auth headers, logging, retry
71
+
72
+ Network Interceptors (addNetworkInterceptor)
73
+ → Run after redirects and cache decisions
74
+ → See the actual network request/response
75
+ → Good for: cache control headers, compression
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Custom Interceptors
81
+
82
+ ```kotlin
83
+ // ✅ Header interceptor — add common headers to every request
84
+ class CommonHeadersInterceptor @Inject constructor(
85
+ private val appVersionProvider: AppVersionProvider
86
+ ) : Interceptor {
87
+ override fun intercept(chain: Interceptor.Chain): Response {
88
+ val request = chain.request().newBuilder()
89
+ .addHeader("X-App-Version", appVersionProvider.version)
90
+ .addHeader("X-Platform", "android")
91
+ .addHeader("Accept-Language", Locale.getDefault().language)
92
+ .build()
93
+ return chain.proceed(request)
94
+ }
95
+ }
96
+
97
+ // ✅ Token refresh interceptor — handle 401 by refreshing token
98
+ class TokenRefreshInterceptor @Inject constructor(
99
+ private val tokenRepository: TokenRepository
100
+ ) : Interceptor {
101
+ override fun intercept(chain: Interceptor.Chain): Response {
102
+ val response = chain.proceed(chain.request())
103
+
104
+ if (response.code == 401) {
105
+ response.close()
106
+ val newToken = runBlocking { tokenRepository.refreshToken() }
107
+ if (newToken != null) {
108
+ val newRequest = chain.request().newBuilder()
109
+ .header("Authorization", "Bearer $newToken")
110
+ .build()
111
+ return chain.proceed(newRequest)
112
+ }
113
+ }
114
+ return response
115
+ }
116
+ }
117
+
118
+ // ✅ Cache control interceptor — force cache for specific endpoints
119
+ class CacheControlInterceptor : Interceptor {
120
+ override fun intercept(chain: Interceptor.Chain): Response {
121
+ val response = chain.proceed(chain.request())
122
+ return if (chain.request().url.pathSegments.contains("static")) {
123
+ response.newBuilder()
124
+ .header("Cache-Control", "public, max-age=86400") // 1 day
125
+ .build()
126
+ } else {
127
+ response
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Logging Interceptor
136
+
137
+ ```kotlin
138
+ // ✅ Debug-only logging — never log in release
139
+ @Provides
140
+ @Singleton
141
+ fun provideLoggingInterceptor(): HttpLoggingInterceptor {
142
+ return HttpLoggingInterceptor { message ->
143
+ Timber.tag("OkHttp").d(message)
144
+ }.apply {
145
+ level = if (BuildConfig.DEBUG)
146
+ HttpLoggingInterceptor.Level.BODY
147
+ else
148
+ HttpLoggingInterceptor.Level.NONE
149
+ }
150
+ }
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Response Extension
156
+
157
+ ```kotlin
158
+ // ✅ Safe response body parsing
159
+ fun Response.bodyOrThrow(): String {
160
+ return body?.string() ?: throw IOException("Empty response body")
161
+ }
162
+
163
+ fun Response.isSuccess(): Boolean = code in 200..299
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Timeout Strategy
169
+
170
+ | Timeout | Recommended | Use Case |
171
+ |---------|-------------|----------|
172
+ | `connectTimeout` | 15–30s | Establishing TCP connection |
173
+ | `readTimeout` | 30–60s | Reading response body |
174
+ | `writeTimeout` | 30–60s | Sending request body |
175
+ | `callTimeout` | 60–120s | Entire call including redirects |
176
+
177
+ ---
178
+
179
+ ## Anti-Patterns
180
+
181
+ - Creating a new `OkHttpClient` per request — destroys connection pool benefits
182
+ - Using `runBlocking` inside interceptors for non-token-refresh logic — blocks threads
183
+ - Logging request bodies in release builds — security risk
184
+ - Setting `callTimeout` too low for file upload/download endpoints
185
+ - Adding too many interceptors in sequence that each read the response body — body can only be read once
186
+
187
+ ---
188
+
189
+ ## Related Skills
190
+ - `retrofit` — Retrofit setup on top of OkHttp
191
+ - `authentication` — token management and refresh flow
192
+ - `certificate-pinning` — TLS security via OkHttp
193
+ - `retry-backoff` — retry interceptor patterns
@@ -0,0 +1,178 @@
1
+ ---
2
+ name: rest
3
+ description: >
4
+ REST API design principles and consumption patterns for Android.
5
+ Load this skill when designing API contracts, structuring HTTP requests,
6
+ handling standard HTTP status codes, modeling API responses,
7
+ or understanding REST conventions used in the data layer.
8
+ ---
9
+
10
+ # REST
11
+
12
+ ## Overview
13
+ REST (Representational State Transfer) is the standard architectural style for HTTP APIs. Understanding REST conventions ensures the data layer handles responses, errors, and status codes correctly and consistently.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Use the correct HTTP method for each operation — GET, POST, PUT, PATCH, DELETE
20
+ - Map HTTP status codes to domain errors — never ignore non-2xx responses
21
+ - Use query parameters for filtering/sorting/pagination — path parameters for resource identity
22
+ - Requests and responses use consistent naming — snake_case in JSON, camelCase in Kotlin DTOs
23
+ - Idempotent operations (GET, PUT, DELETE) are safe to retry — non-idempotent (POST) are not
24
+
25
+ ---
26
+
27
+ ## HTTP Method Mapping
28
+
29
+ | Method | Use For | Idempotent |
30
+ |--------|---------|------------|
31
+ | `GET` | Fetch resource(s) | ✅ Yes |
32
+ | `POST` | Create new resource | ❌ No |
33
+ | `PUT` | Replace entire resource | ✅ Yes |
34
+ | `PATCH` | Partial update | ❌ No |
35
+ | `DELETE` | Remove resource | ✅ Yes |
36
+
37
+ ---
38
+
39
+ ## Standard Endpoints Pattern
40
+
41
+ ```
42
+ GET /users → list users
43
+ POST /users → create user
44
+ GET /users/{id} → get user by id
45
+ PUT /users/{id} → replace user
46
+ PATCH /users/{id} → partial update user
47
+ DELETE /users/{id} → delete user
48
+
49
+ GET /users/{id}/posts → posts belonging to user
50
+ POST /users/{id}/posts → create post for user
51
+ ```
52
+
53
+ ---
54
+
55
+ ## HTTP Status Code Handling
56
+
57
+ ```kotlin
58
+ // ✅ Map status codes to domain errors
59
+ sealed class ApiError : Exception() {
60
+ data class BadRequest(override val message: String) : ApiError() // 400
61
+ data class Unauthorized(override val message: String) : ApiError() // 401
62
+ data class Forbidden(override val message: String) : ApiError() // 403
63
+ data class NotFound(override val message: String) : ApiError() // 404
64
+ data class Conflict(override val message: String) : ApiError() // 409
65
+ data class UnprocessableEntity(val errors: Map<String, List<String>>) : ApiError() // 422
66
+ data class TooManyRequests(val retryAfter: Int?) : ApiError() // 429
67
+ data class ServerError(val code: Int) : ApiError() // 5xx
68
+ }
69
+
70
+ // ✅ Map HttpException to ApiError
71
+ fun HttpException.toApiError(): ApiError {
72
+ return when (code()) {
73
+ 400 -> ApiError.BadRequest(parseErrorMessage())
74
+ 401 -> ApiError.Unauthorized(parseErrorMessage())
75
+ 403 -> ApiError.Forbidden(parseErrorMessage())
76
+ 404 -> ApiError.NotFound(parseErrorMessage())
77
+ 409 -> ApiError.Conflict(parseErrorMessage())
78
+ 422 -> ApiError.UnprocessableEntity(parseValidationErrors())
79
+ 429 -> ApiError.TooManyRequests(response()?.headers()?.get("Retry-After")?.toIntOrNull())
80
+ in 500..599 -> ApiError.ServerError(code())
81
+ else -> ApiError.ServerError(code())
82
+ }
83
+ }
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Pagination Patterns
89
+
90
+ ```kotlin
91
+ // ✅ Offset-based pagination
92
+ @GET("users")
93
+ suspend fun getUsers(
94
+ @Query("page") page: Int,
95
+ @Query("limit") limit: Int = 20
96
+ ): PagedResponse<UserDto>
97
+
98
+ @Serializable
99
+ data class PagedResponse<T>(
100
+ val data: List<T>,
101
+ val total: Int,
102
+ val page: Int,
103
+ val limit: Int,
104
+ val hasMore: Boolean
105
+ )
106
+
107
+ // ✅ Cursor-based pagination
108
+ @GET("feed")
109
+ suspend fun getFeed(
110
+ @Query("cursor") cursor: String? = null,
111
+ @Query("limit") limit: Int = 20
112
+ ): CursorPagedResponse<FeedItemDto>
113
+
114
+ @Serializable
115
+ data class CursorPagedResponse<T>(
116
+ val data: List<T>,
117
+ val nextCursor: String?,
118
+ val hasMore: Boolean
119
+ )
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Standard Response Envelope
125
+
126
+ ```kotlin
127
+ // ✅ Consistent API envelope — if the API uses one
128
+ @Serializable
129
+ data class ApiResponse<T>(
130
+ val success: Boolean,
131
+ val data: T? = null,
132
+ val error: ApiErrorDto? = null,
133
+ val message: String? = null
134
+ )
135
+
136
+ @Serializable
137
+ data class ApiErrorDto(
138
+ val code: String,
139
+ val message: String,
140
+ val details: Map<String, List<String>> = emptyMap()
141
+ )
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Query Parameter Conventions
147
+
148
+ ```kotlin
149
+ // ✅ Common query parameter patterns
150
+ @GET("users")
151
+ suspend fun getUsers(
152
+ @Query("q") search: String? = null, // search
153
+ @Query("sort") sort: String? = "created_at", // sort field
154
+ @Query("order") order: String? = "desc", // sort direction
155
+ @Query("filter[status]") status: String? = null, // filtering
156
+ @Query("page") page: Int = 1,
157
+ @Query("limit") limit: Int = 20
158
+ ): PagedResponse<UserDto>
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Anti-Patterns
164
+
165
+ - Using `GET` for operations with side effects — use `POST` or `DELETE`
166
+ - Ignoring HTTP status codes — check response codes, don't assume 200
167
+ - Putting sensitive data in query parameters — use request body or headers
168
+ - Using `POST` for reads — breaks caching and idempotency
169
+ - Returning `200 OK` for errors with error details in the body — use correct status codes
170
+
171
+ ---
172
+
173
+ ## Related Skills
174
+ - `retrofit` — implementing REST calls with Retrofit
175
+ - `api-contract` — formal API contract definition
176
+ - `authentication` — auth headers and token handling
177
+ - `retry-backoff` — safe retry for idempotent requests
178
+ - `dto-mapping` — mapping REST responses to domain models
@@ -0,0 +1,170 @@
1
+ ---
2
+ name: rate-limiting
3
+ description: >
4
+ Handling rate limiting (HTTP 429) and implementing client-side request
5
+ throttling in Android. Load this skill when handling 429 Too Many Requests
6
+ responses, respecting Retry-After headers, implementing request queuing,
7
+ or throttling outgoing requests to avoid hitting server limits.
8
+ ---
9
+
10
+ # Rate Limiting
11
+
12
+ ## Overview
13
+ Rate limiting occurs when the server rejects requests because the client has exceeded the allowed request frequency. On Android, this is handled in two ways: reacting to 429 responses from the server, and proactively throttling client-side requests to avoid hitting the limit.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Always respect the `Retry-After` header when present — don't guess the delay
20
+ - Implement **client-side throttling** for high-frequency operations (search, autocomplete)
21
+ - Use a **single retry** for 429 — not an infinite loop
22
+ - Log rate limit events — they indicate either a bug or a need to adjust request frequency
23
+ - Debounce user-driven requests (search input) before they reach the network
24
+
25
+ ---
26
+
27
+ ## Handling 429 Server Response
28
+
29
+ ```kotlin
30
+ // ✅ 429 handler in repository
31
+ suspend fun <T> withRateLimitHandling(block: suspend () -> T): Result<T> {
32
+ return runCatching { block() }
33
+ .recoverCatching { throwable ->
34
+ if (throwable is HttpException && throwable.code() == 429) {
35
+ val retryAfterSeconds = throwable.response()
36
+ ?.headers()
37
+ ?.get("Retry-After")
38
+ ?.toLongOrNull()
39
+ ?: 10L // default 10 seconds if header missing
40
+
41
+ delay(retryAfterSeconds * 1_000)
42
+ block() // single retry after waiting
43
+ } else {
44
+ throw throwable
45
+ }
46
+ }
47
+ }
48
+
49
+ // ✅ Usage
50
+ override suspend fun searchUsers(query: String): Result<List<User>> =
51
+ withRateLimitHandling {
52
+ api.searchUsers(query).map { mapper.toDomain(it) }
53
+ }
54
+ ```
55
+
56
+ ---
57
+
58
+ ## OkHttp Rate Limit Interceptor
59
+
60
+ ```kotlin
61
+ // ✅ Intercept 429 at the HTTP level
62
+ class RateLimitInterceptor : Interceptor {
63
+ override fun intercept(chain: Interceptor.Chain): Response {
64
+ val response = chain.proceed(chain.request())
65
+
66
+ if (response.code == 429) {
67
+ val retryAfter = response.headers["Retry-After"]?.toLongOrNull() ?: 10L
68
+ response.close()
69
+ Thread.sleep(retryAfter * 1_000)
70
+ return chain.proceed(chain.request())
71
+ }
72
+
73
+ return response
74
+ }
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Client-Side Debouncing (Search / Autocomplete)
81
+
82
+ ```kotlin
83
+ // ✅ Debounce search input in ViewModel — reduce API calls
84
+ class SearchViewModel @Inject constructor(
85
+ private val searchUseCase: SearchUsersUseCase
86
+ ) : ViewModel() {
87
+
88
+ private val _query = MutableStateFlow("")
89
+ private val _results = MutableStateFlow<UiState<List<User>>>(UiState.Loading)
90
+ val results: StateFlow<UiState<List<User>>> = _results.asStateFlow()
91
+
92
+ init {
93
+ viewModelScope.launch {
94
+ _query
95
+ .debounce(300) // ✅ wait 300ms after last keystroke
96
+ .filter { it.length >= 2 } // ✅ minimum query length
97
+ .distinctUntilChanged() // ✅ skip duplicate queries
98
+ .collectLatest { query -> // ✅ cancel previous in-flight request
99
+ _results.value = UiState.Loading
100
+ searchUseCase(query).fold(
101
+ onSuccess = { _results.value = UiState.Success(it) },
102
+ onFailure = { _results.value = UiState.Error(it.message ?: "Error") }
103
+ )
104
+ }
105
+ }
106
+ }
107
+
108
+ fun onQueryChange(query: String) {
109
+ _query.value = query
110
+ }
111
+ }
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Request Throttler (Token Bucket)
117
+
118
+ ```kotlin
119
+ // ✅ Token bucket throttler — max N requests per window
120
+ class RequestThrottler(
121
+ private val maxRequests: Int = 10,
122
+ private val windowMs: Long = 1_000L
123
+ ) {
124
+ private val mutex = Mutex()
125
+ private val timestamps = ArrayDeque<Long>()
126
+
127
+ suspend fun throttle() {
128
+ mutex.withLock {
129
+ val now = System.currentTimeMillis()
130
+ // remove timestamps outside the window
131
+ while (timestamps.isNotEmpty() && now - timestamps.first() > windowMs) {
132
+ timestamps.removeFirst()
133
+ }
134
+ if (timestamps.size >= maxRequests) {
135
+ val waitMs = windowMs - (now - timestamps.first())
136
+ delay(waitMs)
137
+ }
138
+ timestamps.addLast(System.currentTimeMillis())
139
+ }
140
+ }
141
+ }
142
+
143
+ // ✅ Usage
144
+ class AnalyticsDataSource @Inject constructor(
145
+ private val throttler: RequestThrottler
146
+ ) {
147
+ suspend fun trackEvent(event: AnalyticsEvent) {
148
+ throttler.throttle()
149
+ api.track(event)
150
+ }
151
+ }
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Anti-Patterns
157
+
158
+ - Retrying 429 immediately without delay — will get another 429 immediately
159
+ - Ignoring the `Retry-After` header — use the server-specified delay
160
+ - Not debouncing search input — fires a request on every keystroke
161
+ - Infinite retry loop on 429 — cap at one retry after the wait
162
+ - Using `Thread.sleep` in coroutine context — use `delay` instead
163
+
164
+ ---
165
+
166
+ ## Related Skills
167
+ - `retry-backoff` — general retry strategy for transient failures
168
+ - `okhttp` — interceptor setup for HTTP-level handling
169
+ - `flow` — debounce and distinctUntilChanged operators
170
+ - `fallback-strategy` — what to do when rate limit retries fail