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,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: synchronization-policy
|
|
3
|
+
description: >
|
|
4
|
+
Thread safety and synchronization policy for Android with coroutines.
|
|
5
|
+
Load this skill when defining how shared state is accessed across threads,
|
|
6
|
+
deciding between confinement vs locking, ensuring data consistency,
|
|
7
|
+
or auditing code for race conditions.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Synchronization Policy
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Synchronization policy defines the rules for safely accessing shared mutable state across coroutines and threads. The safest approach is **confinement** — restricting access to a single thread or coroutine — rather than locking. When shared access is unavoidable, use coroutine-friendly primitives like `Mutex` and `StateFlow.update`.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
- Prefer **confinement** over locking — restrict state to one dispatcher or coroutine
|
|
21
|
+
- Prefer **immutable data** — no synchronization needed if data can't change
|
|
22
|
+
- Use `StateFlow.update` for UI state — it's atomic and coroutine-safe
|
|
23
|
+
- Use `Mutex` only when state must be shared across multiple coroutines
|
|
24
|
+
- Never access the same mutable state from both `Dispatchers.IO` and `Dispatchers.Main` without synchronization
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Strategy 1 — Confinement (Preferred)
|
|
29
|
+
|
|
30
|
+
```kotlin
|
|
31
|
+
// ✅ Confine mutable state to a single dispatcher
|
|
32
|
+
class DataCache @Inject constructor() {
|
|
33
|
+
// All access happens on a single-threaded dispatcher
|
|
34
|
+
private val cacheDispatcher = Dispatchers.IO.limitedParallelism(1)
|
|
35
|
+
private val cache = mutableMapOf<String, Data>()
|
|
36
|
+
|
|
37
|
+
suspend fun get(key: String): Data? = withContext(cacheDispatcher) {
|
|
38
|
+
cache[key]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
suspend fun put(key: String, data: Data) = withContext(cacheDispatcher) {
|
|
42
|
+
cache[key] = data
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
suspend fun clear() = withContext(cacheDispatcher) {
|
|
46
|
+
cache.clear()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Strategy 2 — Immutable State + Atomic Updates
|
|
54
|
+
|
|
55
|
+
```kotlin
|
|
56
|
+
// ✅ Immutable data class + atomic StateFlow update
|
|
57
|
+
class CartManager @Inject constructor() {
|
|
58
|
+
private val _cart = MutableStateFlow(Cart())
|
|
59
|
+
val cart: StateFlow<Cart> = _cart.asStateFlow()
|
|
60
|
+
|
|
61
|
+
fun addItem(item: CartItem) {
|
|
62
|
+
_cart.update { current ->
|
|
63
|
+
current.copy(items = current.items + item) // new list — immutable
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fun removeItem(itemId: String) {
|
|
68
|
+
_cart.update { current ->
|
|
69
|
+
current.copy(items = current.items.filter { it.id != itemId })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
data class Cart(
|
|
75
|
+
val items: List<CartItem> = emptyList() // immutable list
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Strategy 3 — Mutex for Shared Mutable State
|
|
82
|
+
|
|
83
|
+
```kotlin
|
|
84
|
+
// ✅ Use Mutex when multiple coroutines must share mutable state
|
|
85
|
+
class ConnectionPool @Inject constructor() {
|
|
86
|
+
private val mutex = Mutex()
|
|
87
|
+
private val connections = mutableListOf<Connection>()
|
|
88
|
+
|
|
89
|
+
suspend fun acquire(): Connection = mutex.withLock {
|
|
90
|
+
connections.removeFirstOrNull() ?: createNewConnection()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
suspend fun release(connection: Connection) = mutex.withLock {
|
|
94
|
+
connections.add(connection)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Choosing the Right Strategy
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Is the state only read after initialization?
|
|
105
|
+
→ Use val + immutable data class — no sync needed
|
|
106
|
+
|
|
107
|
+
Is the state only accessed from one coroutine/dispatcher?
|
|
108
|
+
→ Use confinement (limitedParallelism(1)) — no sync needed
|
|
109
|
+
|
|
110
|
+
Is the state a simple counter or flag?
|
|
111
|
+
→ Use AtomicInteger / AtomicBoolean — no coroutine sync needed
|
|
112
|
+
|
|
113
|
+
Is the state UI state in a ViewModel?
|
|
114
|
+
→ Use MutableStateFlow.update — built-in atomic update
|
|
115
|
+
|
|
116
|
+
Is the state accessed by multiple coroutines concurrently?
|
|
117
|
+
→ Use Mutex.withLock
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Thread Confinement with limitedParallelism
|
|
123
|
+
|
|
124
|
+
```kotlin
|
|
125
|
+
// ✅ Single-threaded context for all operations on shared resource
|
|
126
|
+
private val singleThreadContext = Dispatchers.IO.limitedParallelism(1)
|
|
127
|
+
|
|
128
|
+
// All database writes serialized through single thread
|
|
129
|
+
suspend fun writeData(data: List<Item>) = withContext(singleThreadContext) {
|
|
130
|
+
dao.deleteAll()
|
|
131
|
+
dao.insertAll(data)
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Common Race Conditions to Avoid
|
|
138
|
+
|
|
139
|
+
```kotlin
|
|
140
|
+
// ❌ Check-then-act race condition
|
|
141
|
+
if (cache[key] == null) { // thread A checks
|
|
142
|
+
cache[key] = expensiveCompute() // thread B also checks — both compute
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ✅ Fix with Mutex
|
|
146
|
+
mutex.withLock {
|
|
147
|
+
if (cache[key] == null) {
|
|
148
|
+
cache[key] = expensiveCompute()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ❌ Non-atomic read-modify-write
|
|
153
|
+
_state.value = _state.value.copy(count = _state.value.count + 1) // race
|
|
154
|
+
|
|
155
|
+
// ✅ Fix with update
|
|
156
|
+
_state.update { it.copy(count = it.count + 1) } // atomic
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Annotations for Documentation
|
|
162
|
+
|
|
163
|
+
```kotlin
|
|
164
|
+
// ✅ Document thread-safety expectations
|
|
165
|
+
@ThreadSafe
|
|
166
|
+
class SafeCache { /* ... */ }
|
|
167
|
+
|
|
168
|
+
@MainThread
|
|
169
|
+
fun updateUi(data: List<Item>) { /* ... */ }
|
|
170
|
+
|
|
171
|
+
@WorkerThread
|
|
172
|
+
suspend fun loadFromDisk(): List<Item> { /* ... */ }
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Anti-Patterns
|
|
178
|
+
|
|
179
|
+
- Accessing a `MutableList` from multiple coroutines without synchronization — race condition
|
|
180
|
+
- Using `synchronized {}` in coroutine code — blocks the thread
|
|
181
|
+
- Mutable `var` property in a class accessed from multiple dispatchers without protection
|
|
182
|
+
- Reading `StateFlow.value` and writing separately — use `update {}` instead
|
|
183
|
+
- Assuming coroutines on the same dispatcher are sequential — they may interleave at suspension points
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Related Skills
|
|
188
|
+
|
|
189
|
+
- `mutex-strategy` — Mutex and Semaphore implementation details
|
|
190
|
+
- `coroutine` — dispatcher selection and coroutine fundamentals
|
|
191
|
+
- `structured-concurrency` — coroutine lifetime and cancellation
|
|
192
|
+
- `stateflow` — atomic state updates with StateFlow
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: annotation-processing
|
|
3
|
+
description: >
|
|
4
|
+
Annotation processing setup and usage for Android development.
|
|
5
|
+
Load this skill when working with KSP or KAPT, creating custom annotations,
|
|
6
|
+
configuring code generation tools (Hilt, Room, Moshi, etc.), or
|
|
7
|
+
understanding how annotation processors fit into the build pipeline.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Annotation Processing
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Annotation processing generates code at compile time based on annotations in the source code. In Android, this powers libraries like Room, Hilt, Moshi, and Glide. KSP (Kotlin Symbol Processing) is the modern replacement for KAPT and should be preferred in all new projects.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
- **Always prefer KSP over KAPT** — KSP is faster, Kotlin-first, and KMP-compatible
|
|
21
|
+
- Use KAPT only when a library hasn't migrated to KSP yet
|
|
22
|
+
- Never mix KSP and KAPT for the same library
|
|
23
|
+
- Annotation processors run at **compile time** — errors are caught early
|
|
24
|
+
- Generated code lives in `build/generated/` — never edit it manually
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## KSP vs KAPT
|
|
29
|
+
|
|
30
|
+
| | KSP | KAPT |
|
|
31
|
+
| -------------- | -------------------- | ----------------------- |
|
|
32
|
+
| Speed | Fast (Kotlin-native) | Slow (stubs generation) |
|
|
33
|
+
| KMP Support | ✅ Yes | ❌ No |
|
|
34
|
+
| Incremental | ✅ Yes | Partial |
|
|
35
|
+
| Kotlin-first | ✅ Yes | ❌ Java-based |
|
|
36
|
+
| Recommendation | **Use this** | Only if no KSP support |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Setup
|
|
41
|
+
|
|
42
|
+
### KSP
|
|
43
|
+
|
|
44
|
+
```toml
|
|
45
|
+
# libs.versions.toml
|
|
46
|
+
[versions]
|
|
47
|
+
ksp = "2.0.0-1.0.21"
|
|
48
|
+
|
|
49
|
+
[plugins]
|
|
50
|
+
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```kotlin
|
|
54
|
+
// build.gradle.kts
|
|
55
|
+
plugins {
|
|
56
|
+
alias(libs.plugins.ksp)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
dependencies {
|
|
60
|
+
// KSP-based libraries
|
|
61
|
+
ksp(libs.hilt.compiler)
|
|
62
|
+
ksp(libs.room.compiler)
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### KAPT (legacy — only when KSP unavailable)
|
|
67
|
+
|
|
68
|
+
```kotlin
|
|
69
|
+
plugins {
|
|
70
|
+
kotlin("kapt")
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
dependencies {
|
|
74
|
+
kapt(libs.some.legacy.compiler)
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Common Libraries and Their Processor
|
|
81
|
+
|
|
82
|
+
| Library | Processor Type | Dependency |
|
|
83
|
+
| ----------- | -------------- | -------------------------------- |
|
|
84
|
+
| Hilt | KSP | `ksp(libs.hilt.compiler)` |
|
|
85
|
+
| Room | KSP | `ksp(libs.room.compiler)` |
|
|
86
|
+
| Moshi | KSP | `ksp(libs.moshi.kotlin.codegen)` |
|
|
87
|
+
| Glide | KAPT | `kapt(libs.glide.compiler)` |
|
|
88
|
+
| DataBinding | Built-in | No extra setup |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Custom Annotations
|
|
93
|
+
|
|
94
|
+
```kotlin
|
|
95
|
+
// ✅ Define annotation
|
|
96
|
+
@Target(AnnotationTarget.CLASS)
|
|
97
|
+
@Retention(AnnotationRetention.SOURCE)
|
|
98
|
+
annotation class AutoFactory
|
|
99
|
+
|
|
100
|
+
// ✅ Define annotation with parameters
|
|
101
|
+
@Target(AnnotationTarget.FUNCTION)
|
|
102
|
+
@Retention(AnnotationRetention.RUNTIME)
|
|
103
|
+
annotation class RequiresPermission(val value: String)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Custom KSP Processor
|
|
109
|
+
|
|
110
|
+
```kotlin
|
|
111
|
+
// ✅ Implement SymbolProcessor
|
|
112
|
+
class AutoFactoryProcessor(
|
|
113
|
+
private val codeGenerator: CodeGenerator,
|
|
114
|
+
private val logger: KSPLogger
|
|
115
|
+
) : SymbolProcessor {
|
|
116
|
+
|
|
117
|
+
override fun process(resolver: Resolver): List<KSAnnotated> {
|
|
118
|
+
val symbols = resolver
|
|
119
|
+
.getSymbolsWithAnnotation(AutoFactory::class.qualifiedName!!)
|
|
120
|
+
.filterIsInstance<KSClassDeclaration>()
|
|
121
|
+
|
|
122
|
+
symbols.forEach { classDecl ->
|
|
123
|
+
generateFactory(classDecl)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return emptyList()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private fun generateFactory(classDecl: KSClassDeclaration) {
|
|
130
|
+
val packageName = classDecl.packageName.asString()
|
|
131
|
+
val className = classDecl.simpleName.asString()
|
|
132
|
+
|
|
133
|
+
codeGenerator.createNewFile(
|
|
134
|
+
dependencies = Dependencies(false, classDecl.containingFile!!),
|
|
135
|
+
packageName = packageName,
|
|
136
|
+
fileName = "${className}Factory"
|
|
137
|
+
).use { stream ->
|
|
138
|
+
stream.write(generateFactoryCode(packageName, className).toByteArray())
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ✅ Register processor
|
|
144
|
+
class AutoFactoryProcessorProvider : SymbolProcessorProvider {
|
|
145
|
+
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor =
|
|
146
|
+
AutoFactoryProcessor(environment.codeGenerator, environment.logger)
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
# Register in resources/META-INF/services/
|
|
152
|
+
# com.google.devtools.ksp.processing.SymbolProcessorProvider
|
|
153
|
+
com.example.processor.AutoFactoryProcessorProvider
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## KSP in Multi-Module Projects
|
|
159
|
+
|
|
160
|
+
```kotlin
|
|
161
|
+
// ✅ Apply KSP only in modules that use it
|
|
162
|
+
// feature/build.gradle.kts
|
|
163
|
+
plugins {
|
|
164
|
+
alias(libs.plugins.ksp)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ✅ Room — schema export location per module
|
|
168
|
+
ksp {
|
|
169
|
+
arg("room.schemaLocation", "$projectDir/schemas")
|
|
170
|
+
arg("room.incremental", "true")
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ✅ Hilt — no extra config needed per module
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Incremental Processing
|
|
179
|
+
|
|
180
|
+
```kotlin
|
|
181
|
+
// ✅ Enable incremental annotation processing for Room
|
|
182
|
+
ksp {
|
|
183
|
+
arg("room.incremental", "true")
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ✅ KSP is incremental by default — no extra config needed
|
|
187
|
+
// ❌ KAPT incremental is unreliable — another reason to prefer KSP
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Debugging Annotation Processors
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# View generated code
|
|
196
|
+
# build/generated/ksp/<variant>/kotlin/
|
|
197
|
+
|
|
198
|
+
# Enable KSP verbose logging
|
|
199
|
+
ksp {
|
|
200
|
+
arg("verbose", "true")
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# Clean and rebuild when processor changes
|
|
204
|
+
./gradlew clean assembleDebug
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Anti-Patterns
|
|
210
|
+
|
|
211
|
+
- Using KAPT when KSP is available for the same library
|
|
212
|
+
- Mixing KAPT and KSP for the same library in the same module
|
|
213
|
+
- Editing generated files in `build/generated/` — they are overwritten on rebuild
|
|
214
|
+
- Applying KSP plugin to modules that don't need it — adds unnecessary compile overhead
|
|
215
|
+
- Using `@Retention(RUNTIME)` when `SOURCE` is sufficient — bloats the binary
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Related Skills
|
|
220
|
+
|
|
221
|
+
- `hilt` — Hilt DI with KSP setup
|
|
222
|
+
- `room` — Room database with KSP compiler
|
|
223
|
+
- `gradle` — build configuration and plugin setup
|
|
224
|
+
- `kmp` — KSP in multiplatform modules
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dsl
|
|
3
|
+
description: >
|
|
4
|
+
Kotlin DSL design and implementation patterns for Android development.
|
|
5
|
+
Load this skill when designing builder APIs, configuration blocks,
|
|
6
|
+
type-safe builders, or any fluent API that leverages Kotlin's DSL capabilities.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Kotlin DSL
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Kotlin DSLs (Domain-Specific Languages) allow expressing configuration, building objects, and defining structure in a readable, type-safe way using lambdas with receivers. Common in Gradle scripts, Compose, Ktor, and custom builders.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- DSLs should **read like natural language** in their domain
|
|
20
|
+
- DSLs should be **type-safe** — wrong usage must fail at compile time
|
|
21
|
+
- Use `@DslMarker` to prevent implicit receiver leakage
|
|
22
|
+
- Keep DSL scope **focused** — one responsibility per receiver class
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Basic DSL Pattern
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
// ✅ Lambda with receiver
|
|
30
|
+
class DialogBuilder {
|
|
31
|
+
var title: String = ""
|
|
32
|
+
var message: String = ""
|
|
33
|
+
private val buttons = mutableListOf<Button>()
|
|
34
|
+
|
|
35
|
+
fun button(label: String, action: () -> Unit) {
|
|
36
|
+
buttons.add(Button(label, action))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fun build(): Dialog = Dialog(title, message, buttons)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fun dialog(block: DialogBuilder.() -> Unit): Dialog {
|
|
43
|
+
return DialogBuilder().apply(block).build()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Usage
|
|
47
|
+
val dialog = dialog {
|
|
48
|
+
title = "Confirm"
|
|
49
|
+
message = "Are you sure?"
|
|
50
|
+
button("Yes") { confirm() }
|
|
51
|
+
button("No") { dismiss() }
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## @DslMarker — Preventing Receiver Leakage
|
|
58
|
+
|
|
59
|
+
```kotlin
|
|
60
|
+
// ✅ Always annotate DSL scopes with @DslMarker
|
|
61
|
+
@DslMarker
|
|
62
|
+
annotation class DialogDsl
|
|
63
|
+
|
|
64
|
+
@DialogDsl
|
|
65
|
+
class DialogBuilder { ... }
|
|
66
|
+
|
|
67
|
+
@DialogDsl
|
|
68
|
+
class ButtonBuilder { ... }
|
|
69
|
+
|
|
70
|
+
// Without @DslMarker, inner blocks can access outer receivers
|
|
71
|
+
// causing confusing and hard-to-debug behavior
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Type-Safe Builder Pattern
|
|
77
|
+
|
|
78
|
+
```kotlin
|
|
79
|
+
// ✅ Use for building structured/hierarchical data
|
|
80
|
+
@DslMarker
|
|
81
|
+
annotation class RouteDsl
|
|
82
|
+
|
|
83
|
+
@RouteDsl
|
|
84
|
+
class RouteBuilder {
|
|
85
|
+
private val routes = mutableListOf<Route>()
|
|
86
|
+
|
|
87
|
+
fun route(path: String, block: RouteConfig.() -> Unit) {
|
|
88
|
+
routes.add(RouteConfig(path).apply(block).build())
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fun build(): List<Route> = routes
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@RouteDsl
|
|
95
|
+
class RouteConfig(val path: String) {
|
|
96
|
+
var requiresAuth: Boolean = false
|
|
97
|
+
var deepLink: String? = null
|
|
98
|
+
|
|
99
|
+
fun build(): Route = Route(path, requiresAuth, deepLink)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fun routes(block: RouteBuilder.() -> Unit): List<Route> =
|
|
103
|
+
RouteBuilder().apply(block).build()
|
|
104
|
+
|
|
105
|
+
// Usage
|
|
106
|
+
val appRoutes = routes {
|
|
107
|
+
route("/home") {
|
|
108
|
+
requiresAuth = true
|
|
109
|
+
}
|
|
110
|
+
route("/login") {
|
|
111
|
+
deepLink = "app://login"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## DSL for Configuration
|
|
119
|
+
|
|
120
|
+
```kotlin
|
|
121
|
+
// ✅ Common pattern for library/module configuration
|
|
122
|
+
class NetworkConfig {
|
|
123
|
+
var baseUrl: String = ""
|
|
124
|
+
var timeoutMs: Long = 30_000
|
|
125
|
+
var retryCount: Int = 3
|
|
126
|
+
var enableLogging: Boolean = false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fun configureNetwork(block: NetworkConfig.() -> Unit): NetworkConfig =
|
|
130
|
+
NetworkConfig().apply(block)
|
|
131
|
+
|
|
132
|
+
// Usage
|
|
133
|
+
val config = configureNetwork {
|
|
134
|
+
baseUrl = "https://api.example.com"
|
|
135
|
+
timeoutMs = 60_000
|
|
136
|
+
enableLogging = BuildConfig.DEBUG
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## DSL in Gradle (Convention Plugins)
|
|
143
|
+
|
|
144
|
+
```kotlin
|
|
145
|
+
// ✅ Expose DSL-style API in convention plugins
|
|
146
|
+
fun Project.androidLibrary(block: LibraryExtension.() -> Unit) {
|
|
147
|
+
extensions.configure(LibraryExtension::class.java, block)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Usage in module build.gradle.kts
|
|
151
|
+
androidLibrary {
|
|
152
|
+
compileSdk = 35
|
|
153
|
+
defaultConfig {
|
|
154
|
+
minSdk = 24
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## When to Use DSL
|
|
162
|
+
|
|
163
|
+
| Use DSL | Don't Use DSL |
|
|
164
|
+
| -------------------------------------------------- | ----------------------------------- |
|
|
165
|
+
| Building complex objects with many optional fields | Simple data classes with few fields |
|
|
166
|
+
| Hierarchical/nested configuration | One-level flat configuration |
|
|
167
|
+
| Domain has natural language structure | Technical/algorithmic code |
|
|
168
|
+
| Builder would have 5+ parameters | Builder has 1-2 parameters |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Anti-Patterns
|
|
173
|
+
|
|
174
|
+
- DSL without `@DslMarker` — allows accidental outer receiver access
|
|
175
|
+
- Mutable state escaping the DSL scope — DSL output should be immutable
|
|
176
|
+
- DSL that requires calling methods in a specific order — use phases/staged builders instead
|
|
177
|
+
- Too many nested levels — more than 3 levels deep becomes unreadable
|
|
178
|
+
- Side effects inside DSL blocks — DSL should declare, not execute
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Related Skills
|
|
183
|
+
|
|
184
|
+
- `kotlin` — Kotlin language fundamentals
|
|
185
|
+
- `extension-functions-design` — extension functions used in DSL design
|
|
186
|
+
- `gradle` — Gradle Kotlin DSL conventions
|