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,281 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: analytics
|
|
3
|
+
description: >
|
|
4
|
+
Firebase Analytics setup and event tracking for Android.
|
|
5
|
+
Load this skill when implementing event tracking, screen tracking,
|
|
6
|
+
user properties, conversion funnels, or integrating analytics
|
|
7
|
+
with the app's architecture.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Analytics
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Firebase Analytics provides free, unlimited event tracking for Android apps. It automatically tracks some events (first_open, session_start) and allows custom events. Events appear in the Firebase console and can be forwarded to Google Analytics, BigQuery, and Ads.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Analytics must be **disabled in debug builds** — dev events pollute production data
|
|
20
|
+
- Track **meaningful user actions** — not every tap, only business-relevant events
|
|
21
|
+
- Use a **centralized analytics wrapper** — never call Firebase directly from UI
|
|
22
|
+
- Define an **event taxonomy** before implementation — consistent naming is critical
|
|
23
|
+
- User PII (name, email) must **never** be sent as event parameters
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
```kotlin
|
|
30
|
+
dependencies {
|
|
31
|
+
implementation(platform(libs.firebase.bom))
|
|
32
|
+
implementation(libs.firebase.analytics)
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Analytics Wrapper
|
|
39
|
+
|
|
40
|
+
```kotlin
|
|
41
|
+
// ✅ Centralized wrapper — never call FirebaseAnalytics directly from UI/ViewModel
|
|
42
|
+
interface Analytics {
|
|
43
|
+
fun logEvent(event: AnalyticsEvent)
|
|
44
|
+
fun setUserId(userId: String?)
|
|
45
|
+
fun setUserProperty(name: String, value: String?)
|
|
46
|
+
fun logScreenView(screenName: String, screenClass: String)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class FirebaseAnalyticsImpl @Inject constructor(
|
|
50
|
+
@ApplicationContext context: Context
|
|
51
|
+
) : Analytics {
|
|
52
|
+
|
|
53
|
+
private val firebaseAnalytics = FirebaseAnalytics.getInstance(context)
|
|
54
|
+
|
|
55
|
+
override fun logEvent(event: AnalyticsEvent) {
|
|
56
|
+
firebaseAnalytics.logEvent(event.name, event.toBundle())
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
override fun setUserId(userId: String?) {
|
|
60
|
+
firebaseAnalytics.setUserId(userId)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override fun setUserProperty(name: String, value: String?) {
|
|
64
|
+
firebaseAnalytics.setUserProperty(name, value)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override fun logScreenView(screenName: String, screenClass: String) {
|
|
68
|
+
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) {
|
|
69
|
+
param(FirebaseAnalytics.Param.SCREEN_NAME, screenName)
|
|
70
|
+
param(FirebaseAnalytics.Param.SCREEN_CLASS, screenClass)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ✅ No-op implementation for debug builds
|
|
76
|
+
class NoOpAnalytics @Inject constructor() : Analytics {
|
|
77
|
+
override fun logEvent(event: AnalyticsEvent) = Unit
|
|
78
|
+
override fun setUserId(userId: String?) = Unit
|
|
79
|
+
override fun setUserProperty(name: String, value: String?) = Unit
|
|
80
|
+
override fun logScreenView(screenName: String, screenClass: String) = Unit
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Event Taxonomy
|
|
87
|
+
|
|
88
|
+
```kotlin
|
|
89
|
+
// ✅ Sealed class for type-safe event definitions
|
|
90
|
+
sealed class AnalyticsEvent(val name: String) {
|
|
91
|
+
|
|
92
|
+
abstract fun toBundle(): Bundle
|
|
93
|
+
|
|
94
|
+
// Auth events
|
|
95
|
+
object Login : AnalyticsEvent("login") {
|
|
96
|
+
override fun toBundle() = bundleOf(
|
|
97
|
+
FirebaseAnalytics.Param.METHOD to "email"
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
object Logout : AnalyticsEvent("logout") {
|
|
102
|
+
override fun toBundle() = Bundle.EMPTY
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
data class SignUp(val method: String) : AnalyticsEvent("sign_up") {
|
|
106
|
+
override fun toBundle() = bundleOf(
|
|
107
|
+
FirebaseAnalytics.Param.METHOD to method
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Content events
|
|
112
|
+
data class ViewItem(val itemId: String, val itemName: String, val category: String)
|
|
113
|
+
: AnalyticsEvent(FirebaseAnalytics.Event.VIEW_ITEM) {
|
|
114
|
+
override fun toBundle() = bundleOf(
|
|
115
|
+
FirebaseAnalytics.Param.ITEM_ID to itemId,
|
|
116
|
+
FirebaseAnalytics.Param.ITEM_NAME to itemName,
|
|
117
|
+
FirebaseAnalytics.Param.ITEM_CATEGORY to category
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
data class Search(val query: String) : AnalyticsEvent(FirebaseAnalytics.Event.SEARCH) {
|
|
122
|
+
override fun toBundle() = bundleOf(
|
|
123
|
+
FirebaseAnalytics.Param.SEARCH_TERM to query
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Error events
|
|
128
|
+
data class ErrorOccurred(val errorType: String, val errorMessage: String)
|
|
129
|
+
: AnalyticsEvent("error_occurred") {
|
|
130
|
+
override fun toBundle() = bundleOf(
|
|
131
|
+
"error_type" to errorType,
|
|
132
|
+
"error_message" to errorMessage.take(100) // max 100 chars
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Feature usage
|
|
137
|
+
data class FeatureUsed(val featureName: String) : AnalyticsEvent("feature_used") {
|
|
138
|
+
override fun toBundle() = bundleOf("feature_name" to featureName)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Usage in ViewModel
|
|
146
|
+
|
|
147
|
+
```kotlin
|
|
148
|
+
// ✅ Log events from ViewModel — not from Compose/UI
|
|
149
|
+
@HiltViewModel
|
|
150
|
+
class ProductViewModel @Inject constructor(
|
|
151
|
+
private val repository: ProductRepository,
|
|
152
|
+
private val analytics: Analytics
|
|
153
|
+
) : ViewModel() {
|
|
154
|
+
|
|
155
|
+
fun onProductViewed(product: Product) {
|
|
156
|
+
analytics.logEvent(
|
|
157
|
+
AnalyticsEvent.ViewItem(
|
|
158
|
+
itemId = product.id,
|
|
159
|
+
itemName = product.name,
|
|
160
|
+
category = product.category
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fun onSearchPerformed(query: String) {
|
|
166
|
+
analytics.logEvent(AnalyticsEvent.Search(query))
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Screen Tracking
|
|
174
|
+
|
|
175
|
+
```kotlin
|
|
176
|
+
// ✅ Track screen views automatically via Navigation
|
|
177
|
+
@Composable
|
|
178
|
+
fun AppNavHost(navController: NavHostController, analytics: Analytics) {
|
|
179
|
+
|
|
180
|
+
val currentEntry by navController.currentBackStackEntryAsState()
|
|
181
|
+
|
|
182
|
+
LaunchedEffect(currentEntry) {
|
|
183
|
+
currentEntry?.destination?.route?.let { route ->
|
|
184
|
+
analytics.logScreenView(
|
|
185
|
+
screenName = route,
|
|
186
|
+
screenClass = route
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
NavHost(navController, startDestination = HomeRoute) {
|
|
192
|
+
// routes...
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## User Properties
|
|
200
|
+
|
|
201
|
+
```kotlin
|
|
202
|
+
// ✅ Set stable user attributes for segmentation
|
|
203
|
+
class UserAnalyticsManager @Inject constructor(private val analytics: Analytics) {
|
|
204
|
+
|
|
205
|
+
fun onUserLoggedIn(user: User) {
|
|
206
|
+
analytics.setUserId(user.id)
|
|
207
|
+
analytics.setUserProperty("account_type", user.accountType)
|
|
208
|
+
analytics.setUserProperty("subscription_plan", user.plan)
|
|
209
|
+
// ❌ Never set PII
|
|
210
|
+
// analytics.setUserProperty("email", user.email) // WRONG
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fun onUserLoggedOut() {
|
|
214
|
+
analytics.setUserId(null)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Hilt Binding by Build Type
|
|
222
|
+
|
|
223
|
+
```kotlin
|
|
224
|
+
// ✅ Inject NoOp in debug, real impl in release
|
|
225
|
+
@Module
|
|
226
|
+
@InstallIn(SingletonComponent::class)
|
|
227
|
+
abstract class AnalyticsModule {
|
|
228
|
+
|
|
229
|
+
@Binds
|
|
230
|
+
@Singleton
|
|
231
|
+
abstract fun bindAnalytics(
|
|
232
|
+
// Switch based on build type
|
|
233
|
+
impl: FirebaseAnalyticsImpl // or NoOpAnalytics in debug flavor
|
|
234
|
+
): Analytics
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ✅ Better — use BuildConfig
|
|
238
|
+
@Module
|
|
239
|
+
@InstallIn(SingletonComponent::class)
|
|
240
|
+
object AnalyticsModule {
|
|
241
|
+
|
|
242
|
+
@Provides
|
|
243
|
+
@Singleton
|
|
244
|
+
fun provideAnalytics(@ApplicationContext context: Context): Analytics =
|
|
245
|
+
if (BuildConfig.DEBUG) NoOpAnalytics()
|
|
246
|
+
else FirebaseAnalyticsImpl(context)
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Event Naming Rules
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
✅ snake_case — user_signed_up, product_viewed, checkout_started
|
|
256
|
+
✅ Max 40 characters for event name
|
|
257
|
+
✅ Max 25 parameters per event
|
|
258
|
+
✅ Max 100 characters for parameter value (strings)
|
|
259
|
+
❌ No spaces, no special characters except underscore
|
|
260
|
+
❌ No reserved prefixes: firebase_, google_, ga_
|
|
261
|
+
❌ No PII in any parameter
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Anti-Patterns
|
|
267
|
+
|
|
268
|
+
- Calling `FirebaseAnalytics` directly from Composable — not testable, hard to disable
|
|
269
|
+
- Logging analytics in debug builds — pollutes production dashboards
|
|
270
|
+
- Sending PII (email, name, phone) as event parameters — compliance violation
|
|
271
|
+
- Tracking every user interaction — noise, hard to find signal
|
|
272
|
+
- Inconsistent event names (`user_login` vs `login_user` vs `userLogin`) — breaks funnels
|
|
273
|
+
- No event taxonomy document — events drift over time, become meaningless
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Related Skills
|
|
278
|
+
- `firebase` — Firebase core setup
|
|
279
|
+
- `crashlytics` — crash reporting alongside analytics
|
|
280
|
+
- `remote-config` — feature flags informed by analytics
|
|
281
|
+
- `side-effect-management` — analytics as a side effect in ViewModel
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: crashlytics
|
|
3
|
+
description: >
|
|
4
|
+
Firebase Crashlytics setup and usage for Android crash reporting.
|
|
5
|
+
Load this skill when configuring Crashlytics, logging custom events,
|
|
6
|
+
adding user context to crash reports, or handling non-fatal errors.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Crashlytics
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
Firebase Crashlytics is a real-time crash reporter that helps track, prioritize, and fix stability issues. It automatically captures fatal crashes and provides detailed stack traces, device info, and custom context. Non-fatal errors can also be reported manually.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principles
|
|
17
|
+
|
|
18
|
+
- **Disable** Crashlytics in debug builds — don't pollute production data with dev crashes
|
|
19
|
+
- Add **user context** before crashes happen — userId, screen, and key attributes help diagnose
|
|
20
|
+
- Report **non-fatal errors** explicitly — caught exceptions that indicate problems
|
|
21
|
+
- Use **custom keys** to add state context — what the user was doing before the crash
|
|
22
|
+
- Always pair Crashlytics with **proper logging** — crash reports alone miss context
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
// build.gradle.kts (app)
|
|
30
|
+
plugins {
|
|
31
|
+
alias(libs.plugins.firebase.crashlytics)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
dependencies {
|
|
35
|
+
implementation(platform(libs.firebase.bom))
|
|
36
|
+
implementation(libs.firebase.crashlytics)
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Enable / Disable by Build Type
|
|
43
|
+
|
|
44
|
+
```kotlin
|
|
45
|
+
// ✅ Disable in debug — only report in release
|
|
46
|
+
class MyApplication : Application() {
|
|
47
|
+
override fun onCreate() {
|
|
48
|
+
super.onCreate()
|
|
49
|
+
FirebaseCrashlytics.getInstance()
|
|
50
|
+
.setCrashlyticsCollectionEnabled(!BuildConfig.DEBUG)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## User Context
|
|
58
|
+
|
|
59
|
+
```kotlin
|
|
60
|
+
// ✅ Set user identity — helps group crashes by user
|
|
61
|
+
class CrashlyticsUserTracker @Inject constructor() {
|
|
62
|
+
|
|
63
|
+
fun setUser(userId: String, email: String?) {
|
|
64
|
+
FirebaseCrashlytics.getInstance().apply {
|
|
65
|
+
setUserId(userId)
|
|
66
|
+
email?.let { setCustomKey("user_email", it) }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fun clearUser() {
|
|
71
|
+
FirebaseCrashlytics.getInstance().apply {
|
|
72
|
+
setUserId("")
|
|
73
|
+
setCustomKey("user_email", "")
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Custom Keys — State Context
|
|
82
|
+
|
|
83
|
+
```kotlin
|
|
84
|
+
// ✅ Log app state before a crash
|
|
85
|
+
class CrashlyticsContext @Inject constructor() {
|
|
86
|
+
|
|
87
|
+
fun setScreen(screenName: String) {
|
|
88
|
+
FirebaseCrashlytics.getInstance()
|
|
89
|
+
.setCustomKey("current_screen", screenName)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fun setFeatureFlag(key: String, value: Boolean) {
|
|
93
|
+
FirebaseCrashlytics.getInstance()
|
|
94
|
+
.setCustomKey("flag_$key", value)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fun setApiEndpoint(endpoint: String) {
|
|
98
|
+
FirebaseCrashlytics.getInstance()
|
|
99
|
+
.setCustomKey("last_api_endpoint", endpoint)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fun setDatabaseVersion(version: Int) {
|
|
103
|
+
FirebaseCrashlytics.getInstance()
|
|
104
|
+
.setCustomKey("db_version", version)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Breadcrumb Logging
|
|
112
|
+
|
|
113
|
+
```kotlin
|
|
114
|
+
// ✅ Log events leading up to a crash
|
|
115
|
+
class CrashlyticsLogger @Inject constructor() {
|
|
116
|
+
|
|
117
|
+
fun log(message: String) {
|
|
118
|
+
FirebaseCrashlytics.getInstance().log(message)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fun logUserAction(action: String) {
|
|
122
|
+
FirebaseCrashlytics.getInstance().log("USER_ACTION: $action")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fun logNetworkCall(endpoint: String, statusCode: Int) {
|
|
126
|
+
FirebaseCrashlytics.getInstance()
|
|
127
|
+
.log("NETWORK: $endpoint -> $statusCode")
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Non-Fatal Error Reporting
|
|
135
|
+
|
|
136
|
+
```kotlin
|
|
137
|
+
// ✅ Report caught exceptions that indicate a problem
|
|
138
|
+
class UserRepository @Inject constructor(
|
|
139
|
+
private val crashlytics: CrashlyticsLogger
|
|
140
|
+
) {
|
|
141
|
+
suspend fun getUser(id: String): Result<User> {
|
|
142
|
+
return try {
|
|
143
|
+
val user = api.getUser(id)
|
|
144
|
+
Result.success(user.toDomain())
|
|
145
|
+
} catch (e: HttpException) {
|
|
146
|
+
if (e.code() == 500) {
|
|
147
|
+
// ✅ Server errors are worth tracking
|
|
148
|
+
FirebaseCrashlytics.getInstance().recordException(e)
|
|
149
|
+
}
|
|
150
|
+
Result.failure(e)
|
|
151
|
+
} catch (e: Exception) {
|
|
152
|
+
// ✅ Unexpected exceptions always reported
|
|
153
|
+
FirebaseCrashlytics.getInstance().recordException(e)
|
|
154
|
+
Result.failure(e)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ✅ In global error handler
|
|
160
|
+
class GlobalExceptionHandler @Inject constructor() {
|
|
161
|
+
|
|
162
|
+
fun report(throwable: Throwable, context: String) {
|
|
163
|
+
FirebaseCrashlytics.getInstance().apply {
|
|
164
|
+
setCustomKey("error_context", context)
|
|
165
|
+
recordException(throwable)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Integration with Timber
|
|
174
|
+
|
|
175
|
+
```kotlin
|
|
176
|
+
// ✅ Route Timber ERROR logs to Crashlytics
|
|
177
|
+
class CrashlyticsTree : Timber.Tree() {
|
|
178
|
+
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
|
179
|
+
if (priority < Log.ERROR) return
|
|
180
|
+
|
|
181
|
+
FirebaseCrashlytics.getInstance().apply {
|
|
182
|
+
tag?.let { setCustomKey("log_tag", it) }
|
|
183
|
+
log(message)
|
|
184
|
+
t?.let { recordException(it) }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Plant in Application
|
|
190
|
+
class MyApplication : Application() {
|
|
191
|
+
override fun onCreate() {
|
|
192
|
+
super.onCreate()
|
|
193
|
+
if (BuildConfig.DEBUG) {
|
|
194
|
+
Timber.plant(Timber.DebugTree())
|
|
195
|
+
} else {
|
|
196
|
+
Timber.plant(CrashlyticsTree())
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Testing Crashes
|
|
205
|
+
|
|
206
|
+
```kotlin
|
|
207
|
+
// ✅ Force a test crash (debug only)
|
|
208
|
+
if (BuildConfig.DEBUG) {
|
|
209
|
+
Button(onClick = {
|
|
210
|
+
FirebaseCrashlytics.getInstance().log("Test crash triggered")
|
|
211
|
+
throw RuntimeException("Test crash")
|
|
212
|
+
}) {
|
|
213
|
+
Text("Test Crash")
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Anti-Patterns
|
|
221
|
+
|
|
222
|
+
- Enabling Crashlytics in debug builds — pollutes crash data with dev noise
|
|
223
|
+
- No user context — impossible to reproduce user-specific crashes
|
|
224
|
+
- Reporting every caught exception — noise drowns out real issues; be selective
|
|
225
|
+
- No custom keys — crash report without state context is hard to diagnose
|
|
226
|
+
- Not logging breadcrumbs — no trail of events leading to the crash
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Related Skills
|
|
231
|
+
- `firebase` — Firebase core setup
|
|
232
|
+
- `analytics` — event tracking alongside crash reporting
|
|
233
|
+
- `logging` — structured logging complementing Crashlytics
|
|
234
|
+
- `error-handling` — error handling policy across layers
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: firebase
|
|
3
|
+
description: >
|
|
4
|
+
Firebase setup and core configuration for Android.
|
|
5
|
+
Load this skill when initializing Firebase, configuring google-services.json,
|
|
6
|
+
setting up multiple Firebase environments (dev/prod), or integrating Firebase
|
|
7
|
+
into a multi-module project.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Firebase
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Firebase is Google's app development platform providing backend services — authentication, messaging, crash reporting, analytics, remote config, and more. Each service is a separate SDK added independently. The core setup is shared across all Firebase services.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- One `google-services.json` per app variant — dev and prod must use separate Firebase projects
|
|
20
|
+
- Initialize Firebase **once** in `Application.onCreate()` — never in Activity or Fragment
|
|
21
|
+
- Use **separate Firebase projects** for debug and release — never mix environments
|
|
22
|
+
- Add only the Firebase SDKs you actually use — each adds APK size and startup overhead
|
|
23
|
+
- Never commit `google-services.json` with production credentials to public repos
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
```toml
|
|
30
|
+
[versions]
|
|
31
|
+
firebase-bom = "33.5.1"
|
|
32
|
+
google-services = "4.4.2"
|
|
33
|
+
|
|
34
|
+
[libraries]
|
|
35
|
+
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" }
|
|
36
|
+
# Individual SDKs — version managed by BOM
|
|
37
|
+
firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" }
|
|
38
|
+
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" }
|
|
39
|
+
firebase-messaging = { module = "com.google.firebase:firebase-messaging-ktx" }
|
|
40
|
+
firebase-auth = { module = "com.google.firebase:firebase-auth-ktx" }
|
|
41
|
+
firebase-config = { module = "com.google.firebase:firebase-config-ktx" }
|
|
42
|
+
|
|
43
|
+
[plugins]
|
|
44
|
+
google-services = { id = "com.google.gms.google-services", version.ref = "google-services" }
|
|
45
|
+
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.2" }
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```kotlin
|
|
49
|
+
// build.gradle.kts (root)
|
|
50
|
+
plugins {
|
|
51
|
+
alias(libs.plugins.google.services) apply false
|
|
52
|
+
alias(libs.plugins.firebase.crashlytics) apply false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// build.gradle.kts (app)
|
|
56
|
+
plugins {
|
|
57
|
+
alias(libs.plugins.google.services)
|
|
58
|
+
alias(libs.plugins.firebase.crashlytics)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
dependencies {
|
|
62
|
+
implementation(platform(libs.firebase.bom)) // ✅ BOM manages all versions
|
|
63
|
+
implementation(libs.firebase.analytics)
|
|
64
|
+
implementation(libs.firebase.crashlytics)
|
|
65
|
+
implementation(libs.firebase.messaging)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Application Setup
|
|
72
|
+
|
|
73
|
+
```kotlin
|
|
74
|
+
// ✅ Firebase auto-initializes via ContentProvider — no manual init needed
|
|
75
|
+
// FirebaseApp.initializeApp() is called automatically
|
|
76
|
+
|
|
77
|
+
@HiltAndroidApp
|
|
78
|
+
class MyApplication : Application() {
|
|
79
|
+
override fun onCreate() {
|
|
80
|
+
super.onCreate()
|
|
81
|
+
// Firebase is already initialized here
|
|
82
|
+
// Configure per-service settings if needed
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Multiple Environments (Dev / Prod)
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
// ✅ Separate google-services.json per build variant
|
|
93
|
+
app/
|
|
94
|
+
├── src/
|
|
95
|
+
│ ├── debug/
|
|
96
|
+
│ │ └── google-services.json ← dev Firebase project
|
|
97
|
+
│ └── release/
|
|
98
|
+
│ └── google-services.json ← prod Firebase project
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```kotlin
|
|
102
|
+
// ✅ Or configure via build flavors
|
|
103
|
+
android {
|
|
104
|
+
flavorDimensions += "environment"
|
|
105
|
+
productFlavors {
|
|
106
|
+
create("dev") {
|
|
107
|
+
dimension = "environment"
|
|
108
|
+
applicationIdSuffix = ".dev"
|
|
109
|
+
}
|
|
110
|
+
create("prod") {
|
|
111
|
+
dimension = "environment"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Files:
|
|
117
|
+
// app/src/dev/google-services.json
|
|
118
|
+
// app/src/prod/google-services.json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Manual Firebase App Initialization (Multi-Module / KMP)
|
|
124
|
+
|
|
125
|
+
```kotlin
|
|
126
|
+
// ✅ When automatic initialization isn't sufficient
|
|
127
|
+
val options = FirebaseOptions.Builder()
|
|
128
|
+
.setApiKey(BuildConfig.FIREBASE_API_KEY)
|
|
129
|
+
.setApplicationId(BuildConfig.FIREBASE_APP_ID)
|
|
130
|
+
.setProjectId(BuildConfig.FIREBASE_PROJECT_ID)
|
|
131
|
+
.build()
|
|
132
|
+
|
|
133
|
+
FirebaseApp.initializeApp(context, options, "secondary")
|
|
134
|
+
val secondaryApp = Firebase.app("secondary")
|
|
135
|
+
val secondaryDb = Firebase.database(secondaryApp)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Hilt Integration
|
|
141
|
+
|
|
142
|
+
```kotlin
|
|
143
|
+
// ✅ Provide Firebase services via Hilt
|
|
144
|
+
@Module
|
|
145
|
+
@InstallIn(SingletonComponent::class)
|
|
146
|
+
object FirebaseModule {
|
|
147
|
+
|
|
148
|
+
@Provides
|
|
149
|
+
@Singleton
|
|
150
|
+
fun provideFirebaseAnalytics(): FirebaseAnalytics =
|
|
151
|
+
FirebaseAnalytics.getInstance(get()) // or inject context
|
|
152
|
+
|
|
153
|
+
@Provides
|
|
154
|
+
@Singleton
|
|
155
|
+
fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig =
|
|
156
|
+
FirebaseRemoteConfig.getInstance().also { config ->
|
|
157
|
+
config.setConfigSettingsAsync(
|
|
158
|
+
remoteConfigSettings {
|
|
159
|
+
minimumFetchIntervalInSeconds = if (BuildConfig.DEBUG) 0 else 3600
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Disable Firebase in Debug
|
|
169
|
+
|
|
170
|
+
```kotlin
|
|
171
|
+
// ✅ Disable analytics and crashlytics in debug builds
|
|
172
|
+
class MyApplication : Application() {
|
|
173
|
+
override fun onCreate() {
|
|
174
|
+
super.onCreate()
|
|
175
|
+
if (BuildConfig.DEBUG) {
|
|
176
|
+
FirebaseAnalytics.getInstance(this).setAnalyticsCollectionEnabled(false)
|
|
177
|
+
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Anti-Patterns
|
|
186
|
+
|
|
187
|
+
- Single Firebase project for dev and prod — production data polluted with test events
|
|
188
|
+
- Committing `google-services.json` with prod keys to public repo — security risk
|
|
189
|
+
- Initializing Firebase in Activity — causes multiple initializations
|
|
190
|
+
- Adding all Firebase SDKs without using them — increases APK size and startup time
|
|
191
|
+
- Not using BOM — version conflicts between Firebase SDKs
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Related Skills
|
|
196
|
+
- `firebase-messaging` — push notifications with FCM
|
|
197
|
+
- `crashlytics` — crash reporting setup
|
|
198
|
+
- `analytics` — event tracking
|
|
199
|
+
- `remote-config` — feature flags from Firebase
|
|
200
|
+
- `firebase-auth` — authentication (not in current skill list — add if needed)
|