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.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- 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
|