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,227 @@
1
+ ---
2
+ name: metrics
3
+ description: >
4
+ Application metrics and telemetry for Android apps.
5
+ Load this skill when tracking performance metrics, measuring feature
6
+ adoption, monitoring error rates, capturing user behavior signals,
7
+ or building a custom metrics pipeline.
8
+ ---
9
+
10
+ # Metrics
11
+
12
+ ## Overview
13
+ Metrics provide quantitative signals about app health and user behavior — error rates, response times, feature usage, and performance. On Android, metrics are typically sent via Firebase Analytics, Firebase Performance Monitoring, or a custom backend. The goal is to detect regressions and understand usage patterns before users complain.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Track **outcomes**, not implementation details — "checkout_completed" not "button_tapped"
20
+ - Every metric must be **actionable** — if you can't act on it, don't track it
21
+ - Separate **performance metrics** from **business metrics** — different stakeholders
22
+ - Avoid tracking PII — user IDs are fine, email addresses are not
23
+ - Define metrics **before** shipping a feature — don't instrument retroactively
24
+
25
+ ---
26
+
27
+ ## Firebase Performance Monitoring
28
+
29
+ ```toml
30
+ # libs.versions.toml
31
+ [libraries]
32
+ firebase-perf = { module = "com.google.firebase:firebase-perf-ktx" }
33
+ ```
34
+
35
+ ```kotlin
36
+ // ✅ Custom trace for critical operations
37
+ class SyncRepository @Inject constructor(
38
+ private val firebasePerf: FirebasePerformance
39
+ ) {
40
+ suspend fun syncData(): Result<Unit> {
41
+ val trace = firebasePerf.newTrace("sync_data")
42
+ trace.start()
43
+
44
+ return runCatching {
45
+ val result = performSync()
46
+ trace.putMetric("items_synced", result.itemCount.toLong())
47
+ trace.putAttribute("sync_type", result.type)
48
+ result
49
+ }.onSuccess {
50
+ trace.putAttribute("status", "success")
51
+ }.onFailure {
52
+ trace.putAttribute("status", "failure")
53
+ trace.putAttribute("error", it::class.simpleName ?: "unknown")
54
+ }.also {
55
+ trace.stop()
56
+ }.map { Unit }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Custom Metrics Tracker
64
+
65
+ ```kotlin
66
+ // ✅ Typed metrics tracker wrapping Firebase Analytics
67
+ @Singleton
68
+ class MetricsTracker @Inject constructor(
69
+ private val analytics: FirebaseAnalytics,
70
+ private val firebasePerf: FirebasePerformance
71
+ ) {
72
+ // ✅ Business event tracking
73
+ fun trackEvent(event: MetricEvent) {
74
+ val bundle = Bundle().apply {
75
+ event.properties.forEach { (key, value) ->
76
+ when (value) {
77
+ is String -> putString(key, value)
78
+ is Long -> putLong(key, value)
79
+ is Double -> putDouble(key, value)
80
+ is Int -> putInt(key, value)
81
+ is Boolean -> putBoolean(key, value)
82
+ }
83
+ }
84
+ }
85
+ analytics.logEvent(event.name, bundle)
86
+ }
87
+
88
+ // ✅ Performance timing
89
+ fun <T> measureTrace(name: String, block: () -> T): T {
90
+ val trace = firebasePerf.newTrace(name)
91
+ trace.start()
92
+ return try {
93
+ block()
94
+ } finally {
95
+ trace.stop()
96
+ }
97
+ }
98
+
99
+ // ✅ Async performance timing
100
+ suspend fun <T> measureTraceAsync(name: String, block: suspend () -> T): T {
101
+ val trace = firebasePerf.newTrace(name)
102
+ trace.start()
103
+ return try {
104
+ block()
105
+ } finally {
106
+ trace.stop()
107
+ }
108
+ }
109
+ }
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Metric Events
115
+
116
+ ```kotlin
117
+ // ✅ Typed event definitions
118
+ sealed class MetricEvent(
119
+ val name: String,
120
+ val properties: Map<String, Any> = emptyMap()
121
+ ) {
122
+ class FeatureViewed(featureName: String) : MetricEvent(
123
+ name = "feature_viewed",
124
+ properties = mapOf("feature" to featureName)
125
+ )
126
+
127
+ class ActionCompleted(action: String, durationMs: Long) : MetricEvent(
128
+ name = "action_completed",
129
+ properties = mapOf("action" to action, "duration_ms" to durationMs)
130
+ )
131
+
132
+ class ErrorOccurred(errorType: String, screen: String) : MetricEvent(
133
+ name = "error_occurred",
134
+ properties = mapOf("error_type" to errorType, "screen" to screen)
135
+ )
136
+
137
+ class SyncCompleted(itemCount: Int, durationMs: Long, success: Boolean) : MetricEvent(
138
+ name = "sync_completed",
139
+ properties = mapOf(
140
+ "item_count" to itemCount,
141
+ "duration_ms" to durationMs,
142
+ "success" to success
143
+ )
144
+ )
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Error Rate Tracking
151
+
152
+ ```kotlin
153
+ // ✅ Track error rates per feature
154
+ class UserRepository @Inject constructor(
155
+ private val metrics: MetricsTracker
156
+ ) {
157
+ suspend fun getUser(id: String): Result<User> {
158
+ val startTime = System.currentTimeMillis()
159
+
160
+ return runCatching { api.getUser(id) }
161
+ .onSuccess {
162
+ metrics.trackEvent(MetricEvent.ActionCompleted(
163
+ action = "fetch_user",
164
+ durationMs = System.currentTimeMillis() - startTime
165
+ ))
166
+ }
167
+ .onFailure { error ->
168
+ metrics.trackEvent(MetricEvent.ErrorOccurred(
169
+ errorType = error::class.simpleName ?: "unknown",
170
+ screen = "user_detail"
171
+ ))
172
+ }
173
+ .map { mapper.toDomain(it) }
174
+ }
175
+ }
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Network Request Metrics
181
+
182
+ ```kotlin
183
+ // ✅ Track all network request performance
184
+ class MetricsInterceptor @Inject constructor(
185
+ private val metrics: MetricsTracker
186
+ ) : Interceptor {
187
+ override fun intercept(chain: Interceptor.Chain): Response {
188
+ val start = System.currentTimeMillis()
189
+ val request = chain.request()
190
+
191
+ return try {
192
+ val response = chain.proceed(request)
193
+ val duration = System.currentTimeMillis() - start
194
+
195
+ metrics.trackEvent(MetricEvent.ActionCompleted(
196
+ action = "network_${request.method.lowercase()}_${request.url.encodedPath}",
197
+ durationMs = duration
198
+ ))
199
+ response
200
+ } catch (e: Exception) {
201
+ metrics.trackEvent(MetricEvent.ErrorOccurred(
202
+ errorType = "network_${e::class.simpleName}",
203
+ screen = request.url.encodedPath
204
+ ))
205
+ throw e
206
+ }
207
+ }
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Anti-Patterns
214
+
215
+ - Tracking every tap and scroll — creates noise, hard to find signal
216
+ - Tracking PII (email, name) as metric properties — privacy violation
217
+ - Not defining metric names as constants — typos create duplicate metrics
218
+ - Tracking metrics in debug builds — pollutes production dashboards
219
+ - No baseline — can't detect regression without knowing the previous value
220
+
221
+ ---
222
+
223
+ ## Related Skills
224
+ - `crash-reporting` — crash data alongside error metrics
225
+ - `structured-logging` — log events that feed into metrics pipelines
226
+ - `analytics` — Firebase Analytics for user behavior
227
+ - `observability` — broader observability combining logs, metrics, and traces
@@ -0,0 +1,234 @@
1
+ ---
2
+ name: structured-logging
3
+ description: >
4
+ Structured logging for Android apps — consistent, queryable log format.
5
+ Load this skill when building log aggregation pipelines, adding context
6
+ to log events, correlating logs across requests, or making logs
7
+ machine-parseable for log management tools.
8
+ ---
9
+
10
+ # Structured Logging
11
+
12
+ ## Overview
13
+ Structured logging formats log entries as key-value pairs or JSON instead of free-form strings. This makes logs searchable, filterable, and correlatable in log management systems (Datadog, Elastic, Splunk). On Android, structured logs are typically sent to Crashlytics or a custom logging backend.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - Every log event has a **consistent schema** — event name + context fields
20
+ - Include **correlation IDs** to trace a request across multiple log entries
21
+ - Log **events**, not state — "user_login_succeeded" not "user is logged in"
22
+ - Never include sensitive data in structured logs — same rule as plain logs
23
+ - Use a typed log event model — not free-form strings
24
+
25
+ ---
26
+
27
+ ## Log Event Model
28
+
29
+ ```kotlin
30
+ // ✅ Typed log event — consistent schema
31
+ data class LogEvent(
32
+ val name: String,
33
+ val level: LogLevel,
34
+ val timestamp: Long = System.currentTimeMillis(),
35
+ val userId: String? = null,
36
+ val sessionId: String? = null,
37
+ val properties: Map<String, Any> = emptyMap(),
38
+ val error: Throwable? = null
39
+ )
40
+
41
+ enum class LogLevel { DEBUG, INFO, WARN, ERROR }
42
+
43
+ // ✅ Event name constants — prevents typos
44
+ object LogEvents {
45
+ const val USER_LOGIN_SUCCESS = "user_login_success"
46
+ const val USER_LOGIN_FAILURE = "user_login_failure"
47
+ const val SYNC_STARTED = "sync_started"
48
+ const val SYNC_COMPLETED = "sync_completed"
49
+ const val SYNC_FAILED = "sync_failed"
50
+ const val NETWORK_REQUEST = "network_request"
51
+ const val NETWORK_ERROR = "network_error"
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Structured Logger
58
+
59
+ ```kotlin
60
+ // ✅ Central structured logger
61
+ @Singleton
62
+ class StructuredLogger @Inject constructor(
63
+ private val sessionManager: SessionManager,
64
+ private val crashlytics: FirebaseCrashlytics
65
+ ) {
66
+ fun log(
67
+ name: String,
68
+ level: LogLevel = LogLevel.INFO,
69
+ properties: Map<String, Any> = emptyMap(),
70
+ error: Throwable? = null
71
+ ) {
72
+ val event = LogEvent(
73
+ name = name,
74
+ level = level,
75
+ userId = sessionManager.currentUserId,
76
+ sessionId = sessionManager.sessionId,
77
+ properties = properties,
78
+ error = error
79
+ )
80
+
81
+ // Debug: log to Logcat
82
+ if (BuildConfig.DEBUG) {
83
+ Timber.tag("StructuredLog").d(event.toLogString())
84
+ }
85
+
86
+ // Release: send to Crashlytics
87
+ crashlytics.log(event.toLogString())
88
+ error?.let { crashlytics.recordException(it) }
89
+ }
90
+
91
+ private fun LogEvent.toLogString(): String {
92
+ val props = properties.entries.joinToString(", ") { "${it.key}=${it.value}" }
93
+ return "[$level] $name | userId=$userId sessionId=$sessionId | $props"
94
+ }
95
+ }
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Usage in Repository and Use Cases
101
+
102
+ ```kotlin
103
+ // ✅ Log structured events at key boundaries
104
+ class AuthRepositoryImpl @Inject constructor(
105
+ private val api: AuthApi,
106
+ private val logger: StructuredLogger
107
+ ) : AuthRepository {
108
+
109
+ override suspend fun login(email: String, password: String): Result<User> {
110
+ return runCatching {
111
+ val response = api.login(email, password)
112
+ logger.log(
113
+ name = LogEvents.USER_LOGIN_SUCCESS,
114
+ properties = mapOf("method" to "email")
115
+ )
116
+ mapper.toDomain(response)
117
+ }.onFailure { error ->
118
+ logger.log(
119
+ name = LogEvents.USER_LOGIN_FAILURE,
120
+ level = LogLevel.ERROR,
121
+ properties = mapOf(
122
+ "method" to "email",
123
+ "error_type" to error::class.simpleName.orEmpty()
124
+ ),
125
+ error = error
126
+ )
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Network Request Logging
135
+
136
+ ```kotlin
137
+ // ✅ Log all network requests with structured context
138
+ class StructuredLoggingInterceptor @Inject constructor(
139
+ private val logger: StructuredLogger
140
+ ) : Interceptor {
141
+
142
+ override fun intercept(chain: Interceptor.Chain): Response {
143
+ val request = chain.request()
144
+ val startTime = System.currentTimeMillis()
145
+
146
+ return try {
147
+ val response = chain.proceed(request)
148
+ val duration = System.currentTimeMillis() - startTime
149
+
150
+ logger.log(
151
+ name = LogEvents.NETWORK_REQUEST,
152
+ properties = mapOf(
153
+ "method" to request.method,
154
+ "path" to request.url.encodedPath,
155
+ "status" to response.code,
156
+ "duration" to duration
157
+ )
158
+ )
159
+ response
160
+ } catch (e: IOException) {
161
+ logger.log(
162
+ name = LogEvents.NETWORK_ERROR,
163
+ level = LogLevel.ERROR,
164
+ properties = mapOf(
165
+ "method" to request.method,
166
+ "path" to request.url.encodedPath
167
+ ),
168
+ error = e
169
+ )
170
+ throw e
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Session Correlation
179
+
180
+ ```kotlin
181
+ // ✅ Generate session ID to correlate logs within a session
182
+ @Singleton
183
+ class SessionManager @Inject constructor() {
184
+ var currentUserId: String? = null
185
+ private set
186
+
187
+ var sessionId: String = generateSessionId()
188
+ private set
189
+
190
+ fun onUserLoggedIn(userId: String) {
191
+ currentUserId = userId
192
+ sessionId = generateSessionId() // new session per login
193
+ }
194
+
195
+ fun onUserLoggedOut() {
196
+ currentUserId = null
197
+ sessionId = generateSessionId()
198
+ }
199
+
200
+ private fun generateSessionId() = UUID.randomUUID().toString().take(8)
201
+ }
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Crashlytics Key-Value Context
207
+
208
+ ```kotlin
209
+ // ✅ Set Crashlytics keys for crash context
210
+ fun setCrashlyticsContext(userId: String, plan: String) {
211
+ val crashlytics = FirebaseCrashlytics.getInstance()
212
+ crashlytics.setUserId(userId)
213
+ crashlytics.setCustomKey("user_plan", plan)
214
+ crashlytics.setCustomKey("app_version", BuildConfig.VERSION_NAME)
215
+ crashlytics.setCustomKey("session_id", sessionManager.sessionId)
216
+ }
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Anti-Patterns
222
+
223
+ - Free-form log strings — not queryable or consistent
224
+ - Logging sensitive data (email, tokens) as properties — privacy violation
225
+ - No event name constants — typos cause missed log entries
226
+ - Logging too many events — drowns out important signals
227
+ - Not including session/user context — can't correlate related events
228
+
229
+ ---
230
+
231
+ ## Related Skills
232
+ - `logging` — basic Timber setup and log levels
233
+ - `crash-reporting` — integrating structured logs with crash reports
234
+ - `observability` — metrics and telemetry alongside logs
@@ -0,0 +1,192 @@
1
+ ---
2
+ name: anr-prevention
3
+ description: >
4
+ ANR (Application Not Responding) prevention in Android.
5
+ Load this skill when investigating ANR reports, identifying blocking
6
+ main thread operations, using StrictMode to detect violations,
7
+ or ensuring the main thread stays responsive under load.
8
+ ---
9
+
10
+ # ANR Prevention
11
+
12
+ ## Overview
13
+ An ANR occurs when the main thread is blocked for more than 5 seconds (input) or 10 seconds (broadcast). Android shows a dialog asking the user to wait or close the app. ANRs are caused by I/O, network, database queries, or heavy computation running on the main thread.
14
+
15
+ ---
16
+
17
+ ## Core Principles
18
+
19
+ - **Nothing blocking** on the main thread — no I/O, no network, no heavy computation
20
+ - Use `Dispatchers.IO` for all I/O operations — database, file, network
21
+ - Use `Dispatchers.Default` for CPU-heavy work — parsing, sorting, crypto
22
+ - Keep `BroadcastReceiver.onReceive()` under 10 seconds — delegate to WorkManager if longer
23
+ - Use StrictMode in development to catch violations before they reach users
24
+
25
+ ---
26
+
27
+ ## ANR Triggers
28
+
29
+ ```
30
+ Input dispatch timeout: 5 seconds — user touched screen, app didn't respond
31
+ Broadcast timeout: 10 seconds (foreground) / 60 seconds (background)
32
+ Service timeout: 20 seconds (foreground) / 200 seconds (background)
33
+ ContentProvider timeout: 10 seconds
34
+
35
+ Common causes:
36
+ - SharedPreferences.edit().commit() on main thread (use apply())
37
+ - Room query on main thread (Room throws by default — don't disable this)
38
+ - Bitmap decoding on main thread
39
+ - Heavy JSON parsing on main thread
40
+ - Synchronized lock contention on main thread
41
+ - Binder calls to slow system services on main thread
42
+ ```
43
+
44
+ ---
45
+
46
+ ## StrictMode — Catch Violations in Development
47
+
48
+ ```kotlin
49
+ // ✅ Enable StrictMode in debug builds
50
+ class App : Application() {
51
+ override fun onCreate() {
52
+ super.onCreate()
53
+
54
+ if (BuildConfig.DEBUG) {
55
+ StrictMode.setThreadPolicy(
56
+ StrictMode.ThreadPolicy.Builder()
57
+ .detectDiskReads()
58
+ .detectDiskWrites()
59
+ .detectNetwork()
60
+ .detectCustomSlowCalls()
61
+ .penaltyLog() // log to Logcat
62
+ .penaltyFlashScreen() // visual flash in debug
63
+ .build()
64
+ )
65
+
66
+ StrictMode.setVmPolicy(
67
+ StrictMode.VmPolicy.Builder()
68
+ .detectLeakedSqlLiteObjects()
69
+ .detectLeakedClosableObjects()
70
+ .detectActivityLeaks()
71
+ .penaltyLog()
72
+ .build()
73
+ )
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Moving Work off Main Thread
82
+
83
+ ```kotlin
84
+ // ❌ Blocking main thread
85
+ class UserRepository {
86
+ fun getUser(id: String): User {
87
+ return database.userDao().getUser(id) // blocks main thread
88
+ }
89
+ }
90
+
91
+ // ✅ Suspend function on IO dispatcher
92
+ class UserRepository {
93
+ suspend fun getUser(id: String): User = withContext(Dispatchers.IO) {
94
+ database.userDao().getUser(id)
95
+ }
96
+ }
97
+
98
+ // ❌ SharedPreferences commit on main thread
99
+ prefs.edit().putString("key", "value").commit() // synchronous write
100
+
101
+ // ✅ apply() — asynchronous write
102
+ prefs.edit().putString("key", "value").apply()
103
+
104
+ // ❌ Heavy computation on main thread
105
+ val sorted = largeList.sortedBy { expensiveFunction(it) } // main thread
106
+
107
+ // ✅ Move to Default dispatcher
108
+ val sorted = withContext(Dispatchers.Default) {
109
+ largeList.sortedBy { expensiveFunction(it) }
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## BroadcastReceiver — Delegate Long Work
116
+
117
+ ```kotlin
118
+ // ❌ Long work in onReceive — 10 second limit
119
+ class SyncReceiver : BroadcastReceiver() {
120
+ override fun onReceive(context: Context, intent: Intent) {
121
+ performLongSync() // may exceed 10s — ANR
122
+ }
123
+ }
124
+
125
+ // ✅ Delegate to WorkManager from onReceive
126
+ class SyncReceiver : BroadcastReceiver() {
127
+ override fun onReceive(context: Context, intent: Intent) {
128
+ // onReceive must return quickly — enqueue work
129
+ WorkManager.getInstance(context)
130
+ .enqueue(OneTimeWorkRequestBuilder<SyncWorker>().build())
131
+ }
132
+ }
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Detecting ANRs in Production
138
+
139
+ ```kotlin
140
+ // ✅ Firebase Crashlytics reports ANRs automatically
141
+ // Check Firebase Console → Crashlytics → ANRs tab
142
+
143
+ // ✅ Custom ANR watchdog for more detail
144
+ class AnrWatchdog(private val timeoutMs: Long = 5_000) : Thread() {
145
+ private val mainHandler = Handler(Looper.getMainLooper())
146
+ @Volatile private var tick = 0
147
+
148
+ override fun run() {
149
+ while (!isInterrupted) {
150
+ val prev = tick
151
+ mainHandler.post { tick++ }
152
+ sleep(timeoutMs)
153
+ if (tick == prev) {
154
+ // Main thread hasn't processed the message — potential ANR
155
+ Timber.e("Potential ANR detected — main thread blocked for ${timeoutMs}ms")
156
+ }
157
+ }
158
+ }
159
+ }
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Reading ANR Traces
165
+
166
+ ```bash
167
+ # Pull ANR trace from device
168
+ adb pull /data/anr/traces.txt
169
+
170
+ # Key things to look for in traces.txt:
171
+ # "main" thread in state BLOCKED or WAIT
172
+ # Holding lock: or waiting to lock:
173
+ # Long stack traces in android.database or java.io
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Anti-Patterns
179
+
180
+ - `SharedPreferences.commit()` on main thread — use `apply()`
181
+ - Room `allowMainThreadQueries()` — never enable in production
182
+ - `Bitmap.decodeFile()` on main thread — use coroutine with `Dispatchers.IO`
183
+ - Long `synchronized {}` blocks on main thread — can cause lock contention ANR
184
+ - `Thread.sleep()` on main thread — blocks all UI processing
185
+
186
+ ---
187
+
188
+ ## Related Skills
189
+ - `coroutine` — moving work off the main thread
190
+ - `background-processing` — delegating work to background
191
+ - `rendering-performance` — keeping main thread free during frames
192
+ - `workmanager` — delegating BroadcastReceiver work safely