code-abyss 1.6.16 → 1.7.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/package.json +2 -2
- package/skills/SKILL.md +24 -16
- package/skills/domains/ai/SKILL.md +2 -2
- package/skills/domains/ai/prompt-and-eval.md +279 -0
- package/skills/domains/architecture/SKILL.md +2 -3
- package/skills/domains/architecture/security-arch.md +87 -0
- package/skills/domains/data-engineering/SKILL.md +188 -26
- package/skills/domains/development/SKILL.md +1 -4
- package/skills/domains/devops/SKILL.md +3 -5
- package/skills/domains/devops/performance.md +63 -0
- package/skills/domains/devops/testing.md +97 -0
- package/skills/domains/frontend-design/SKILL.md +12 -3
- package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
- package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
- package/skills/domains/frontend-design/engineering.md +287 -0
- package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
- package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
- package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
- package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
- package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
- package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
- package/skills/domains/infrastructure/SKILL.md +174 -34
- package/skills/domains/mobile/SKILL.md +211 -21
- package/skills/domains/orchestration/SKILL.md +1 -0
- package/skills/domains/security/SKILL.md +4 -6
- package/skills/domains/security/blue-team.md +57 -0
- package/skills/domains/security/red-team.md +54 -0
- package/skills/domains/security/threat-intel.md +50 -0
- package/skills/orchestration/multi-agent/SKILL.md +195 -46
- package/skills/run_skill.js +134 -0
- package/skills/tools/gen-docs/SKILL.md +6 -4
- package/skills/tools/gen-docs/scripts/doc_generator.js +349 -0
- package/skills/tools/verify-change/SKILL.md +8 -6
- package/skills/tools/verify-change/scripts/change_analyzer.js +270 -0
- package/skills/tools/verify-module/SKILL.md +6 -4
- package/skills/tools/verify-module/scripts/module_scanner.js +145 -0
- package/skills/tools/verify-quality/SKILL.md +5 -3
- package/skills/tools/verify-quality/scripts/quality_checker.js +276 -0
- package/skills/tools/verify-security/SKILL.md +7 -5
- package/skills/tools/verify-security/scripts/security_scanner.js +133 -0
- package/skills/__pycache__/run_skill.cpython-312.pyc +0 -0
- package/skills/domains/COVERAGE_PLAN.md +0 -232
- package/skills/domains/ai/model-evaluation.md +0 -790
- package/skills/domains/ai/prompt-engineering.md +0 -703
- package/skills/domains/architecture/compliance.md +0 -299
- package/skills/domains/architecture/data-security.md +0 -184
- package/skills/domains/data-engineering/data-pipeline.md +0 -762
- package/skills/domains/data-engineering/data-quality.md +0 -894
- package/skills/domains/data-engineering/stream-processing.md +0 -791
- package/skills/domains/development/dart.md +0 -963
- package/skills/domains/development/kotlin.md +0 -834
- package/skills/domains/development/php.md +0 -659
- package/skills/domains/development/swift.md +0 -755
- package/skills/domains/devops/e2e-testing.md +0 -914
- package/skills/domains/devops/performance-testing.md +0 -734
- package/skills/domains/devops/testing-strategy.md +0 -667
- package/skills/domains/frontend-design/build-tools.md +0 -743
- package/skills/domains/frontend-design/performance.md +0 -734
- package/skills/domains/frontend-design/testing.md +0 -699
- package/skills/domains/infrastructure/gitops.md +0 -735
- package/skills/domains/infrastructure/iac.md +0 -855
- package/skills/domains/infrastructure/kubernetes.md +0 -1018
- package/skills/domains/mobile/android-dev.md +0 -979
- package/skills/domains/mobile/cross-platform.md +0 -795
- package/skills/domains/mobile/ios-dev.md +0 -931
- package/skills/domains/security/secrets-management.md +0 -834
- package/skills/domains/security/supply-chain.md +0 -931
- package/skills/domains/security/threat-modeling.md +0 -828
- package/skills/run_skill.py +0 -153
- package/skills/tests/README.md +0 -225
- package/skills/tests/SUMMARY.md +0 -362
- package/skills/tests/__init__.py +0 -3
- package/skills/tests/__pycache__/test_change_analyzer.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_doc_generator.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_module_scanner.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_quality_checker.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_security_scanner.cpython-312.pyc +0 -0
- package/skills/tests/test_change_analyzer.py +0 -558
- package/skills/tests/test_doc_generator.py +0 -538
- package/skills/tests/test_module_scanner.py +0 -376
- package/skills/tests/test_quality_checker.py +0 -516
- package/skills/tests/test_security_scanner.py +0 -426
- package/skills/tools/gen-docs/scripts/__pycache__/doc_generator.cpython-312.pyc +0 -0
- package/skills/tools/gen-docs/scripts/doc_generator.py +0 -520
- package/skills/tools/verify-change/scripts/__pycache__/change_analyzer.cpython-312.pyc +0 -0
- package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
- package/skills/tools/verify-module/scripts/__pycache__/module_scanner.cpython-312.pyc +0 -0
- package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
- package/skills/tools/verify-quality/scripts/__pycache__/quality_checker.cpython-312.pyc +0 -0
- package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
- package/skills/tools/verify-security/scripts/__pycache__/security_scanner.cpython-312.pyc +0 -0
- package/skills/tools/verify-security/scripts/security_scanner.py +0 -374
|
@@ -1,834 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: kotlin
|
|
3
|
-
description: Kotlin 开发技术。Jetpack Compose、Coroutines、Flow、Android 开发、协程并发。当用户提到 Kotlin、Jetpack Compose、Coroutines、Flow、Android 开发时使用。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# 🤖 Kotlin 开发 · Kotlin Development
|
|
7
|
-
|
|
8
|
-
## 生态架构
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
Kotlin Coroutines
|
|
12
|
-
│
|
|
13
|
-
┌─────────┼─────────┐
|
|
14
|
-
│ │ │
|
|
15
|
-
Flow Channel StateFlow
|
|
16
|
-
│ │ │
|
|
17
|
-
└─────────┼─────────┘
|
|
18
|
-
│
|
|
19
|
-
Jetpack Compose
|
|
20
|
-
│
|
|
21
|
-
┌─────────┼─────────┐
|
|
22
|
-
ViewModel Room Retrofit
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Kotlin 语言特性
|
|
26
|
-
|
|
27
|
-
### 空安全
|
|
28
|
-
```kotlin
|
|
29
|
-
// 可空类型
|
|
30
|
-
var name: String? = null
|
|
31
|
-
val length = name?.length ?: 0 // Elvis 操作符
|
|
32
|
-
|
|
33
|
-
// 安全调用链
|
|
34
|
-
val city = user?.address?.city
|
|
35
|
-
|
|
36
|
-
// 非空断言 (谨慎使用)
|
|
37
|
-
val length = name!!.length
|
|
38
|
-
|
|
39
|
-
// let 作用域函数
|
|
40
|
-
name?.let {
|
|
41
|
-
println("Name is $it")
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// 智能转换
|
|
45
|
-
fun process(value: Any) {
|
|
46
|
-
if (value is String) {
|
|
47
|
-
println(value.length) // 自动转换为 String
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### 数据类与密封类
|
|
53
|
-
```kotlin
|
|
54
|
-
// 数据类
|
|
55
|
-
data class User(
|
|
56
|
-
val id: String,
|
|
57
|
-
val name: String,
|
|
58
|
-
val email: String
|
|
59
|
-
) {
|
|
60
|
-
fun isAdmin() = email.endsWith("@admin.com")
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
val user = User("1", "John", "john@example.com")
|
|
64
|
-
val updated = user.copy(name = "Jane")
|
|
65
|
-
|
|
66
|
-
// 密封类 (类型安全的枚举)
|
|
67
|
-
sealed class Result<out T> {
|
|
68
|
-
data class Success<T>(val data: T) : Result<T>()
|
|
69
|
-
data class Error(val message: String) : Result<Nothing>()
|
|
70
|
-
object Loading : Result<Nothing>()
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
fun handleResult(result: Result<User>) {
|
|
74
|
-
when (result) {
|
|
75
|
-
is Result.Success -> println(result.data)
|
|
76
|
-
is Result.Error -> println(result.message)
|
|
77
|
-
Result.Loading -> println("Loading...")
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### 扩展函数
|
|
83
|
-
```kotlin
|
|
84
|
-
// 为现有类添加方法
|
|
85
|
-
fun String.isEmail(): Boolean {
|
|
86
|
-
return this.contains("@") && this.contains(".")
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
fun List<Int>.average(): Double {
|
|
90
|
-
return if (isEmpty()) 0.0 else sum().toDouble() / size
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 使用
|
|
94
|
-
val email = "test@example.com"
|
|
95
|
-
if (email.isEmail()) {
|
|
96
|
-
println("Valid email")
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
val numbers = listOf(1, 2, 3, 4, 5)
|
|
100
|
-
println(numbers.average()) // 3.0
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### 高阶函数与 Lambda
|
|
104
|
-
```kotlin
|
|
105
|
-
// 高阶函数
|
|
106
|
-
fun <T> List<T>.customFilter(predicate: (T) -> Boolean): List<T> {
|
|
107
|
-
val result = mutableListOf<T>()
|
|
108
|
-
for (item in this) {
|
|
109
|
-
if (predicate(item)) {
|
|
110
|
-
result.add(item)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return result
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// 使用
|
|
117
|
-
val numbers = listOf(1, 2, 3, 4, 5)
|
|
118
|
-
val evens = numbers.customFilter { it % 2 == 0 }
|
|
119
|
-
|
|
120
|
-
// 函数类型参数
|
|
121
|
-
fun performOperation(x: Int, y: Int, operation: (Int, Int) -> Int): Int {
|
|
122
|
-
return operation(x, y)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
val sum = performOperation(5, 3) { a, b -> a + b }
|
|
126
|
-
val product = performOperation(5, 3) { a, b -> a * b }
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
## Jetpack Compose
|
|
130
|
-
|
|
131
|
-
### 组合函数
|
|
132
|
-
```kotlin
|
|
133
|
-
import androidx.compose.runtime.*
|
|
134
|
-
import androidx.compose.material3.*
|
|
135
|
-
import androidx.compose.foundation.layout.*
|
|
136
|
-
|
|
137
|
-
@Composable
|
|
138
|
-
fun UserProfile(user: User) {
|
|
139
|
-
var isExpanded by remember { mutableStateOf(false) }
|
|
140
|
-
|
|
141
|
-
Card(
|
|
142
|
-
modifier = Modifier
|
|
143
|
-
.fillMaxWidth()
|
|
144
|
-
.padding(16.dp)
|
|
145
|
-
.clickable { isExpanded = !isExpanded }
|
|
146
|
-
) {
|
|
147
|
-
Column(modifier = Modifier.padding(16.dp)) {
|
|
148
|
-
Text(
|
|
149
|
-
text = user.name,
|
|
150
|
-
style = MaterialTheme.typography.headlineMedium
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
Text(
|
|
154
|
-
text = user.email,
|
|
155
|
-
style = MaterialTheme.typography.bodyMedium,
|
|
156
|
-
color = MaterialTheme.colorScheme.secondary
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
AnimatedVisibility(visible = isExpanded) {
|
|
160
|
-
Text(
|
|
161
|
-
text = user.bio,
|
|
162
|
-
modifier = Modifier.padding(top = 8.dp)
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
### 状态管理
|
|
171
|
-
```kotlin
|
|
172
|
-
// remember - 组合内状态
|
|
173
|
-
@Composable
|
|
174
|
-
fun Counter() {
|
|
175
|
-
var count by remember { mutableStateOf(0) }
|
|
176
|
-
|
|
177
|
-
Button(onClick = { count++ }) {
|
|
178
|
-
Text("Count: $count")
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// rememberSaveable - 配置变更后保留
|
|
183
|
-
@Composable
|
|
184
|
-
fun SaveableCounter() {
|
|
185
|
-
var count by rememberSaveable { mutableStateOf(0) }
|
|
186
|
-
|
|
187
|
-
Button(onClick = { count++ }) {
|
|
188
|
-
Text("Count: $count")
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// ViewModel 状态
|
|
193
|
-
class UserViewModel : ViewModel() {
|
|
194
|
-
private val _users = MutableStateFlow<List<User>>(emptyList())
|
|
195
|
-
val users: StateFlow<List<User>> = _users.asStateFlow()
|
|
196
|
-
|
|
197
|
-
fun loadUsers() {
|
|
198
|
-
viewModelScope.launch {
|
|
199
|
-
_users.value = repository.getUsers()
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
@Composable
|
|
205
|
-
fun UserList(viewModel: UserViewModel = viewModel()) {
|
|
206
|
-
val users by viewModel.users.collectAsState()
|
|
207
|
-
|
|
208
|
-
LaunchedEffect(Unit) {
|
|
209
|
-
viewModel.loadUsers()
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
LazyColumn {
|
|
213
|
-
items(users) { user ->
|
|
214
|
-
UserItem(user)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### 列表与导航
|
|
221
|
-
```kotlin
|
|
222
|
-
@Composable
|
|
223
|
-
fun ItemList(items: List<Item>, onItemClick: (Item) -> Unit) {
|
|
224
|
-
LazyColumn {
|
|
225
|
-
items(items, key = { it.id }) { item ->
|
|
226
|
-
ItemRow(
|
|
227
|
-
item = item,
|
|
228
|
-
onClick = { onItemClick(item) }
|
|
229
|
-
)
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
@Composable
|
|
235
|
-
fun ItemRow(item: Item, onClick: () -> Unit) {
|
|
236
|
-
Row(
|
|
237
|
-
modifier = Modifier
|
|
238
|
-
.fillMaxWidth()
|
|
239
|
-
.clickable(onClick = onClick)
|
|
240
|
-
.padding(16.dp),
|
|
241
|
-
horizontalArrangement = Arrangement.SpaceBetween
|
|
242
|
-
) {
|
|
243
|
-
Column {
|
|
244
|
-
Text(
|
|
245
|
-
text = item.name,
|
|
246
|
-
style = MaterialTheme.typography.titleMedium
|
|
247
|
-
)
|
|
248
|
-
Text(
|
|
249
|
-
text = item.description,
|
|
250
|
-
style = MaterialTheme.typography.bodySmall
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
Icon(
|
|
255
|
-
imageVector = Icons.Default.ChevronRight,
|
|
256
|
-
contentDescription = null
|
|
257
|
-
)
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Navigation
|
|
262
|
-
@Composable
|
|
263
|
-
fun AppNavigation() {
|
|
264
|
-
val navController = rememberNavController()
|
|
265
|
-
|
|
266
|
-
NavHost(navController, startDestination = "home") {
|
|
267
|
-
composable("home") {
|
|
268
|
-
HomeScreen(
|
|
269
|
-
onNavigateToDetail = { id ->
|
|
270
|
-
navController.navigate("detail/$id")
|
|
271
|
-
}
|
|
272
|
-
)
|
|
273
|
-
}
|
|
274
|
-
composable(
|
|
275
|
-
route = "detail/{id}",
|
|
276
|
-
arguments = listOf(navArgument("id") { type = NavType.StringType })
|
|
277
|
-
) { backStackEntry ->
|
|
278
|
-
val id = backStackEntry.arguments?.getString("id")
|
|
279
|
-
DetailScreen(id = id)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
### 副作用处理
|
|
286
|
-
```kotlin
|
|
287
|
-
@Composable
|
|
288
|
-
fun EffectsExample() {
|
|
289
|
-
// LaunchedEffect - 启动协程
|
|
290
|
-
LaunchedEffect(key1 = Unit) {
|
|
291
|
-
// 组合进入时执行一次
|
|
292
|
-
loadData()
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// DisposableEffect - 清理资源
|
|
296
|
-
DisposableEffect(Unit) {
|
|
297
|
-
val listener = setupListener()
|
|
298
|
-
onDispose {
|
|
299
|
-
listener.remove()
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// SideEffect - 发布状态到非 Compose 代码
|
|
304
|
-
SideEffect {
|
|
305
|
-
analytics.trackScreenView("Home")
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// derivedStateOf - 派生状态
|
|
309
|
-
val items = remember { mutableStateListOf<Item>() }
|
|
310
|
-
val hasItems by remember {
|
|
311
|
-
derivedStateOf { items.isNotEmpty() }
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
## Kotlin Coroutines
|
|
317
|
-
|
|
318
|
-
### 基础协程
|
|
319
|
-
```kotlin
|
|
320
|
-
import kotlinx.coroutines.*
|
|
321
|
-
|
|
322
|
-
// 启动协程
|
|
323
|
-
fun main() = runBlocking {
|
|
324
|
-
launch {
|
|
325
|
-
delay(1000L)
|
|
326
|
-
println("World!")
|
|
327
|
-
}
|
|
328
|
-
println("Hello,")
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// async/await 并发
|
|
332
|
-
suspend fun fetchUserData(userId: String): UserData = coroutineScope {
|
|
333
|
-
val userDeferred = async { fetchUser(userId) }
|
|
334
|
-
val postsDeferred = async { fetchPosts(userId) }
|
|
335
|
-
val friendsDeferred = async { fetchFriends(userId) }
|
|
336
|
-
|
|
337
|
-
UserData(
|
|
338
|
-
user = userDeferred.await(),
|
|
339
|
-
posts = postsDeferred.await(),
|
|
340
|
-
friends = friendsDeferred.await()
|
|
341
|
-
)
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// 超时控制
|
|
345
|
-
suspend fun fetchWithTimeout() {
|
|
346
|
-
try {
|
|
347
|
-
withTimeout(5000L) {
|
|
348
|
-
fetchData()
|
|
349
|
-
}
|
|
350
|
-
} catch (e: TimeoutCancellationException) {
|
|
351
|
-
println("Request timed out")
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### 协程作用域
|
|
357
|
-
```kotlin
|
|
358
|
-
class MyViewModel : ViewModel() {
|
|
359
|
-
// ViewModel 作用域
|
|
360
|
-
fun loadData() {
|
|
361
|
-
viewModelScope.launch {
|
|
362
|
-
try {
|
|
363
|
-
val data = repository.fetchData()
|
|
364
|
-
_state.value = State.Success(data)
|
|
365
|
-
} catch (e: Exception) {
|
|
366
|
-
_state.value = State.Error(e.message)
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
class MyActivity : AppCompatActivity() {
|
|
373
|
-
// 生命周期作用域
|
|
374
|
-
override fun onCreate(savedInstanceState: Bundle?) {
|
|
375
|
-
super.onCreate(savedInstanceState)
|
|
376
|
-
|
|
377
|
-
lifecycleScope.launch {
|
|
378
|
-
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
379
|
-
viewModel.uiState.collect { state ->
|
|
380
|
-
updateUI(state)
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// 自定义作用域
|
|
388
|
-
class DataRepository {
|
|
389
|
-
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
390
|
-
|
|
391
|
-
fun fetchData() {
|
|
392
|
-
scope.launch {
|
|
393
|
-
// 后台任务
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
fun cleanup() {
|
|
398
|
-
scope.cancel()
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
### 调度器
|
|
404
|
-
```kotlin
|
|
405
|
-
// Dispatchers.Main - UI 线程
|
|
406
|
-
lifecycleScope.launch(Dispatchers.Main) {
|
|
407
|
-
updateUI()
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// Dispatchers.IO - IO 操作
|
|
411
|
-
withContext(Dispatchers.IO) {
|
|
412
|
-
val data = database.query()
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// Dispatchers.Default - CPU 密集型
|
|
416
|
-
withContext(Dispatchers.Default) {
|
|
417
|
-
val result = complexCalculation()
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// 切换调度器
|
|
421
|
-
suspend fun loadData() {
|
|
422
|
-
val data = withContext(Dispatchers.IO) {
|
|
423
|
-
fetchFromNetwork()
|
|
424
|
-
}
|
|
425
|
-
withContext(Dispatchers.Main) {
|
|
426
|
-
displayData(data)
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
## Flow 流式编程
|
|
432
|
-
|
|
433
|
-
### Flow 基础
|
|
434
|
-
```kotlin
|
|
435
|
-
import kotlinx.coroutines.flow.*
|
|
436
|
-
|
|
437
|
-
// 创建 Flow
|
|
438
|
-
fun simpleFlow(): Flow<Int> = flow {
|
|
439
|
-
for (i in 1..3) {
|
|
440
|
-
delay(100)
|
|
441
|
-
emit(i)
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// 收集 Flow
|
|
446
|
-
suspend fun collectFlow() {
|
|
447
|
-
simpleFlow().collect { value ->
|
|
448
|
-
println(value)
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Flow 操作符
|
|
453
|
-
fun transformFlow(): Flow<String> = flow {
|
|
454
|
-
emit(1)
|
|
455
|
-
emit(2)
|
|
456
|
-
emit(3)
|
|
457
|
-
}.map { value ->
|
|
458
|
-
"Number: $value"
|
|
459
|
-
}.filter { text ->
|
|
460
|
-
text.contains("2")
|
|
461
|
-
}
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
### StateFlow 与 SharedFlow
|
|
465
|
-
```kotlin
|
|
466
|
-
class UserRepository {
|
|
467
|
-
// StateFlow - 状态流 (有初始值)
|
|
468
|
-
private val _users = MutableStateFlow<List<User>>(emptyList())
|
|
469
|
-
val users: StateFlow<List<User>> = _users.asStateFlow()
|
|
470
|
-
|
|
471
|
-
// SharedFlow - 事件流 (无初始值)
|
|
472
|
-
private val _events = MutableSharedFlow<Event>()
|
|
473
|
-
val events: SharedFlow<Event> = _events.asSharedFlow()
|
|
474
|
-
|
|
475
|
-
suspend fun loadUsers() {
|
|
476
|
-
val result = api.getUsers()
|
|
477
|
-
_users.value = result
|
|
478
|
-
_events.emit(Event.UsersLoaded)
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// 在 ViewModel 中使用
|
|
483
|
-
class UserViewModel(private val repository: UserRepository) : ViewModel() {
|
|
484
|
-
val users = repository.users
|
|
485
|
-
.stateIn(
|
|
486
|
-
scope = viewModelScope,
|
|
487
|
-
started = SharingStarted.WhileSubscribed(5000),
|
|
488
|
-
initialValue = emptyList()
|
|
489
|
-
)
|
|
490
|
-
|
|
491
|
-
init {
|
|
492
|
-
viewModelScope.launch {
|
|
493
|
-
repository.events.collect { event ->
|
|
494
|
-
handleEvent(event)
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
### Flow 操作符链
|
|
502
|
-
```kotlin
|
|
503
|
-
class SearchViewModel : ViewModel() {
|
|
504
|
-
private val _searchQuery = MutableStateFlow("")
|
|
505
|
-
val searchQuery: StateFlow<String> = _searchQuery
|
|
506
|
-
|
|
507
|
-
val searchResults: StateFlow<List<Result>> = searchQuery
|
|
508
|
-
.debounce(300)
|
|
509
|
-
.filter { it.length >= 3 }
|
|
510
|
-
.distinctUntilChanged()
|
|
511
|
-
.flatMapLatest { query ->
|
|
512
|
-
repository.search(query)
|
|
513
|
-
.catch { emit(emptyList()) }
|
|
514
|
-
}
|
|
515
|
-
.stateIn(
|
|
516
|
-
scope = viewModelScope,
|
|
517
|
-
started = SharingStarted.WhileSubscribed(5000),
|
|
518
|
-
initialValue = emptyList()
|
|
519
|
-
)
|
|
520
|
-
|
|
521
|
-
fun onSearchQueryChanged(query: String) {
|
|
522
|
-
_searchQuery.value = query
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
## Android 架构
|
|
528
|
-
|
|
529
|
-
### MVVM 模式
|
|
530
|
-
```kotlin
|
|
531
|
-
// Model
|
|
532
|
-
data class User(
|
|
533
|
-
val id: String,
|
|
534
|
-
val name: String,
|
|
535
|
-
val email: String
|
|
536
|
-
)
|
|
537
|
-
|
|
538
|
-
// Repository
|
|
539
|
-
class UserRepository(
|
|
540
|
-
private val api: ApiService,
|
|
541
|
-
private val dao: UserDao
|
|
542
|
-
) {
|
|
543
|
-
fun getUsers(): Flow<List<User>> = flow {
|
|
544
|
-
// 先发射缓存数据
|
|
545
|
-
emit(dao.getAll())
|
|
546
|
-
|
|
547
|
-
// 然后获取网络数据
|
|
548
|
-
val users = api.getUsers()
|
|
549
|
-
dao.insertAll(users)
|
|
550
|
-
emit(users)
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// ViewModel
|
|
555
|
-
class UserViewModel(
|
|
556
|
-
private val repository: UserRepository
|
|
557
|
-
) : ViewModel() {
|
|
558
|
-
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
|
|
559
|
-
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
|
|
560
|
-
|
|
561
|
-
init {
|
|
562
|
-
loadUsers()
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
private fun loadUsers() {
|
|
566
|
-
viewModelScope.launch {
|
|
567
|
-
repository.getUsers()
|
|
568
|
-
.catch { e ->
|
|
569
|
-
_uiState.value = UiState.Error(e.message ?: "Unknown error")
|
|
570
|
-
}
|
|
571
|
-
.collect { users ->
|
|
572
|
-
_uiState.value = UiState.Success(users)
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
sealed class UiState {
|
|
579
|
-
object Loading : UiState()
|
|
580
|
-
data class Success(val users: List<User>) : UiState()
|
|
581
|
-
data class Error(val message: String) : UiState()
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// View (Compose)
|
|
585
|
-
@Composable
|
|
586
|
-
fun UserScreen(viewModel: UserViewModel = viewModel()) {
|
|
587
|
-
val uiState by viewModel.uiState.collectAsState()
|
|
588
|
-
|
|
589
|
-
when (val state = uiState) {
|
|
590
|
-
is UiState.Loading -> LoadingIndicator()
|
|
591
|
-
is UiState.Success -> UserList(state.users)
|
|
592
|
-
is UiState.Error -> ErrorMessage(state.message)
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
## Room 数据库
|
|
598
|
-
|
|
599
|
-
### 实体与 DAO
|
|
600
|
-
```kotlin
|
|
601
|
-
// Entity
|
|
602
|
-
@Entity(tableName = "users")
|
|
603
|
-
data class UserEntity(
|
|
604
|
-
@PrimaryKey val id: String,
|
|
605
|
-
@ColumnInfo(name = "name") val name: String,
|
|
606
|
-
@ColumnInfo(name = "email") val email: String,
|
|
607
|
-
@ColumnInfo(name = "created_at") val createdAt: Long
|
|
608
|
-
)
|
|
609
|
-
|
|
610
|
-
// DAO
|
|
611
|
-
@Dao
|
|
612
|
-
interface UserDao {
|
|
613
|
-
@Query("SELECT * FROM users")
|
|
614
|
-
fun getAll(): Flow<List<UserEntity>>
|
|
615
|
-
|
|
616
|
-
@Query("SELECT * FROM users WHERE id = :userId")
|
|
617
|
-
suspend fun getById(userId: String): UserEntity?
|
|
618
|
-
|
|
619
|
-
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
620
|
-
suspend fun insert(user: UserEntity)
|
|
621
|
-
|
|
622
|
-
@Insert
|
|
623
|
-
suspend fun insertAll(users: List<UserEntity>)
|
|
624
|
-
|
|
625
|
-
@Update
|
|
626
|
-
suspend fun update(user: UserEntity)
|
|
627
|
-
|
|
628
|
-
@Delete
|
|
629
|
-
suspend fun delete(user: UserEntity)
|
|
630
|
-
|
|
631
|
-
@Query("DELETE FROM users WHERE created_at < :timestamp")
|
|
632
|
-
suspend fun deleteOlderThan(timestamp: Long)
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// Database
|
|
636
|
-
@Database(entities = [UserEntity::class], version = 1)
|
|
637
|
-
abstract class AppDatabase : RoomDatabase() {
|
|
638
|
-
abstract fun userDao(): UserDao
|
|
639
|
-
|
|
640
|
-
companion object {
|
|
641
|
-
@Volatile
|
|
642
|
-
private var INSTANCE: AppDatabase? = null
|
|
643
|
-
|
|
644
|
-
fun getDatabase(context: Context): AppDatabase {
|
|
645
|
-
return INSTANCE ?: synchronized(this) {
|
|
646
|
-
val instance = Room.databaseBuilder(
|
|
647
|
-
context.applicationContext,
|
|
648
|
-
AppDatabase::class.java,
|
|
649
|
-
"app_database"
|
|
650
|
-
).build()
|
|
651
|
-
INSTANCE = instance
|
|
652
|
-
instance
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
## Retrofit 网络请求
|
|
660
|
-
|
|
661
|
-
### API 定义
|
|
662
|
-
```kotlin
|
|
663
|
-
interface ApiService {
|
|
664
|
-
@GET("users")
|
|
665
|
-
suspend fun getUsers(): List<User>
|
|
666
|
-
|
|
667
|
-
@GET("users/{id}")
|
|
668
|
-
suspend fun getUser(@Path("id") userId: String): User
|
|
669
|
-
|
|
670
|
-
@POST("users")
|
|
671
|
-
suspend fun createUser(@Body user: CreateUserRequest): User
|
|
672
|
-
|
|
673
|
-
@PUT("users/{id}")
|
|
674
|
-
suspend fun updateUser(
|
|
675
|
-
@Path("id") userId: String,
|
|
676
|
-
@Body user: UpdateUserRequest
|
|
677
|
-
): User
|
|
678
|
-
|
|
679
|
-
@DELETE("users/{id}")
|
|
680
|
-
suspend fun deleteUser(@Path("id") userId: String)
|
|
681
|
-
|
|
682
|
-
@GET("search")
|
|
683
|
-
suspend fun search(@Query("q") query: String): SearchResult
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// Retrofit 配置
|
|
687
|
-
object RetrofitClient {
|
|
688
|
-
private val okHttpClient = OkHttpClient.Builder()
|
|
689
|
-
.addInterceptor { chain ->
|
|
690
|
-
val request = chain.request().newBuilder()
|
|
691
|
-
.addHeader("Authorization", "Bearer $token")
|
|
692
|
-
.build()
|
|
693
|
-
chain.proceed(request)
|
|
694
|
-
}
|
|
695
|
-
.connectTimeout(30, TimeUnit.SECONDS)
|
|
696
|
-
.readTimeout(30, TimeUnit.SECONDS)
|
|
697
|
-
.build()
|
|
698
|
-
|
|
699
|
-
val api: ApiService = Retrofit.Builder()
|
|
700
|
-
.baseUrl("https://api.example.com/")
|
|
701
|
-
.client(okHttpClient)
|
|
702
|
-
.addConverterFactory(GsonConverterFactory.create())
|
|
703
|
-
.build()
|
|
704
|
-
.create(ApiService::class.java)
|
|
705
|
-
}
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
## 依赖注入 (Hilt)
|
|
709
|
-
|
|
710
|
-
### 模块配置
|
|
711
|
-
```kotlin
|
|
712
|
-
@HiltAndroidApp
|
|
713
|
-
class MyApplication : Application()
|
|
714
|
-
|
|
715
|
-
@Module
|
|
716
|
-
@InstallIn(SingletonComponent::class)
|
|
717
|
-
object AppModule {
|
|
718
|
-
@Provides
|
|
719
|
-
@Singleton
|
|
720
|
-
fun provideApiService(): ApiService {
|
|
721
|
-
return RetrofitClient.api
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
@Provides
|
|
725
|
-
@Singleton
|
|
726
|
-
fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
|
|
727
|
-
return AppDatabase.getDatabase(context)
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
@Provides
|
|
731
|
-
fun provideUserDao(database: AppDatabase): UserDao {
|
|
732
|
-
return database.userDao()
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// 注入使用
|
|
737
|
-
@HiltViewModel
|
|
738
|
-
class UserViewModel @Inject constructor(
|
|
739
|
-
private val repository: UserRepository
|
|
740
|
-
) : ViewModel() {
|
|
741
|
-
// ...
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
@AndroidEntryPoint
|
|
745
|
-
class MainActivity : ComponentActivity() {
|
|
746
|
-
private val viewModel: UserViewModel by viewModels()
|
|
747
|
-
|
|
748
|
-
override fun onCreate(savedInstanceState: Bundle?) {
|
|
749
|
-
super.onCreate(savedInstanceState)
|
|
750
|
-
// ...
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
## 测试
|
|
756
|
-
|
|
757
|
-
### 单元测试
|
|
758
|
-
```kotlin
|
|
759
|
-
class CalculatorTest {
|
|
760
|
-
private lateinit var calculator: Calculator
|
|
761
|
-
|
|
762
|
-
@Before
|
|
763
|
-
fun setup() {
|
|
764
|
-
calculator = Calculator()
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
@Test
|
|
768
|
-
fun `addition should return correct result`() {
|
|
769
|
-
val result = calculator.add(2, 3)
|
|
770
|
-
assertEquals(5, result)
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
@Test
|
|
774
|
-
fun `division by zero should throw exception`() {
|
|
775
|
-
assertThrows<ArithmeticException> {
|
|
776
|
-
calculator.divide(10, 0)
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// 协程测试
|
|
782
|
-
@ExperimentalCoroutinesApi
|
|
783
|
-
class UserViewModelTest {
|
|
784
|
-
@get:Rule
|
|
785
|
-
val mainDispatcherRule = MainDispatcherRule()
|
|
786
|
-
|
|
787
|
-
private lateinit var viewModel: UserViewModel
|
|
788
|
-
private lateinit var repository: FakeUserRepository
|
|
789
|
-
|
|
790
|
-
@Before
|
|
791
|
-
fun setup() {
|
|
792
|
-
repository = FakeUserRepository()
|
|
793
|
-
viewModel = UserViewModel(repository)
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
@Test
|
|
797
|
-
fun `loadUsers should update state to success`() = runTest {
|
|
798
|
-
val users = listOf(User("1", "John", "john@example.com"))
|
|
799
|
-
repository.setUsers(users)
|
|
800
|
-
|
|
801
|
-
viewModel.loadUsers()
|
|
802
|
-
|
|
803
|
-
val state = viewModel.uiState.value
|
|
804
|
-
assertTrue(state is UiState.Success)
|
|
805
|
-
assertEquals(users, (state as UiState.Success).users)
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
## 最佳实践
|
|
811
|
-
|
|
812
|
-
| 场景 | 推荐做法 |
|
|
813
|
-
|------|----------|
|
|
814
|
-
| 异步操作 | 使用 Coroutines + Flow |
|
|
815
|
-
| 状态管理 | StateFlow + ViewModel |
|
|
816
|
-
| 依赖注入 | Hilt |
|
|
817
|
-
| 网络请求 | Retrofit + OkHttp |
|
|
818
|
-
| 本地存储 | Room + DataStore |
|
|
819
|
-
| UI 开发 | Jetpack Compose |
|
|
820
|
-
|
|
821
|
-
## 工具清单
|
|
822
|
-
|
|
823
|
-
| 工具 | 用途 |
|
|
824
|
-
|------|------|
|
|
825
|
-
| Android Studio | 官方 IDE |
|
|
826
|
-
| Gradle | 构建工具 |
|
|
827
|
-
| Kotlin Coroutines | 异步编程 |
|
|
828
|
-
| Jetpack Compose | 声明式 UI |
|
|
829
|
-
| Hilt | 依赖注入 |
|
|
830
|
-
| Retrofit | 网络请求 |
|
|
831
|
-
| Room | 数据库 |
|
|
832
|
-
| Coil | 图片加载 |
|
|
833
|
-
|
|
834
|
-
---
|