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,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: extension-functions-design
|
|
3
|
+
description: >
|
|
4
|
+
Design principles and patterns for Kotlin extension functions in Android.
|
|
5
|
+
Load this skill when deciding whether to use an extension function,
|
|
6
|
+
where to place it, how to scope it, and how to avoid common misuse patterns.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Extension Functions Design
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Extension functions add behavior to existing types without inheritance or modification. They are one of Kotlin's most powerful features but must be used with discipline — overuse leads to scattered, hard-to-discover code.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Extensions are **discoverable utilities**, not replacements for proper design
|
|
20
|
+
- Only extend types **you don't own** or when inheritance is impossible
|
|
21
|
+
- If you own the type, **add the method directly** — don't use an extension
|
|
22
|
+
- Extensions must be **stateless and pure** — no side effects, no hidden dependencies
|
|
23
|
+
- Place extensions **close to where they're used** — not in a global utils file
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## When to Use Extension Functions
|
|
28
|
+
|
|
29
|
+
| Situation | Use Extension? |
|
|
30
|
+
| ---------------------------------------------------------------- | ---------------------------------- |
|
|
31
|
+
| Adding utility to stdlib types (`String`, `List`, `Int`) | ✅ Yes |
|
|
32
|
+
| Adding Android-specific helpers to `Context`, `View`, `Fragment` | ✅ Yes |
|
|
33
|
+
| Adding behavior to a class you own | ❌ No — add method directly |
|
|
34
|
+
| Replacing a utility class method | ✅ Yes |
|
|
35
|
+
| Adding domain logic to a domain model | ❌ No — add to the class or UseCase |
|
|
36
|
+
| Bypassing encapsulation to access internals | ❌ Never |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Placement Strategy
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
// ✅ Place extensions next to the type they extend
|
|
44
|
+
// If extending Context → ContextExtensions.kt
|
|
45
|
+
// If extending String → StringExtensions.kt
|
|
46
|
+
// If extending View → ViewExtensions.kt
|
|
47
|
+
|
|
48
|
+
// ✅ Place feature-specific extensions inside the feature package
|
|
49
|
+
// feature/auth/AuthExtensions.kt — extensions only used in auth
|
|
50
|
+
// NOT in a global utils/Extensions.kt that grows forever
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Stdlib Extensions
|
|
56
|
+
|
|
57
|
+
```kotlin
|
|
58
|
+
// ✅ String utilities
|
|
59
|
+
fun String.toSlug(): String =
|
|
60
|
+
lowercase().replace(" ", "-").replace(Regex("[^a-z0-9-]"), "")
|
|
61
|
+
|
|
62
|
+
fun String.isValidEmail(): Boolean =
|
|
63
|
+
android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
|
|
64
|
+
|
|
65
|
+
fun String?.orEmpty(): String = this ?: ""
|
|
66
|
+
|
|
67
|
+
// ✅ Number formatting
|
|
68
|
+
fun Int.toPx(context: Context): Int =
|
|
69
|
+
(this * context.resources.displayMetrics.density).toInt()
|
|
70
|
+
|
|
71
|
+
fun Long.toFormattedDate(): String =
|
|
72
|
+
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(this))
|
|
73
|
+
|
|
74
|
+
// ✅ Collection utilities
|
|
75
|
+
fun <T> List<T>.second(): T = this[1]
|
|
76
|
+
fun <T> List<T>.indexOfFirstOrNull(predicate: (T) -> Boolean): Int? =
|
|
77
|
+
indexOfFirst(predicate).takeIf { it >= 0 }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Android Platform Extensions
|
|
83
|
+
|
|
84
|
+
```kotlin
|
|
85
|
+
// ✅ Context extensions
|
|
86
|
+
fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
|
|
87
|
+
Toast.makeText(this, message, duration).show()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fun Context.getColorCompat(@ColorRes colorRes: Int): Int =
|
|
91
|
+
ContextCompat.getColor(this, colorRes)
|
|
92
|
+
|
|
93
|
+
fun Context.dpToPx(dp: Float): Int =
|
|
94
|
+
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics).toInt()
|
|
95
|
+
|
|
96
|
+
// ✅ View extensions
|
|
97
|
+
fun View.show() { visibility = View.VISIBLE }
|
|
98
|
+
fun View.hide() { visibility = View.GONE }
|
|
99
|
+
fun View.invisible() { visibility = View.INVISIBLE }
|
|
100
|
+
|
|
101
|
+
fun View.setOnSingleClickListener(action: () -> Unit) {
|
|
102
|
+
var lastClickTime = 0L
|
|
103
|
+
setOnClickListener {
|
|
104
|
+
val now = System.currentTimeMillis()
|
|
105
|
+
if (now - lastClickTime > 500) {
|
|
106
|
+
lastClickTime = now
|
|
107
|
+
action()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ✅ Fragment extensions
|
|
113
|
+
fun Fragment.hideKeyboard() {
|
|
114
|
+
val imm = requireContext().getSystemService(InputMethodManager::class.java)
|
|
115
|
+
imm.hideSoftInputFromWindow(requireView().windowToken, 0)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Flow / Coroutine Extensions
|
|
122
|
+
|
|
123
|
+
```kotlin
|
|
124
|
+
// ✅ Lifecycle-aware collection helper
|
|
125
|
+
fun <T> Flow<T>.collectWithLifecycle(
|
|
126
|
+
lifecycleOwner: LifecycleOwner,
|
|
127
|
+
state: Lifecycle.State = Lifecycle.State.STARTED,
|
|
128
|
+
block: suspend (T) -> Unit
|
|
129
|
+
) {
|
|
130
|
+
lifecycleOwner.lifecycleScope.launch {
|
|
131
|
+
lifecycleOwner.repeatOnLifecycle(state) {
|
|
132
|
+
collect { block(it) }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ✅ Result extensions
|
|
138
|
+
fun <T> Result<T>.onSuccess(action: (T) -> Unit): Result<T> {
|
|
139
|
+
if (isSuccess) action(getOrThrow())
|
|
140
|
+
return this
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Scoping with Companion Objects
|
|
147
|
+
|
|
148
|
+
```kotlin
|
|
149
|
+
// ✅ Scope extension to a specific context using companion object
|
|
150
|
+
class UserValidator {
|
|
151
|
+
companion object // empty companion — allows scoped extensions
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fun UserValidator.Companion.isValidAge(age: Int): Boolean = age in 18..120
|
|
155
|
+
|
|
156
|
+
// Usage
|
|
157
|
+
UserValidator.isValidAge(25)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Anti-Patterns
|
|
163
|
+
|
|
164
|
+
```kotlin
|
|
165
|
+
// ❌ Extending a class you own — add the method directly
|
|
166
|
+
class User(val name: String)
|
|
167
|
+
fun User.displayName() = name.trim() // wrong — add to User class
|
|
168
|
+
|
|
169
|
+
// ❌ Extension that accesses internal state via reflection or casting
|
|
170
|
+
fun Any.forceGetField(name: String): Any? { ... } // bypasses encapsulation
|
|
171
|
+
|
|
172
|
+
// ❌ Extension with hidden dependencies
|
|
173
|
+
fun String.sendToAnalytics() {
|
|
174
|
+
AnalyticsSingleton.track(this) // hidden side effect — not obvious from signature
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ❌ Giant global Extensions.kt file
|
|
178
|
+
// utils/Extensions.kt with 500 lines of unrelated extensions
|
|
179
|
+
// → split by type and feature
|
|
180
|
+
|
|
181
|
+
// ❌ Extension that duplicates stdlib
|
|
182
|
+
fun List<Int>.mySum() = reduce { acc, i -> acc + i } // use sum() instead
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Related Skills
|
|
188
|
+
|
|
189
|
+
- `kotlin` — core Kotlin language conventions
|
|
190
|
+
- `dsl` — extension functions as DSL building blocks
|
|
191
|
+
- `reactive-streams` — Flow extension patterns
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: immutability
|
|
3
|
+
description: >
|
|
4
|
+
Immutability patterns and enforcement for Android/Kotlin development.
|
|
5
|
+
Load this skill when designing data models, state classes, collections,
|
|
6
|
+
or any shared data structure to ensure thread safety and predictable behavior.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Immutability
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
Immutability means that once an object is created, its state cannot change. In Android development, immutability is critical for thread safety, predictable UI state, and preventing hard-to-debug side effects.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Default to `val` — only use `var` when mutation is genuinely required
|
|
20
|
+
- Default to immutable collections — only use mutable when building
|
|
21
|
+
- State should flow in one direction — never mutate state from outside
|
|
22
|
+
- Shared data across threads must always be immutable
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Properties
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
// ✅ val by default
|
|
30
|
+
data class User(val id: String, val name: String, val email: String)
|
|
31
|
+
|
|
32
|
+
// ✅ var only when genuinely needed (e.g., local accumulator)
|
|
33
|
+
var count = 0
|
|
34
|
+
repeat(10) { count++ }
|
|
35
|
+
|
|
36
|
+
// ❌ var on a data class field shared across layers
|
|
37
|
+
data class User(var name: String) // mutability leaks outside
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Data Classes and copy()
|
|
43
|
+
|
|
44
|
+
```kotlin
|
|
45
|
+
// ✅ Use copy() to produce new state — never mutate
|
|
46
|
+
data class UiState(
|
|
47
|
+
val isLoading: Boolean = false,
|
|
48
|
+
val items: List<Item> = emptyList(),
|
|
49
|
+
val error: String? = null
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// In ViewModel
|
|
53
|
+
_uiState.update { it.copy(isLoading = true) }
|
|
54
|
+
_uiState.update { it.copy(isLoading = false, items = newItems) }
|
|
55
|
+
|
|
56
|
+
// ❌ Never do this
|
|
57
|
+
_uiState.value.items = newItems // not possible with val, but don't expose mutable state
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Collections
|
|
63
|
+
|
|
64
|
+
```kotlin
|
|
65
|
+
// ✅ Expose immutable collections from all public APIs
|
|
66
|
+
class UserRepository {
|
|
67
|
+
fun getUsers(): List<User> = _users.toList() // defensive copy
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ✅ Build with mutable, expose as immutable
|
|
71
|
+
val users: List<User> = buildList {
|
|
72
|
+
add(User("1", "Ali"))
|
|
73
|
+
add(User("2", "Sara"))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ✅ StateFlow always holds immutable state
|
|
77
|
+
private val _uiState = MutableStateFlow(UiState())
|
|
78
|
+
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
|
|
79
|
+
|
|
80
|
+
// ❌ Never expose MutableStateFlow or MutableList
|
|
81
|
+
val uiState = MutableStateFlow(UiState()) // external code can modify
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Immutable State in ViewModel
|
|
87
|
+
|
|
88
|
+
```kotlin
|
|
89
|
+
// ✅ Correct pattern — immutable state, controlled updates
|
|
90
|
+
class UserViewModel : ViewModel() {
|
|
91
|
+
|
|
92
|
+
private val _state = MutableStateFlow(UserUiState())
|
|
93
|
+
val state: StateFlow<UserUiState> = _state.asStateFlow()
|
|
94
|
+
|
|
95
|
+
fun onNameChanged(name: String) {
|
|
96
|
+
_state.update { it.copy(name = name) }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ✅ State class — all vals
|
|
101
|
+
data class UserUiState(
|
|
102
|
+
val name: String = "",
|
|
103
|
+
val isLoading: Boolean = false,
|
|
104
|
+
val error: String? = null
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Immutability Across Layers
|
|
111
|
+
|
|
112
|
+
| Layer | Rule |
|
|
113
|
+
| ----------------------- | ------------------------------------------------ |
|
|
114
|
+
| Domain models | Always immutable (`data class` with `val`) |
|
|
115
|
+
| DTOs | Always immutable (`data class` with `val`) |
|
|
116
|
+
| UI State | Always immutable — update via `copy()` |
|
|
117
|
+
| Repository return types | Return `List<T>` not `MutableList<T>` |
|
|
118
|
+
| Exposed flows | Always `StateFlow`/`Flow` not `MutableStateFlow` |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Thread Safety Through Immutability
|
|
123
|
+
|
|
124
|
+
```kotlin
|
|
125
|
+
// ✅ Immutable objects are safe to share across coroutines
|
|
126
|
+
data class Config(val baseUrl: String, val timeout: Long)
|
|
127
|
+
|
|
128
|
+
// Safe — no synchronization needed
|
|
129
|
+
val config = Config("https://api.example.com", 30_000)
|
|
130
|
+
launch(Dispatchers.IO) { fetchData(config) }
|
|
131
|
+
launch(Dispatchers.Default) { processData(config) }
|
|
132
|
+
|
|
133
|
+
// ❌ Mutable shared state requires synchronization
|
|
134
|
+
class Config {
|
|
135
|
+
var baseUrl: String = "" // race condition if accessed from multiple threads
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Anti-Patterns
|
|
142
|
+
|
|
143
|
+
- `var` properties on domain models or state classes
|
|
144
|
+
- Exposing `MutableList`, `MutableMap`, or `MutableStateFlow` from public APIs
|
|
145
|
+
- Mutating a list after exposing it — always return a defensive copy
|
|
146
|
+
- Using `apply {}` to mutate an object after it's been shared
|
|
147
|
+
- Casting `List<T>` to `MutableList<T>` — breaks immutability contract
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Related Skills
|
|
152
|
+
|
|
153
|
+
- `kotlin` — `val`/`var` and data class conventions
|
|
154
|
+
- `state-management` — UI state modeling with immutable data
|
|
155
|
+
- `coroutine` — thread safety with immutable shared state
|
|
156
|
+
- `repository-pattern` — immutable return types from repositories
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kmp
|
|
3
|
+
description: >
|
|
4
|
+
Kotlin Multiplatform (KMP) setup, structure, and best practices.
|
|
5
|
+
Load this skill when setting up a KMP project, sharing code between
|
|
6
|
+
Android and other platforms, or deciding what belongs in shared vs
|
|
7
|
+
platform-specific modules.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Kotlin Multiplatform (KMP)
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
KMP allows sharing Kotlin code across platforms (Android, iOS, Desktop, Web) while keeping platform-specific implementations separate. The goal is to maximize shared logic while respecting platform boundaries.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
- Share **business logic** — not UI, not platform APIs
|
|
21
|
+
- Use `expect/actual` only when necessary — prefer interface + DI
|
|
22
|
+
- Keep shared code free of any platform dependency
|
|
23
|
+
- The `commonMain` module must never import Android or iOS specific APIs
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Module Structure
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
project/
|
|
31
|
+
├── shared/
|
|
32
|
+
│ ├── src/
|
|
33
|
+
│ │ ├── commonMain/kotlin/ ← shared business logic
|
|
34
|
+
│ │ ├── commonTest/kotlin/ ← shared unit tests
|
|
35
|
+
│ │ ├── androidMain/kotlin/ ← Android actual implementations
|
|
36
|
+
│ │ └── iosMain/kotlin/ ← iOS actual implementations
|
|
37
|
+
├── androidApp/ ← Android UI & entry point
|
|
38
|
+
└── iosApp/ ← iOS UI & entry point
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## What Goes Where
|
|
44
|
+
|
|
45
|
+
| Layer | commonMain | androidMain / iosMain |
|
|
46
|
+
| -------------------------- | ---------- | --------------------- |
|
|
47
|
+
| Domain models | ✅ | ❌ |
|
|
48
|
+
| Use cases | ✅ | ❌ |
|
|
49
|
+
| Repository interfaces | ✅ | ❌ |
|
|
50
|
+
| Repository implementations | ❌ | ✅ |
|
|
51
|
+
| Network (Ktor) | ✅ | ❌ |
|
|
52
|
+
| Database (SQLDelight) | ✅ schema | ✅ driver |
|
|
53
|
+
| UI | ❌ | ✅ |
|
|
54
|
+
| Platform APIs | ❌ | ✅ |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## expect / actual Pattern
|
|
59
|
+
|
|
60
|
+
### Prefer interface + DI over expect/actual
|
|
61
|
+
|
|
62
|
+
```kotlin
|
|
63
|
+
// ✅ Preferred — interface in commonMain, implementation injected
|
|
64
|
+
interface PlatformLogger {
|
|
65
|
+
fun log(message: String)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// androidMain
|
|
69
|
+
class AndroidLogger : PlatformLogger {
|
|
70
|
+
override fun log(message: String) = Log.d("App", message)
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Use expect/actual only for simple platform utilities
|
|
75
|
+
|
|
76
|
+
```kotlin
|
|
77
|
+
// commonMain
|
|
78
|
+
expect fun getCurrentTimeMillis(): Long
|
|
79
|
+
|
|
80
|
+
// androidMain
|
|
81
|
+
actual fun getCurrentTimeMillis(): Long = System.currentTimeMillis()
|
|
82
|
+
|
|
83
|
+
// iosMain
|
|
84
|
+
actual fun getCurrentTimeMillis(): Long =
|
|
85
|
+
NSDate().timeIntervalSince1970.toLong() * 1000
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Networking — Ktor (shared)
|
|
91
|
+
|
|
92
|
+
```kotlin
|
|
93
|
+
// commonMain — use Ktor, not Retrofit (Retrofit is Android-only)
|
|
94
|
+
val client = HttpClient {
|
|
95
|
+
install(ContentNegotiation) {
|
|
96
|
+
json(Json { ignoreUnknownKeys = true })
|
|
97
|
+
}
|
|
98
|
+
install(HttpTimeout) {
|
|
99
|
+
requestTimeoutMillis = 30_000
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Database — SQLDelight (shared schema)
|
|
105
|
+
|
|
106
|
+
```kotlin
|
|
107
|
+
// commonMain — define schema in .sq files
|
|
108
|
+
// androidMain — provide AndroidSqliteDriver
|
|
109
|
+
// iosMain — provide NativeSqliteDriver
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Coroutines in KMP
|
|
115
|
+
|
|
116
|
+
```kotlin
|
|
117
|
+
// ✅ Use kotlinx.coroutines in commonMain
|
|
118
|
+
// ✅ Use Dispatchers.Default for CPU work in common code
|
|
119
|
+
// ✅ Use platform dispatcher injection for UI thread
|
|
120
|
+
|
|
121
|
+
// androidMain
|
|
122
|
+
val uiDispatcher: CoroutineDispatcher = Dispatchers.Main
|
|
123
|
+
|
|
124
|
+
// iosMain
|
|
125
|
+
val uiDispatcher: CoroutineDispatcher = Dispatchers.Main
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Gradle Setup (libs.versions.toml)
|
|
131
|
+
|
|
132
|
+
```toml
|
|
133
|
+
[versions]
|
|
134
|
+
kotlin = "2.0.0"
|
|
135
|
+
kmp = "2.0.0"
|
|
136
|
+
|
|
137
|
+
[plugins]
|
|
138
|
+
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```kotlin
|
|
142
|
+
// shared/build.gradle.kts
|
|
143
|
+
kotlin {
|
|
144
|
+
androidTarget()
|
|
145
|
+
iosX64()
|
|
146
|
+
iosArm64()
|
|
147
|
+
iosSimulatorArm64()
|
|
148
|
+
|
|
149
|
+
sourceSets {
|
|
150
|
+
commonMain.dependencies {
|
|
151
|
+
implementation(libs.ktor.client.core)
|
|
152
|
+
implementation(libs.kotlinx.coroutines.core)
|
|
153
|
+
implementation(libs.kotlinx.serialization.json)
|
|
154
|
+
}
|
|
155
|
+
androidMain.dependencies {
|
|
156
|
+
implementation(libs.ktor.client.okhttp)
|
|
157
|
+
}
|
|
158
|
+
iosMain.dependencies {
|
|
159
|
+
implementation(libs.ktor.client.darwin)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Anti-Patterns
|
|
168
|
+
|
|
169
|
+
- Importing `android.*` in `commonMain` — breaks iOS compilation
|
|
170
|
+
- Using Retrofit in shared code — it's Android-only; use Ktor
|
|
171
|
+
- Putting UI logic in `commonMain` — UI must stay platform-specific
|
|
172
|
+
- Overusing `expect/actual` — prefer interface + DI for testability
|
|
173
|
+
- Sharing ViewModels in commonMain — keep ViewModels platform-specific
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Related Skills
|
|
178
|
+
|
|
179
|
+
- `kotlin` — Kotlin language conventions
|
|
180
|
+
- `coroutine` — coroutine patterns in shared and platform code
|
|
181
|
+
- `serialization` — Kotlinx Serialization setup for KMP
|
|
182
|
+
- `ktor` — Ktor client setup and usage
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kotlin
|
|
3
|
+
description: >
|
|
4
|
+
Kotlin language conventions and best practices for Android development.
|
|
5
|
+
Load this skill whenever writing, reviewing, or refactoring any Kotlin code.
|
|
6
|
+
Covers idioms, language features, and patterns that should be consistently
|
|
7
|
+
applied across the entire codebase.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Kotlin
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Kotlin is the primary language for Android development. This skill defines which language features to use, when to use them, and which patterns to avoid.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
- Prefer **idiomatic Kotlin** over Java-style code
|
|
21
|
+
- Prefer **immutability** by default (`val` over `var`, `data class`, `copy()`)
|
|
22
|
+
- Prefer **null safety** at compile time — minimize nullable types
|
|
23
|
+
- Prefer **expression over statement** where it improves readability
|
|
24
|
+
- Never use `!!` — always handle nullability explicitly
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Language Features
|
|
29
|
+
|
|
30
|
+
### Null Safety
|
|
31
|
+
|
|
32
|
+
```kotlin
|
|
33
|
+
// ✅ Use safe call + elvis
|
|
34
|
+
val name = user?.name ?: "Unknown"
|
|
35
|
+
|
|
36
|
+
// ✅ Use let for nullable execution
|
|
37
|
+
user?.let { sendEmail(it) }
|
|
38
|
+
|
|
39
|
+
// ❌ Never force-unwrap
|
|
40
|
+
val name = user!!.name
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Data Classes
|
|
44
|
+
|
|
45
|
+
```kotlin
|
|
46
|
+
// ✅ Use data class for value holders
|
|
47
|
+
data class User(val id: String, val name: String)
|
|
48
|
+
|
|
49
|
+
// ✅ Use copy() for mutation
|
|
50
|
+
val updated = user.copy(name = "Ali")
|
|
51
|
+
|
|
52
|
+
// ❌ Never mutate fields directly
|
|
53
|
+
user.name = "Ali"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Sealed Classes
|
|
57
|
+
|
|
58
|
+
```kotlin
|
|
59
|
+
// ✅ Use sealed class for exhaustive state modeling
|
|
60
|
+
sealed class Result<out T> {
|
|
61
|
+
data class Success<T>(val data: T) : Result<T>()
|
|
62
|
+
data class Error(val exception: Throwable) : Result<Nothing>()
|
|
63
|
+
object Loading : Result<Nothing>()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ✅ Always use when exhaustively — no else branch
|
|
67
|
+
when (result) {
|
|
68
|
+
is Result.Success -> showData(result.data)
|
|
69
|
+
is Result.Error -> showError(result.exception)
|
|
70
|
+
is Result.Loading -> showLoading()
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Extension Functions
|
|
75
|
+
|
|
76
|
+
```kotlin
|
|
77
|
+
// ✅ Use for adding behavior to existing types without inheritance
|
|
78
|
+
fun String.toFormattedDate(): String { ... }
|
|
79
|
+
|
|
80
|
+
// ✅ Use for scoping utility functions to a receiver
|
|
81
|
+
fun Context.showToast(message: String) {
|
|
82
|
+
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ❌ Don't use extension functions to bypass encapsulation
|
|
86
|
+
// ❌ Don't define extensions on types you own — add the method directly
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Scope Functions
|
|
90
|
+
|
|
91
|
+
```kotlin
|
|
92
|
+
// ✅ let — nullable transformation or scoped execution
|
|
93
|
+
val length = text?.let { it.trim().length }
|
|
94
|
+
|
|
95
|
+
// ✅ apply — object configuration / builder pattern
|
|
96
|
+
val intent = Intent().apply {
|
|
97
|
+
putExtra("key", value)
|
|
98
|
+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ✅ run — transformation on receiver
|
|
102
|
+
val result = user.run { "$name ($email)" }
|
|
103
|
+
|
|
104
|
+
// ✅ also — side effects (logging, debugging)
|
|
105
|
+
val user = createUser().also { log("User created: $it") }
|
|
106
|
+
|
|
107
|
+
// ❌ Don't nest scope functions more than 2 levels deep
|
|
108
|
+
// ❌ Don't use scope functions just for style — only when they add clarity
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### When Expression
|
|
112
|
+
|
|
113
|
+
```kotlin
|
|
114
|
+
// ✅ Use when as an expression
|
|
115
|
+
val label = when (status) {
|
|
116
|
+
Status.ACTIVE -> "Active"
|
|
117
|
+
Status.INACTIVE -> "Inactive"
|
|
118
|
+
Status.PENDING -> "Pending"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ✅ Always exhaustive — avoid else on sealed/enum
|
|
122
|
+
// ❌ Don't use else on sealed class when — it hides new state cases
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Inline Classes / Value Classes
|
|
126
|
+
|
|
127
|
+
```kotlin
|
|
128
|
+
// ✅ Use to add type safety to primitives
|
|
129
|
+
@JvmInline
|
|
130
|
+
value class UserId(val value: String)
|
|
131
|
+
|
|
132
|
+
@JvmInline
|
|
133
|
+
value class Email(val value: String)
|
|
134
|
+
|
|
135
|
+
// Prevents mixing up primitive parameters
|
|
136
|
+
fun findUser(id: UserId, email: Email) { ... }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Collections
|
|
140
|
+
|
|
141
|
+
```kotlin
|
|
142
|
+
// ✅ Use immutable collections by default
|
|
143
|
+
val items: List<String> = listOf("a", "b", "c")
|
|
144
|
+
val map: Map<String, Int> = mapOf("a" to 1)
|
|
145
|
+
|
|
146
|
+
// ✅ Use mutableListOf() only when mutation is required
|
|
147
|
+
val buffer = mutableListOf<String>()
|
|
148
|
+
|
|
149
|
+
// ✅ Prefer functional operators
|
|
150
|
+
val names = users.filter { it.isActive }.map { it.name }
|
|
151
|
+
|
|
152
|
+
// ❌ Don't use Java stream API — use Kotlin collections API
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Naming Conventions
|
|
158
|
+
|
|
159
|
+
| Element | Convention | Example |
|
|
160
|
+
| -------- | -------------------------- | -------------------------- |
|
|
161
|
+
| Class | PascalCase | `UserRepository` |
|
|
162
|
+
| Function | camelCase | `getUserById()` |
|
|
163
|
+
| Property | camelCase | `userName` |
|
|
164
|
+
| Constant | SCREAMING_SNAKE | `MAX_RETRY_COUNT` |
|
|
165
|
+
| Package | lowercase | `com.example.feature.auth` |
|
|
166
|
+
| File | PascalCase (matches class) | `UserRepository.kt` |
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Anti-Patterns
|
|
171
|
+
|
|
172
|
+
- `!!` — use safe calls and elvis instead
|
|
173
|
+
- `lateinit var` on types that could be `val` — redesign initialization
|
|
174
|
+
- `object` for classes that hold state — use class with DI instead
|
|
175
|
+
- Returning `null` to signal error — use `Result<T>` or sealed class
|
|
176
|
+
- Java-style getters/setters — use Kotlin properties
|
|
177
|
+
- `companion object` holding mutable state — use DI or top-level properties
|
|
178
|
+
- Catching `Exception` broadly — catch specific exception types
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Related Skills
|
|
183
|
+
|
|
184
|
+
- `coroutine` — async/concurrency patterns
|
|
185
|
+
- `extension-functions-design` — detailed extension function guidelines
|
|
186
|
+
- `immutability` — immutability policy across layers
|
|
187
|
+
- `serialization` — Kotlin serialization setup and usage
|