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,304 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: version-catalog
|
|
3
|
+
description: >
|
|
4
|
+
Gradle Version Catalog setup and usage for Android projects.
|
|
5
|
+
Load this skill when managing dependency versions centrally,
|
|
6
|
+
adding new dependencies, or organizing libs.versions.toml.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Version Catalog
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
Gradle Version Catalog (`libs.versions.toml`) is the standard way to centralize all dependency versions in one file. It provides type-safe accessors in build scripts, prevents version conflicts, and makes upgrades visible in a single diff.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principles
|
|
17
|
+
|
|
18
|
+
- **All dependencies** go through Version Catalog — no inline versions anywhere
|
|
19
|
+
- Group related libraries under the same version alias when possible
|
|
20
|
+
- Use **bundles** for sets of dependencies that always go together
|
|
21
|
+
- Keep `libs.versions.toml` the **single source of truth** for all versions
|
|
22
|
+
- Separate **plugin versions** from library versions clearly
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## File Structure
|
|
27
|
+
|
|
28
|
+
```toml
|
|
29
|
+
# gradle/libs.versions.toml
|
|
30
|
+
|
|
31
|
+
[versions]
|
|
32
|
+
# SDK
|
|
33
|
+
android-compileSdk = "35"
|
|
34
|
+
android-minSdk = "24"
|
|
35
|
+
android-targetSdk = "35"
|
|
36
|
+
|
|
37
|
+
# Core
|
|
38
|
+
kotlin = "2.0.0"
|
|
39
|
+
ksp = "2.0.0-1.0.21"
|
|
40
|
+
agp = "8.7.0"
|
|
41
|
+
|
|
42
|
+
# AndroidX
|
|
43
|
+
androidx-core = "1.13.1"
|
|
44
|
+
androidx-lifecycle = "2.8.6"
|
|
45
|
+
androidx-navigation = "2.8.3"
|
|
46
|
+
androidx-room = "2.6.1"
|
|
47
|
+
androidx-datastore = "1.1.1"
|
|
48
|
+
androidx-paging = "3.3.2"
|
|
49
|
+
androidx-work = "2.9.1"
|
|
50
|
+
|
|
51
|
+
# Compose
|
|
52
|
+
compose-bom = "2024.10.01"
|
|
53
|
+
compose-compiler = "1.5.14"
|
|
54
|
+
|
|
55
|
+
# Network
|
|
56
|
+
retrofit = "2.11.0"
|
|
57
|
+
okhttp = "4.12.0"
|
|
58
|
+
ktor = "2.3.12"
|
|
59
|
+
|
|
60
|
+
# DI
|
|
61
|
+
hilt = "2.51.1"
|
|
62
|
+
hilt-navigation = "1.2.0"
|
|
63
|
+
|
|
64
|
+
# Firebase
|
|
65
|
+
firebase-bom = "33.5.1"
|
|
66
|
+
google-services = "4.4.2"
|
|
67
|
+
firebase-crashlytics-plugin = "3.0.2"
|
|
68
|
+
|
|
69
|
+
# Serialization
|
|
70
|
+
kotlinx-serialization = "1.7.3"
|
|
71
|
+
kotlinx-coroutines = "1.9.0"
|
|
72
|
+
|
|
73
|
+
# Media
|
|
74
|
+
media3 = "1.4.0"
|
|
75
|
+
camerax = "1.3.4"
|
|
76
|
+
|
|
77
|
+
# Testing
|
|
78
|
+
junit = "4.13.2"
|
|
79
|
+
junit5 = "5.10.2"
|
|
80
|
+
mockk = "1.13.12"
|
|
81
|
+
turbine = "1.1.0"
|
|
82
|
+
androidx-test-runner = "1.6.2"
|
|
83
|
+
espresso = "3.6.1"
|
|
84
|
+
|
|
85
|
+
# Quality
|
|
86
|
+
detekt = "1.23.7"
|
|
87
|
+
ktlint = "12.1.1"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
[libraries]
|
|
91
|
+
# AndroidX Core
|
|
92
|
+
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
|
|
93
|
+
androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" }
|
|
94
|
+
androidx-activity-compose = { module = "androidx.activity:activity-compose", version = "1.9.2" }
|
|
95
|
+
|
|
96
|
+
# Lifecycle
|
|
97
|
+
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
|
|
98
|
+
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" }
|
|
99
|
+
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
|
|
100
|
+
|
|
101
|
+
# Compose
|
|
102
|
+
compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
|
|
103
|
+
compose-ui = { module = "androidx.compose.ui:ui" }
|
|
104
|
+
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
|
|
105
|
+
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
|
|
106
|
+
compose-foundation = { module = "androidx.compose.foundation:foundation" }
|
|
107
|
+
compose-material3 = { module = "androidx.compose.material3:material3" }
|
|
108
|
+
compose-material-icons = { module = "androidx.compose.material:material-icons-extended" }
|
|
109
|
+
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
|
|
110
|
+
compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
|
|
111
|
+
|
|
112
|
+
# Navigation
|
|
113
|
+
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
|
|
114
|
+
|
|
115
|
+
# Room
|
|
116
|
+
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" }
|
|
117
|
+
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" }
|
|
118
|
+
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" }
|
|
119
|
+
androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "androidx-room" }
|
|
120
|
+
androidx-room-testing = { module = "androidx.room:room-testing", version.ref = "androidx-room" }
|
|
121
|
+
|
|
122
|
+
# DataStore
|
|
123
|
+
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
|
124
|
+
androidx-datastore-proto = { module = "androidx.datastore:datastore", version.ref = "androidx-datastore" }
|
|
125
|
+
|
|
126
|
+
# Paging
|
|
127
|
+
androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "androidx-paging" }
|
|
128
|
+
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" }
|
|
129
|
+
|
|
130
|
+
# WorkManager
|
|
131
|
+
androidx-work-runtime = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" }
|
|
132
|
+
androidx-work-hilt = { module = "androidx.hilt:hilt-work", version = "1.2.0" }
|
|
133
|
+
androidx-work-testing = { module = "androidx.work:work-testing", version.ref = "androidx-work" }
|
|
134
|
+
|
|
135
|
+
# Network
|
|
136
|
+
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
|
137
|
+
retrofit-kotlinx-serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" }
|
|
138
|
+
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
|
139
|
+
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
|
140
|
+
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
|
141
|
+
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
|
|
142
|
+
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
|
143
|
+
ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
|
144
|
+
|
|
145
|
+
# DI — Hilt
|
|
146
|
+
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
|
|
147
|
+
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
|
|
148
|
+
hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hilt-navigation" }
|
|
149
|
+
hilt-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" }
|
|
150
|
+
|
|
151
|
+
# Serialization / Coroutines
|
|
152
|
+
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
|
153
|
+
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
|
|
154
|
+
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
|
155
|
+
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
|
|
156
|
+
|
|
157
|
+
# Firebase
|
|
158
|
+
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase-bom" }
|
|
159
|
+
firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" }
|
|
160
|
+
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" }
|
|
161
|
+
firebase-messaging = { module = "com.google.firebase:firebase-messaging-ktx" }
|
|
162
|
+
firebase-config = { module = "com.google.firebase:firebase-config-ktx" }
|
|
163
|
+
firebase-auth = { module = "com.google.firebase:firebase-auth-ktx" }
|
|
164
|
+
|
|
165
|
+
# Media
|
|
166
|
+
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
|
|
167
|
+
media3-exoplayer-hls = { module = "androidx.media3:media3-exoplayer-hls", version.ref = "media3" }
|
|
168
|
+
media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
|
|
169
|
+
media3-session = { module = "androidx.media3:media3-session", version.ref = "media3" }
|
|
170
|
+
camerax-core = { module = "androidx.camera:camera-core", version.ref = "camerax" }
|
|
171
|
+
camerax-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" }
|
|
172
|
+
camerax-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" }
|
|
173
|
+
camerax-video = { module = "androidx.camera:camera-video", version.ref = "camerax" }
|
|
174
|
+
camerax-view = { module = "androidx.camera:camera-view", version.ref = "camerax" }
|
|
175
|
+
|
|
176
|
+
# Security
|
|
177
|
+
androidx-security-crypto = { module = "androidx.security:security-crypto", version = "1.1.0-alpha06" }
|
|
178
|
+
|
|
179
|
+
# Testing
|
|
180
|
+
junit = { module = "junit:junit", version.ref = "junit" }
|
|
181
|
+
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
|
|
182
|
+
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" }
|
|
183
|
+
turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }
|
|
184
|
+
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" }
|
|
185
|
+
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
|
|
186
|
+
androidx-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }
|
|
187
|
+
|
|
188
|
+
# Quality
|
|
189
|
+
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
[bundles]
|
|
193
|
+
# ✅ Bundles — groups of always-used-together dependencies
|
|
194
|
+
compose = [
|
|
195
|
+
"compose-ui",
|
|
196
|
+
"compose-foundation",
|
|
197
|
+
"compose-material3",
|
|
198
|
+
"compose-ui-tooling-preview",
|
|
199
|
+
"compose-material-icons"
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
lifecycle = [
|
|
203
|
+
"androidx-lifecycle-viewmodel",
|
|
204
|
+
"androidx-lifecycle-runtime",
|
|
205
|
+
"androidx-lifecycle-runtime-compose"
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
room = [
|
|
209
|
+
"androidx-room-runtime",
|
|
210
|
+
"androidx-room-ktx"
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
network = [
|
|
214
|
+
"retrofit",
|
|
215
|
+
"retrofit-kotlinx-serialization",
|
|
216
|
+
"okhttp",
|
|
217
|
+
"okhttp-logging"
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
coroutines = [
|
|
221
|
+
"kotlinx-coroutines-core",
|
|
222
|
+
"kotlinx-coroutines-android"
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
testing-unit = [
|
|
226
|
+
"junit",
|
|
227
|
+
"mockk",
|
|
228
|
+
"kotlinx-coroutines-test",
|
|
229
|
+
"turbine"
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
testing-android = [
|
|
233
|
+
"androidx-test-runner",
|
|
234
|
+
"androidx-junit",
|
|
235
|
+
"espresso-core"
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
[plugins]
|
|
240
|
+
android-application = { id = "com.android.application", version.ref = "agp" }
|
|
241
|
+
android-library = { id = "com.android.library", version.ref = "agp" }
|
|
242
|
+
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
|
243
|
+
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
|
244
|
+
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
|
245
|
+
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
|
246
|
+
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
|
247
|
+
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
|
248
|
+
google-services = { id = "com.google.gms.google-services", version.ref = "google-services" }
|
|
249
|
+
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics-plugin" }
|
|
250
|
+
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
|
|
251
|
+
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Using in build.gradle.kts
|
|
257
|
+
|
|
258
|
+
```kotlin
|
|
259
|
+
// ✅ Reference via type-safe accessors
|
|
260
|
+
dependencies {
|
|
261
|
+
// Single library
|
|
262
|
+
implementation(libs.androidx.core.ktx)
|
|
263
|
+
implementation(libs.hilt.android)
|
|
264
|
+
ksp(libs.hilt.compiler)
|
|
265
|
+
|
|
266
|
+
// Bundle
|
|
267
|
+
implementation(libs.bundles.compose)
|
|
268
|
+
implementation(libs.bundles.room)
|
|
269
|
+
ksp(libs.androidx.room.compiler)
|
|
270
|
+
|
|
271
|
+
// BOM
|
|
272
|
+
implementation(platform(libs.firebase.bom))
|
|
273
|
+
implementation(libs.firebase.analytics)
|
|
274
|
+
|
|
275
|
+
// Test
|
|
276
|
+
testImplementation(libs.bundles.testing.unit)
|
|
277
|
+
androidTestImplementation(libs.bundles.testing.android)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
plugins {
|
|
281
|
+
alias(libs.plugins.android.application)
|
|
282
|
+
alias(libs.plugins.kotlin.android)
|
|
283
|
+
alias(libs.plugins.hilt)
|
|
284
|
+
alias(libs.plugins.ksp)
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Anti-Patterns
|
|
291
|
+
|
|
292
|
+
- Inline versions in `build.gradle.kts` — `implementation("com.example:lib:1.0.0")`
|
|
293
|
+
- Duplicate version definitions across modules
|
|
294
|
+
- Not using bundles for frequently co-used dependencies
|
|
295
|
+
- Mixing BOM and explicit versions for same library group
|
|
296
|
+
- Not keeping catalog alphabetically sorted — hard to find entries
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Related Skills
|
|
301
|
+
- `gradle` — Gradle build configuration
|
|
302
|
+
- `convention-plugin` — using catalog in shared build logic
|
|
303
|
+
- `dependency-management` — resolving conflicts with catalog
|
|
304
|
+
- `build-variant` — applying different deps per variant
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: background-processing
|
|
3
|
+
description: >
|
|
4
|
+
Background processing strategies in Android.
|
|
5
|
+
Load this skill when choosing between WorkManager, coroutines, and
|
|
6
|
+
foreground services for background work, scheduling deferred tasks,
|
|
7
|
+
handling doze mode, or running work that must survive app death.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Background Processing
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Android restricts background processing to preserve battery and memory. The correct tool depends on whether the work must survive app death, needs to be scheduled, or must run immediately. WorkManager is the standard for deferrable, guaranteed work. Coroutines handle in-process background work. Foreground Services handle long-running user-visible work.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
- Use **WorkManager** for deferrable work that must survive app death or device restart
|
|
21
|
+
- Use **coroutines** (`Dispatchers.IO`) for in-process background work tied to app lifetime
|
|
22
|
+
- Use **ForegroundService** for long-running work that the user is aware of (music, upload)
|
|
23
|
+
- Never use `AsyncTask`, `HandlerThread`, or bare `Thread` — use coroutines
|
|
24
|
+
- Respect **Doze mode** — only WorkManager and ForegroundService work reliably in Doze
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Decision Matrix
|
|
29
|
+
|
|
30
|
+
| Work Type | Survives App Death | User Visible | Use |
|
|
31
|
+
| ------------------------- | ------------------ | ------------ | -------------------------- |
|
|
32
|
+
| Short async (network, DB) | ❌ | ❌ | Coroutine |
|
|
33
|
+
| Deferred, guaranteed | ✅ | ❌ | WorkManager |
|
|
34
|
+
| Periodic background sync | ✅ | ❌ | WorkManager |
|
|
35
|
+
| Long-running, user-aware | ✅ | ✅ | ForegroundService |
|
|
36
|
+
| Immediate, in-process | ❌ | ❌ | Coroutine + Dispatchers.IO |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## WorkManager — Deferrable Work
|
|
41
|
+
|
|
42
|
+
```kotlin
|
|
43
|
+
// ✅ Define a Worker
|
|
44
|
+
class SyncWorker(
|
|
45
|
+
context: Context,
|
|
46
|
+
params: WorkerParameters
|
|
47
|
+
) : CoroutineWorker(context, params) {
|
|
48
|
+
|
|
49
|
+
override suspend fun doWork(): Result {
|
|
50
|
+
return try {
|
|
51
|
+
val userId = inputData.getString("user_id") ?: return Result.failure()
|
|
52
|
+
syncRepository.syncUser(userId)
|
|
53
|
+
Result.success()
|
|
54
|
+
} catch (e: Exception) {
|
|
55
|
+
if (runAttemptCount < 3) Result.retry()
|
|
56
|
+
else Result.failure()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ✅ Schedule one-time work
|
|
62
|
+
val request = OneTimeWorkRequestBuilder<SyncWorker>()
|
|
63
|
+
.setInputData(workDataOf("user_id" to userId))
|
|
64
|
+
.setConstraints(
|
|
65
|
+
Constraints.Builder()
|
|
66
|
+
.setRequiredNetworkType(NetworkType.CONNECTED)
|
|
67
|
+
.build()
|
|
68
|
+
)
|
|
69
|
+
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 15, TimeUnit.MINUTES)
|
|
70
|
+
.build()
|
|
71
|
+
|
|
72
|
+
WorkManager.getInstance(context).enqueueUniqueWork(
|
|
73
|
+
"sync_user_$userId",
|
|
74
|
+
ExistingWorkPolicy.KEEP,
|
|
75
|
+
request
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
// ✅ Schedule periodic work
|
|
79
|
+
val periodicRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.HOURS)
|
|
80
|
+
.setConstraints(
|
|
81
|
+
Constraints.Builder()
|
|
82
|
+
.setRequiredNetworkType(NetworkType.CONNECTED)
|
|
83
|
+
.setRequiresBatteryNotLow(true)
|
|
84
|
+
.build()
|
|
85
|
+
)
|
|
86
|
+
.build()
|
|
87
|
+
|
|
88
|
+
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
|
89
|
+
"periodic_sync",
|
|
90
|
+
ExistingPeriodicWorkPolicy.KEEP,
|
|
91
|
+
periodicRequest
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Coroutine Background Work
|
|
98
|
+
|
|
99
|
+
```kotlin
|
|
100
|
+
// ✅ Short background work — in-process only
|
|
101
|
+
class DataRepository @Inject constructor(
|
|
102
|
+
private val api: DataApi,
|
|
103
|
+
private val dao: DataDao
|
|
104
|
+
) {
|
|
105
|
+
suspend fun refreshData() = withContext(Dispatchers.IO) {
|
|
106
|
+
val data = api.getData()
|
|
107
|
+
dao.insertAll(data)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ✅ Long-running in-process work with custom scope
|
|
112
|
+
@Singleton
|
|
113
|
+
class PollingManager @Inject constructor(
|
|
114
|
+
private val repository: DataRepository
|
|
115
|
+
) {
|
|
116
|
+
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
117
|
+
|
|
118
|
+
fun startPolling() {
|
|
119
|
+
scope.launch {
|
|
120
|
+
while (isActive) {
|
|
121
|
+
runCatching { repository.refreshData() }
|
|
122
|
+
delay(30_000)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fun stopPolling() = scope.cancel()
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Observing WorkManager Progress
|
|
134
|
+
|
|
135
|
+
```kotlin
|
|
136
|
+
// ✅ Observe work state in ViewModel
|
|
137
|
+
class SyncViewModel @Inject constructor(
|
|
138
|
+
private val workManager: WorkManager
|
|
139
|
+
) : ViewModel() {
|
|
140
|
+
|
|
141
|
+
val syncState: LiveData<WorkInfo> = workManager
|
|
142
|
+
.getWorkInfosForUniqueWorkLiveData("periodic_sync")
|
|
143
|
+
.map { workInfos -> workInfos.firstOrNull() }
|
|
144
|
+
.filterNotNull()
|
|
145
|
+
|
|
146
|
+
// Or as Flow
|
|
147
|
+
val syncStateFlow: Flow<WorkInfo?> = workManager
|
|
148
|
+
.getWorkInfosForUniqueWorkFlow("periodic_sync")
|
|
149
|
+
.map { it.firstOrNull() }
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Chaining Work
|
|
156
|
+
|
|
157
|
+
```kotlin
|
|
158
|
+
// ✅ Chain workers sequentially
|
|
159
|
+
WorkManager.getInstance(context)
|
|
160
|
+
.beginUniqueWork("upload_chain", ExistingWorkPolicy.REPLACE,
|
|
161
|
+
OneTimeWorkRequestBuilder<CompressWorker>().build()
|
|
162
|
+
)
|
|
163
|
+
.then(OneTimeWorkRequestBuilder<UploadWorker>().build())
|
|
164
|
+
.then(OneTimeWorkRequestBuilder<NotifyWorker>().build())
|
|
165
|
+
.enqueue()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Anti-Patterns
|
|
171
|
+
|
|
172
|
+
- Using `GlobalScope` for work that should survive — use WorkManager instead
|
|
173
|
+
- Running network calls in a bare `Thread` — use coroutines with Dispatchers.IO
|
|
174
|
+
- Using `AlarmManager` for periodic work — WorkManager handles this better
|
|
175
|
+
- Starting a ForegroundService for short work — use WorkManager
|
|
176
|
+
- Not setting constraints on WorkManager — work runs even on metered network or low battery
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Related Skills
|
|
181
|
+
|
|
182
|
+
- `workmanager` — detailed WorkManager patterns
|
|
183
|
+
- `foreground-service` — long-running user-visible work
|
|
184
|
+
- `coroutine` — coroutine fundamentals and dispatcher selection
|
|
185
|
+
- `structured-concurrency` — managing coroutine lifecycle
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: channel
|
|
3
|
+
description: >
|
|
4
|
+
Kotlin Channel for guaranteed single-consumer message delivery in Android.
|
|
5
|
+
Load this skill when implementing one-time UI events with delivery guarantee,
|
|
6
|
+
building producer-consumer pipelines, handling work queues,
|
|
7
|
+
or choosing between Channel and SharedFlow for event delivery.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Channel
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
`Channel` is a coroutine primitive for communication between coroutines. Unlike `SharedFlow`, a `Channel` guarantees delivery to exactly one consumer and buffers messages when the consumer is not active. On Android, `Channel` is the preferred mechanism for one-time ViewModel events (navigation, toasts) where delivery must be guaranteed.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
- Use `Channel` when delivery must be **guaranteed** — events must not be dropped or missed
|
|
21
|
+
- Use `SharedFlow` when **broadcasting** to multiple collectors is needed
|
|
22
|
+
- Always expose `Channel` as `receiveAsFlow()` — never expose the raw `Channel`
|
|
23
|
+
- Use `Channel.BUFFERED` capacity — prevents suspension when collector is temporarily inactive
|
|
24
|
+
- One `Channel` per event type, or a sealed class `Channel` for all events from one ViewModel
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Basic Channel for UI Events
|
|
29
|
+
|
|
30
|
+
```kotlin
|
|
31
|
+
// ✅ Standard ViewModel event channel
|
|
32
|
+
class UserViewModel : ViewModel() {
|
|
33
|
+
|
|
34
|
+
private val _events = Channel<UserEvent>(Channel.BUFFERED)
|
|
35
|
+
val events: Flow<UserEvent> = _events.receiveAsFlow()
|
|
36
|
+
|
|
37
|
+
fun onSaveSuccess() {
|
|
38
|
+
viewModelScope.launch {
|
|
39
|
+
_events.send(UserEvent.NavigateBack)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fun onError(message: String) {
|
|
44
|
+
viewModelScope.launch {
|
|
45
|
+
_events.send(UserEvent.ShowSnackbar(message))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
sealed interface UserEvent {
|
|
51
|
+
data object NavigateBack : UserEvent
|
|
52
|
+
data class ShowSnackbar(val message: String) : UserEvent
|
|
53
|
+
data class NavigateToDetail(val userId: String) : UserEvent
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Collecting in Compose
|
|
60
|
+
|
|
61
|
+
```kotlin
|
|
62
|
+
// ✅ Collect channel events in LaunchedEffect
|
|
63
|
+
@Composable
|
|
64
|
+
fun UserScreen(
|
|
65
|
+
onNavigateBack: () -> Unit,
|
|
66
|
+
onNavigateToDetail: (String) -> Unit,
|
|
67
|
+
viewModel: UserViewModel = hiltViewModel()
|
|
68
|
+
) {
|
|
69
|
+
val snackbarHostState = remember { SnackbarHostState() }
|
|
70
|
+
|
|
71
|
+
LaunchedEffect(Unit) {
|
|
72
|
+
viewModel.events.collect { event ->
|
|
73
|
+
when (event) {
|
|
74
|
+
is UserEvent.NavigateBack -> onNavigateBack()
|
|
75
|
+
is UserEvent.ShowSnackbar -> snackbarHostState.showSnackbar(event.message)
|
|
76
|
+
is UserEvent.NavigateToDetail -> onNavigateToDetail(event.userId)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { padding ->
|
|
82
|
+
// screen content
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Channel Capacity Options
|
|
90
|
+
|
|
91
|
+
```kotlin
|
|
92
|
+
// ✅ BUFFERED — default buffer (64 elements), send never suspends unless full
|
|
93
|
+
Channel<Event>(Channel.BUFFERED)
|
|
94
|
+
|
|
95
|
+
// ✅ UNLIMITED — unbounded buffer, send never suspends
|
|
96
|
+
Channel<Event>(Channel.UNLIMITED)
|
|
97
|
+
|
|
98
|
+
// ✅ RENDEZVOUS — no buffer, send suspends until receive
|
|
99
|
+
Channel<Event>(Channel.RENDEZVOUS)
|
|
100
|
+
|
|
101
|
+
// ✅ CONFLATED — keeps only the latest, old values dropped
|
|
102
|
+
Channel<Event>(Channel.CONFLATED)
|
|
103
|
+
|
|
104
|
+
// ✅ Custom capacity
|
|
105
|
+
Channel<Event>(capacity = 32)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Producer-Consumer Pipeline
|
|
111
|
+
|
|
112
|
+
```kotlin
|
|
113
|
+
// ✅ Work queue with Channel
|
|
114
|
+
class ImageProcessingQueue @Inject constructor() {
|
|
115
|
+
private val queue = Channel<ImageTask>(capacity = 32)
|
|
116
|
+
|
|
117
|
+
suspend fun enqueue(task: ImageTask) {
|
|
118
|
+
queue.send(task)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fun startProcessing(scope: CoroutineScope) {
|
|
122
|
+
scope.launch(Dispatchers.Default) {
|
|
123
|
+
for (task in queue) { // ✅ for loop — processes until channel is closed
|
|
124
|
+
processImage(task)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fun close() {
|
|
130
|
+
queue.close()
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Channel vs SharedFlow
|
|
138
|
+
|
|
139
|
+
| | Channel | SharedFlow |
|
|
140
|
+
| ------------- | ----------------------- | ------------------------------ |
|
|
141
|
+
| Consumers | Single | Multiple |
|
|
142
|
+
| Delivery | Guaranteed (buffered) | Not guaranteed (can miss) |
|
|
143
|
+
| Replay | No | Configurable |
|
|
144
|
+
| Best for | UI events, work queues | Broadcast events, status |
|
|
145
|
+
| Missed events | Buffered until consumed | Dropped if no active collector |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## trySend vs send
|
|
150
|
+
|
|
151
|
+
```kotlin
|
|
152
|
+
// ✅ send — suspends if buffer full (use inside coroutines)
|
|
153
|
+
viewModelScope.launch {
|
|
154
|
+
_events.send(UserEvent.NavigateBack)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ✅ trySend — returns ChannelResult, non-suspending (use outside coroutines)
|
|
158
|
+
val result = _events.trySend(UserEvent.NavigateBack)
|
|
159
|
+
if (result.isFailure) Timber.w("Event could not be sent — channel full or closed")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Closing a Channel
|
|
165
|
+
|
|
166
|
+
```kotlin
|
|
167
|
+
// ✅ Close channel when done producing — consumer loop ends
|
|
168
|
+
class DataProducer {
|
|
169
|
+
private val channel = Channel<Data>(Channel.BUFFERED)
|
|
170
|
+
|
|
171
|
+
suspend fun produce() {
|
|
172
|
+
try {
|
|
173
|
+
dataList.forEach { channel.send(it) }
|
|
174
|
+
} finally {
|
|
175
|
+
channel.close() // ✅ always close in finally
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fun consume(scope: CoroutineScope) {
|
|
180
|
+
scope.launch {
|
|
181
|
+
for (data in channel) { // ends when channel is closed
|
|
182
|
+
process(data)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Anti-Patterns
|
|
192
|
+
|
|
193
|
+
- Exposing raw `MutableSharedFlow` or `Channel` — external code can send/close
|
|
194
|
+
- Not using `receiveAsFlow()` — exposes Channel API to consumers
|
|
195
|
+
- Using `Channel.RENDEZVOUS` for UI events — send suspends if UI isn't collecting
|
|
196
|
+
- Using `SharedFlow` when you need guaranteed single delivery — events can be missed
|
|
197
|
+
- Forgetting to close the channel in producer — consumer loop hangs forever
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Related Skills
|
|
202
|
+
|
|
203
|
+
- `sharedflow` — broadcast to multiple collectors
|
|
204
|
+
- `stateflow` — persistent UI state
|
|
205
|
+
- `flow` — cold stream fundamentals
|
|
206
|
+
- `coroutine` — structured concurrency and scopes
|
|
207
|
+
- `mvvm` — event pattern in ViewModel
|