code-abyss 1.6.15 → 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.
Files changed (82) hide show
  1. package/bin/install.js +25 -4
  2. package/package.json +2 -2
  3. package/skills/SKILL.md +24 -16
  4. package/skills/domains/ai/SKILL.md +2 -2
  5. package/skills/domains/ai/prompt-and-eval.md +279 -0
  6. package/skills/domains/architecture/SKILL.md +2 -3
  7. package/skills/domains/architecture/security-arch.md +87 -0
  8. package/skills/domains/data-engineering/SKILL.md +188 -26
  9. package/skills/domains/development/SKILL.md +1 -4
  10. package/skills/domains/devops/SKILL.md +3 -5
  11. package/skills/domains/devops/performance.md +63 -0
  12. package/skills/domains/devops/testing.md +97 -0
  13. package/skills/domains/frontend-design/SKILL.md +12 -3
  14. package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
  15. package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  16. package/skills/domains/frontend-design/engineering.md +287 -0
  17. package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
  18. package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  19. package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
  20. package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  21. package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
  22. package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  23. package/skills/domains/infrastructure/SKILL.md +174 -34
  24. package/skills/domains/mobile/SKILL.md +211 -21
  25. package/skills/domains/orchestration/SKILL.md +1 -0
  26. package/skills/domains/security/SKILL.md +4 -6
  27. package/skills/domains/security/blue-team.md +57 -0
  28. package/skills/domains/security/red-team.md +54 -0
  29. package/skills/domains/security/threat-intel.md +50 -0
  30. package/skills/orchestration/multi-agent/SKILL.md +195 -46
  31. package/skills/run_skill.js +134 -0
  32. package/skills/tools/gen-docs/SKILL.md +6 -4
  33. package/skills/tools/gen-docs/scripts/doc_generator.js +349 -0
  34. package/skills/tools/verify-change/SKILL.md +8 -6
  35. package/skills/tools/verify-change/scripts/change_analyzer.js +270 -0
  36. package/skills/tools/verify-module/SKILL.md +6 -4
  37. package/skills/tools/verify-module/scripts/module_scanner.js +145 -0
  38. package/skills/tools/verify-quality/SKILL.md +5 -3
  39. package/skills/tools/verify-quality/scripts/quality_checker.js +276 -0
  40. package/skills/tools/verify-security/SKILL.md +7 -5
  41. package/skills/tools/verify-security/scripts/security_scanner.js +133 -0
  42. package/skills/domains/COVERAGE_PLAN.md +0 -232
  43. package/skills/domains/ai/model-evaluation.md +0 -790
  44. package/skills/domains/ai/prompt-engineering.md +0 -703
  45. package/skills/domains/architecture/compliance.md +0 -299
  46. package/skills/domains/architecture/data-security.md +0 -184
  47. package/skills/domains/data-engineering/data-pipeline.md +0 -762
  48. package/skills/domains/data-engineering/data-quality.md +0 -894
  49. package/skills/domains/data-engineering/stream-processing.md +0 -791
  50. package/skills/domains/development/dart.md +0 -963
  51. package/skills/domains/development/kotlin.md +0 -834
  52. package/skills/domains/development/php.md +0 -659
  53. package/skills/domains/development/swift.md +0 -755
  54. package/skills/domains/devops/e2e-testing.md +0 -914
  55. package/skills/domains/devops/performance-testing.md +0 -734
  56. package/skills/domains/devops/testing-strategy.md +0 -667
  57. package/skills/domains/frontend-design/build-tools.md +0 -743
  58. package/skills/domains/frontend-design/performance.md +0 -734
  59. package/skills/domains/frontend-design/testing.md +0 -699
  60. package/skills/domains/infrastructure/gitops.md +0 -735
  61. package/skills/domains/infrastructure/iac.md +0 -855
  62. package/skills/domains/infrastructure/kubernetes.md +0 -1018
  63. package/skills/domains/mobile/android-dev.md +0 -979
  64. package/skills/domains/mobile/cross-platform.md +0 -795
  65. package/skills/domains/mobile/ios-dev.md +0 -931
  66. package/skills/domains/security/secrets-management.md +0 -834
  67. package/skills/domains/security/supply-chain.md +0 -931
  68. package/skills/domains/security/threat-modeling.md +0 -828
  69. package/skills/run_skill.py +0 -88
  70. package/skills/tests/README.md +0 -225
  71. package/skills/tests/SUMMARY.md +0 -362
  72. package/skills/tests/__init__.py +0 -3
  73. package/skills/tests/test_change_analyzer.py +0 -558
  74. package/skills/tests/test_doc_generator.py +0 -538
  75. package/skills/tests/test_module_scanner.py +0 -376
  76. package/skills/tests/test_quality_checker.py +0 -516
  77. package/skills/tests/test_security_scanner.py +0 -426
  78. package/skills/tools/gen-docs/scripts/doc_generator.py +0 -491
  79. package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
  80. package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
  81. package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
  82. package/skills/tools/verify-security/scripts/security_scanner.py +0 -368
@@ -1,979 +0,0 @@
1
- ---
2
- name: android-dev
3
- description: Android 开发。Jetpack Compose、Kotlin、ViewModel、LiveData、Coroutines、Flow、MVVM、Android架构。当用户提到 Android 开发、Jetpack Compose、Kotlin 时使用。
4
- ---
5
-
6
- # 🤖 Android 开发 · Android Development
7
-
8
- ## Jetpack Compose 基础
9
-
10
- ### Composable 函数
11
- ```kotlin
12
- import androidx.compose.material3.*
13
- import androidx.compose.runtime.*
14
- import androidx.compose.ui.Modifier
15
- import androidx.compose.ui.unit.dp
16
-
17
- @Composable
18
- fun Greeting(name: String) {
19
- Column(
20
- modifier = Modifier.padding(16.dp)
21
- ) {
22
- Text(
23
- text = "Hello, $name!",
24
- style = MaterialTheme.typography.headlineMedium
25
- )
26
-
27
- Spacer(modifier = Modifier.height(8.dp))
28
-
29
- Button(onClick = { /* TODO */ }) {
30
- Text("Click Me")
31
- }
32
- }
33
- }
34
- ```
35
-
36
- ### State 管理
37
- ```kotlin
38
- @Composable
39
- fun CounterScreen() {
40
- var count by remember { mutableStateOf(0) }
41
- var isEnabled by remember { mutableStateOf(true) }
42
-
43
- Column(
44
- modifier = Modifier.padding(16.dp),
45
- horizontalAlignment = Alignment.CenterHorizontally
46
- ) {
47
- Text(
48
- text = "Count: $count",
49
- style = MaterialTheme.typography.headlineLarge
50
- )
51
-
52
- Row(
53
- horizontalArrangement = Arrangement.spacedBy(8.dp)
54
- ) {
55
- Button(onClick = { count-- }) {
56
- Text("-")
57
- }
58
- Button(onClick = { count++ }) {
59
- Text("+")
60
- }
61
- }
62
-
63
- Switch(
64
- checked = isEnabled,
65
- onCheckedChange = { isEnabled = it }
66
- )
67
- }
68
- }
69
- ```
70
-
71
- ### LazyColumn 列表
72
- ```kotlin
73
- @Composable
74
- fun UserList(users: List<User>) {
75
- LazyColumn(
76
- contentPadding = PaddingValues(16.dp),
77
- verticalArrangement = Arrangement.spacedBy(8.dp)
78
- ) {
79
- items(users) { user ->
80
- UserItem(user)
81
- }
82
- }
83
- }
84
-
85
- @Composable
86
- fun UserItem(user: User) {
87
- Card(
88
- modifier = Modifier.fillMaxWidth(),
89
- elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
90
- ) {
91
- Row(
92
- modifier = Modifier.padding(16.dp),
93
- verticalAlignment = Alignment.CenterVertically
94
- ) {
95
- AsyncImage(
96
- model = user.avatarUrl,
97
- contentDescription = null,
98
- modifier = Modifier.size(48.dp)
99
- )
100
-
101
- Spacer(modifier = Modifier.width(16.dp))
102
-
103
- Column {
104
- Text(
105
- text = user.name,
106
- style = MaterialTheme.typography.titleMedium
107
- )
108
- Text(
109
- text = user.email,
110
- style = MaterialTheme.typography.bodySmall
111
- )
112
- }
113
- }
114
- }
115
- }
116
- ```
117
-
118
- ## Compose 高级
119
-
120
- ### Side Effects
121
- ```kotlin
122
- @Composable
123
- fun TimerScreen() {
124
- var seconds by remember { mutableStateOf(0) }
125
-
126
- // LaunchedEffect: 启动协程
127
- LaunchedEffect(Unit) {
128
- while (true) {
129
- delay(1000)
130
- seconds++
131
- }
132
- }
133
-
134
- Text("Elapsed: $seconds seconds")
135
- }
136
-
137
- @Composable
138
- fun AnalyticsScreen(screenName: String) {
139
- // DisposableEffect: 清理资源
140
- DisposableEffect(screenName) {
141
- analytics.logScreenView(screenName)
142
- onDispose {
143
- analytics.logScreenExit(screenName)
144
- }
145
- }
146
-
147
- // SideEffect: 同步状态到外部
148
- SideEffect {
149
- externalState.update(screenName)
150
- }
151
- }
152
-
153
- @Composable
154
- fun SearchScreen() {
155
- var query by remember { mutableStateOf("") }
156
- var results by remember { mutableStateOf<List<Item>>(emptyList()) }
157
-
158
- // snapshotFlow: 监听状态变化
159
- LaunchedEffect(Unit) {
160
- snapshotFlow { query }
161
- .debounce(300)
162
- .filter { it.isNotEmpty() }
163
- .collectLatest { q ->
164
- results = repository.search(q)
165
- }
166
- }
167
-
168
- Column {
169
- TextField(
170
- value = query,
171
- onValueChange = { query = it }
172
- )
173
- LazyColumn {
174
- items(results) { item ->
175
- Text(item.name)
176
- }
177
- }
178
- }
179
- }
180
- ```
181
-
182
- ### Custom Modifier
183
- ```kotlin
184
- fun Modifier.shimmer(): Modifier = composed {
185
- var offsetX by remember { mutableStateOf(0f) }
186
- val infiniteTransition = rememberInfiniteTransition()
187
-
188
- val shimmerOffset by infiniteTransition.animateFloat(
189
- initialValue = 0f,
190
- targetValue = 1000f,
191
- animationSpec = infiniteRepeatable(
192
- animation = tween(1000, easing = LinearEasing)
193
- )
194
- )
195
-
196
- this.drawWithContent {
197
- drawContent()
198
- drawRect(
199
- brush = Brush.linearGradient(
200
- colors = listOf(
201
- Color.Transparent,
202
- Color.White.copy(alpha = 0.3f),
203
- Color.Transparent
204
- ),
205
- start = Offset(shimmerOffset, 0f),
206
- end = Offset(shimmerOffset + 200f, 0f)
207
- )
208
- )
209
- }
210
- }
211
-
212
- // 使用
213
- Box(
214
- modifier = Modifier
215
- .size(200.dp)
216
- .shimmer()
217
- )
218
- ```
219
-
220
- ### Navigation
221
- ```kotlin
222
- @Composable
223
- fun AppNavigation() {
224
- val navController = rememberNavController()
225
-
226
- NavHost(
227
- navController = navController,
228
- startDestination = "home"
229
- ) {
230
- composable("home") {
231
- HomeScreen(
232
- onNavigateToDetail = { id ->
233
- navController.navigate("detail/$id")
234
- }
235
- )
236
- }
237
-
238
- composable(
239
- route = "detail/{itemId}",
240
- arguments = listOf(navArgument("itemId") { type = NavType.IntType })
241
- ) { backStackEntry ->
242
- val itemId = backStackEntry.arguments?.getInt("itemId")
243
- DetailScreen(itemId = itemId)
244
- }
245
- }
246
- }
247
- ```
248
-
249
- ## ViewModel & LiveData
250
-
251
- ### ViewModel
252
- ```kotlin
253
- class UserViewModel(
254
- private val repository: UserRepository
255
- ) : ViewModel() {
256
-
257
- private val _users = MutableLiveData<List<User>>()
258
- val users: LiveData<List<User>> = _users
259
-
260
- private val _isLoading = MutableLiveData(false)
261
- val isLoading: LiveData<Boolean> = _isLoading
262
-
263
- private val _error = MutableLiveData<String?>()
264
- val error: LiveData<String?> = _error
265
-
266
- fun loadUsers() {
267
- viewModelScope.launch {
268
- _isLoading.value = true
269
- _error.value = null
270
-
271
- try {
272
- val result = repository.getUsers()
273
- _users.value = result
274
- } catch (e: Exception) {
275
- _error.value = e.message
276
- } finally {
277
- _isLoading.value = false
278
- }
279
- }
280
- }
281
-
282
- fun refresh() {
283
- loadUsers()
284
- }
285
- }
286
- ```
287
-
288
- ### StateFlow (推荐)
289
- ```kotlin
290
- class UserViewModel(
291
- private val repository: UserRepository
292
- ) : ViewModel() {
293
-
294
- private val _uiState = MutableStateFlow(UserUiState())
295
- val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
296
-
297
- init {
298
- loadUsers()
299
- }
300
-
301
- fun loadUsers() {
302
- viewModelScope.launch {
303
- _uiState.update { it.copy(isLoading = true) }
304
-
305
- repository.getUsers()
306
- .onSuccess { users ->
307
- _uiState.update {
308
- it.copy(users = users, isLoading = false)
309
- }
310
- }
311
- .onFailure { error ->
312
- _uiState.update {
313
- it.copy(error = error.message, isLoading = false)
314
- }
315
- }
316
- }
317
- }
318
- }
319
-
320
- data class UserUiState(
321
- val users: List<User> = emptyList(),
322
- val isLoading: Boolean = false,
323
- val error: String? = null
324
- )
325
-
326
- // Compose 中使用
327
- @Composable
328
- fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
329
- val uiState by viewModel.uiState.collectAsState()
330
-
331
- when {
332
- uiState.isLoading -> LoadingView()
333
- uiState.error != null -> ErrorView(uiState.error!!)
334
- else -> UserList(uiState.users)
335
- }
336
- }
337
- ```
338
-
339
- ## Kotlin Coroutines
340
-
341
- ### 基础用法
342
- ```kotlin
343
- // 启动协程
344
- viewModelScope.launch {
345
- val result = withContext(Dispatchers.IO) {
346
- repository.fetchData()
347
- }
348
- updateUI(result)
349
- }
350
-
351
- // 并发执行
352
- suspend fun loadData() = coroutineScope {
353
- val users = async { repository.getUsers() }
354
- val posts = async { repository.getPosts() }
355
-
356
- CombinedData(
357
- users = users.await(),
358
- posts = posts.await()
359
- )
360
- }
361
-
362
- // 超时控制
363
- try {
364
- withTimeout(5000) {
365
- repository.fetchData()
366
- }
367
- } catch (e: TimeoutCancellationException) {
368
- // 处理超时
369
- }
370
- ```
371
-
372
- ### Flow
373
- ```kotlin
374
- class UserRepository {
375
- fun observeUsers(): Flow<List<User>> = flow {
376
- while (true) {
377
- val users = api.getUsers()
378
- emit(users)
379
- delay(30000) // 每30秒刷新
380
- }
381
- }
382
-
383
- fun searchUsers(query: String): Flow<List<User>> = flow {
384
- emit(emptyList()) // 初始状态
385
- delay(300) // 防抖
386
- val results = api.search(query)
387
- emit(results)
388
- }.flowOn(Dispatchers.IO)
389
- }
390
-
391
- // ViewModel 中使用
392
- class UserViewModel(
393
- private val repository: UserRepository
394
- ) : ViewModel() {
395
-
396
- val users: StateFlow<List<User>> = repository.observeUsers()
397
- .stateIn(
398
- scope = viewModelScope,
399
- started = SharingStarted.WhileSubscribed(5000),
400
- initialValue = emptyList()
401
- )
402
-
403
- private val searchQuery = MutableStateFlow("")
404
-
405
- val searchResults: StateFlow<List<User>> = searchQuery
406
- .debounce(300)
407
- .filter { it.isNotEmpty() }
408
- .flatMapLatest { query ->
409
- repository.searchUsers(query)
410
- }
411
- .stateIn(
412
- scope = viewModelScope,
413
- started = SharingStarted.WhileSubscribed(),
414
- initialValue = emptyList()
415
- )
416
-
417
- fun search(query: String) {
418
- searchQuery.value = query
419
- }
420
- }
421
- ```
422
-
423
- ### Channel
424
- ```kotlin
425
- class EventBus {
426
- private val _events = Channel<Event>(Channel.BUFFERED)
427
- val events: Flow<Event> = _events.receiveAsFlow()
428
-
429
- suspend fun send(event: Event) {
430
- _events.send(event)
431
- }
432
- }
433
-
434
- // 使用
435
- viewModelScope.launch {
436
- eventBus.events.collect { event ->
437
- when (event) {
438
- is Event.UserLoggedIn -> handleLogin(event.user)
439
- is Event.DataUpdated -> refresh()
440
- }
441
- }
442
- }
443
- ```
444
-
445
- ## MVVM 架构
446
-
447
- ### Model
448
- ```kotlin
449
- data class User(
450
- val id: Int,
451
- val name: String,
452
- val email: String,
453
- val avatarUrl: String
454
- )
455
-
456
- data class LoginRequest(
457
- val username: String,
458
- val password: String
459
- )
460
-
461
- data class LoginResponse(
462
- val token: String,
463
- val user: User
464
- )
465
- ```
466
-
467
- ### Repository
468
- ```kotlin
469
- interface UserRepository {
470
- suspend fun login(username: String, password: String): Result<LoginResponse>
471
- suspend fun getProfile(): Result<User>
472
- fun observeUser(): Flow<User?>
473
- }
474
-
475
- class UserRepositoryImpl(
476
- private val api: ApiService,
477
- private val userDao: UserDao,
478
- private val tokenManager: TokenManager
479
- ) : UserRepository {
480
-
481
- override suspend fun login(username: String, password: String): Result<LoginResponse> {
482
- return try {
483
- val request = LoginRequest(username, password)
484
- val response = api.login(request)
485
- tokenManager.saveToken(response.token)
486
- userDao.insert(response.user)
487
- Result.success(response)
488
- } catch (e: Exception) {
489
- Result.failure(e)
490
- }
491
- }
492
-
493
- override suspend fun getProfile(): Result<User> {
494
- return try {
495
- val user = api.getProfile()
496
- userDao.insert(user)
497
- Result.success(user)
498
- } catch (e: Exception) {
499
- Result.failure(e)
500
- }
501
- }
502
-
503
- override fun observeUser(): Flow<User?> {
504
- return userDao.observeUser()
505
- }
506
- }
507
- ```
508
-
509
- ### ViewModel
510
- ```kotlin
511
- @HiltViewModel
512
- class LoginViewModel @Inject constructor(
513
- private val repository: UserRepository
514
- ) : ViewModel() {
515
-
516
- private val _uiState = MutableStateFlow(LoginUiState())
517
- val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
518
-
519
- fun updateUsername(username: String) {
520
- _uiState.update { it.copy(username = username) }
521
- }
522
-
523
- fun updatePassword(password: String) {
524
- _uiState.update { it.copy(password = password) }
525
- }
526
-
527
- fun login() {
528
- val state = _uiState.value
529
- if (!state.isValid) return
530
-
531
- viewModelScope.launch {
532
- _uiState.update { it.copy(isLoading = true, error = null) }
533
-
534
- repository.login(state.username, state.password)
535
- .onSuccess {
536
- _uiState.update { it.copy(isLoading = false, isLoggedIn = true) }
537
- }
538
- .onFailure { error ->
539
- _uiState.update {
540
- it.copy(isLoading = false, error = error.message)
541
- }
542
- }
543
- }
544
- }
545
- }
546
-
547
- data class LoginUiState(
548
- val username: String = "",
549
- val password: String = "",
550
- val isLoading: Boolean = false,
551
- val error: String? = null,
552
- val isLoggedIn: Boolean = false
553
- ) {
554
- val isValid: Boolean
555
- get() = username.isNotEmpty() && password.length >= 6
556
- }
557
- ```
558
-
559
- ### View (Compose)
560
- ```kotlin
561
- @Composable
562
- fun LoginScreen(
563
- viewModel: LoginViewModel = hiltViewModel(),
564
- onLoginSuccess: () -> Unit
565
- ) {
566
- val uiState by viewModel.uiState.collectAsState()
567
-
568
- LaunchedEffect(uiState.isLoggedIn) {
569
- if (uiState.isLoggedIn) {
570
- onLoginSuccess()
571
- }
572
- }
573
-
574
- Column(
575
- modifier = Modifier
576
- .fillMaxSize()
577
- .padding(16.dp),
578
- verticalArrangement = Arrangement.Center
579
- ) {
580
- OutlinedTextField(
581
- value = uiState.username,
582
- onValueChange = viewModel::updateUsername,
583
- label = { Text("Username") },
584
- modifier = Modifier.fillMaxWidth()
585
- )
586
-
587
- Spacer(modifier = Modifier.height(8.dp))
588
-
589
- OutlinedTextField(
590
- value = uiState.password,
591
- onValueChange = viewModel::updatePassword,
592
- label = { Text("Password") },
593
- visualTransformation = PasswordVisualTransformation(),
594
- modifier = Modifier.fillMaxWidth()
595
- )
596
-
597
- if (uiState.error != null) {
598
- Text(
599
- text = uiState.error!!,
600
- color = MaterialTheme.colorScheme.error,
601
- style = MaterialTheme.typography.bodySmall,
602
- modifier = Modifier.padding(top = 8.dp)
603
- )
604
- }
605
-
606
- Spacer(modifier = Modifier.height(16.dp))
607
-
608
- Button(
609
- onClick = viewModel::login,
610
- enabled = uiState.isValid && !uiState.isLoading,
611
- modifier = Modifier.fillMaxWidth()
612
- ) {
613
- if (uiState.isLoading) {
614
- CircularProgressIndicator(
615
- modifier = Modifier.size(24.dp),
616
- color = MaterialTheme.colorScheme.onPrimary
617
- )
618
- } else {
619
- Text("Login")
620
- }
621
- }
622
- }
623
- }
624
- ```
625
-
626
- ## 依赖注入 (Hilt)
627
-
628
- ### 配置
629
- ```kotlin
630
- @HiltAndroidApp
631
- class MyApplication : Application()
632
-
633
- @AndroidEntryPoint
634
- class MainActivity : ComponentActivity() {
635
- override fun onCreate(savedInstanceState: Bundle?) {
636
- super.onCreate(savedInstanceState)
637
- setContent {
638
- MyApp()
639
- }
640
- }
641
- }
642
- ```
643
-
644
- ### Module
645
- ```kotlin
646
- @Module
647
- @InstallIn(SingletonComponent::class)
648
- object NetworkModule {
649
-
650
- @Provides
651
- @Singleton
652
- fun provideOkHttpClient(): OkHttpClient {
653
- return OkHttpClient.Builder()
654
- .addInterceptor(AuthInterceptor())
655
- .connectTimeout(30, TimeUnit.SECONDS)
656
- .build()
657
- }
658
-
659
- @Provides
660
- @Singleton
661
- fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
662
- return Retrofit.Builder()
663
- .baseUrl("https://api.example.com")
664
- .client(okHttpClient)
665
- .addConverterFactory(GsonConverterFactory.create())
666
- .build()
667
- }
668
-
669
- @Provides
670
- @Singleton
671
- fun provideApiService(retrofit: Retrofit): ApiService {
672
- return retrofit.create(ApiService::class.java)
673
- }
674
- }
675
-
676
- @Module
677
- @InstallIn(SingletonComponent::class)
678
- abstract class RepositoryModule {
679
-
680
- @Binds
681
- @Singleton
682
- abstract fun bindUserRepository(
683
- impl: UserRepositoryImpl
684
- ): UserRepository
685
- }
686
- ```
687
-
688
- ## Room 数据库
689
-
690
- ### Entity
691
- ```kotlin
692
- @Entity(tableName = "users")
693
- data class UserEntity(
694
- @PrimaryKey val id: Int,
695
- val name: String,
696
- val email: String,
697
- @ColumnInfo(name = "avatar_url") val avatarUrl: String,
698
- @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis()
699
- )
700
- ```
701
-
702
- ### DAO
703
- ```kotlin
704
- @Dao
705
- interface UserDao {
706
- @Query("SELECT * FROM users")
707
- fun observeAll(): Flow<List<UserEntity>>
708
-
709
- @Query("SELECT * FROM users WHERE id = :id")
710
- suspend fun getById(id: Int): UserEntity?
711
-
712
- @Insert(onConflict = OnConflictStrategy.REPLACE)
713
- suspend fun insert(user: UserEntity)
714
-
715
- @Insert(onConflict = OnConflictStrategy.REPLACE)
716
- suspend fun insertAll(users: List<UserEntity>)
717
-
718
- @Delete
719
- suspend fun delete(user: UserEntity)
720
-
721
- @Query("DELETE FROM users")
722
- suspend fun deleteAll()
723
- }
724
- ```
725
-
726
- ### Database
727
- ```kotlin
728
- @Database(
729
- entities = [UserEntity::class],
730
- version = 1,
731
- exportSchema = false
732
- )
733
- abstract class AppDatabase : RoomDatabase() {
734
- abstract fun userDao(): UserDao
735
- }
736
-
737
- @Module
738
- @InstallIn(SingletonComponent::class)
739
- object DatabaseModule {
740
-
741
- @Provides
742
- @Singleton
743
- fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
744
- return Room.databaseBuilder(
745
- context,
746
- AppDatabase::class.java,
747
- "app_database"
748
- )
749
- .fallbackToDestructiveMigration()
750
- .build()
751
- }
752
-
753
- @Provides
754
- fun provideUserDao(database: AppDatabase): UserDao {
755
- return database.userDao()
756
- }
757
- }
758
- ```
759
-
760
- ## 网络层
761
-
762
- ### Retrofit
763
- ```kotlin
764
- interface ApiService {
765
- @POST("auth/login")
766
- suspend fun login(@Body request: LoginRequest): LoginResponse
767
-
768
- @GET("users/{id}")
769
- suspend fun getUser(@Path("id") id: Int): User
770
-
771
- @GET("users")
772
- suspend fun getUsers(
773
- @Query("page") page: Int,
774
- @Query("limit") limit: Int = 20
775
- ): List<User>
776
-
777
- @Multipart
778
- @POST("upload")
779
- suspend fun uploadImage(
780
- @Part file: MultipartBody.Part
781
- ): UploadResponse
782
- }
783
- ```
784
-
785
- ### Interceptor
786
- ```kotlin
787
- class AuthInterceptor(
788
- private val tokenManager: TokenManager
789
- ) : Interceptor {
790
- override fun intercept(chain: Interceptor.Chain): Response {
791
- val original = chain.request()
792
- val token = tokenManager.getToken()
793
-
794
- val request = if (token != null) {
795
- original.newBuilder()
796
- .header("Authorization", "Bearer $token")
797
- .build()
798
- } else {
799
- original
800
- }
801
-
802
- return chain.proceed(request)
803
- }
804
- }
805
-
806
- class LoggingInterceptor : Interceptor {
807
- override fun intercept(chain: Interceptor.Chain): Response {
808
- val request = chain.request()
809
- Log.d("API", "Request: ${request.method} ${request.url}")
810
-
811
- val response = chain.proceed(request)
812
- Log.d("API", "Response: ${response.code}")
813
-
814
- return response
815
- }
816
- }
817
- ```
818
-
819
- ## 性能优化
820
-
821
- ### LazyColumn 优化
822
- ```kotlin
823
- @Composable
824
- fun OptimizedList(items: List<Item>) {
825
- LazyColumn(
826
- contentPadding = PaddingValues(16.dp),
827
- verticalArrangement = Arrangement.spacedBy(8.dp)
828
- ) {
829
- items(
830
- items = items,
831
- key = { it.id } // 关键:提供稳定的 key
832
- ) { item ->
833
- ItemCard(item)
834
- }
835
- }
836
- }
837
-
838
- @Composable
839
- fun ItemCard(item: Item) {
840
- // 使用 remember 避免重组时重新计算
841
- val formattedDate = remember(item.timestamp) {
842
- formatDate(item.timestamp)
843
- }
844
-
845
- Card {
846
- Text(formattedDate)
847
- }
848
- }
849
- ```
850
-
851
- ### 图片加载 (Coil)
852
- ```kotlin
853
- @Composable
854
- fun AsyncImage(url: String) {
855
- AsyncImage(
856
- model = ImageRequest.Builder(LocalContext.current)
857
- .data(url)
858
- .crossfade(true)
859
- .memoryCachePolicy(CachePolicy.ENABLED)
860
- .diskCachePolicy(CachePolicy.ENABLED)
861
- .build(),
862
- contentDescription = null,
863
- modifier = Modifier.size(200.dp)
864
- )
865
- }
866
- ```
867
-
868
- ### 避免过度重组
869
- ```kotlin
870
- @Composable
871
- fun ExpensiveScreen(data: Data) {
872
- // ❌ 错误:每次重组都会创建新实例
873
- val processor = DataProcessor()
874
-
875
- // ✅ 正确:使用 remember
876
- val processor = remember { DataProcessor() }
877
-
878
- // ✅ 使用 derivedStateOf 避免不必要的重组
879
- val filteredData by remember {
880
- derivedStateOf {
881
- data.items.filter { it.isActive }
882
- }
883
- }
884
- }
885
- ```
886
-
887
- ## 测试
888
-
889
- ### Unit Test
890
- ```kotlin
891
- @Test
892
- fun `login success updates state correctly`() = runTest {
893
- val repository = FakeUserRepository()
894
- val viewModel = LoginViewModel(repository)
895
-
896
- viewModel.updateUsername("test")
897
- viewModel.updatePassword("password")
898
- viewModel.login()
899
-
900
- advanceUntilIdle()
901
-
902
- val state = viewModel.uiState.value
903
- assertTrue(state.isLoggedIn)
904
- assertNull(state.error)
905
- }
906
-
907
- class FakeUserRepository : UserRepository {
908
- var loginResult: Result<LoginResponse>? = null
909
-
910
- override suspend fun login(username: String, password: String): Result<LoginResponse> {
911
- return loginResult ?: Result.success(
912
- LoginResponse("token", User(1, "Test", "test@example.com", ""))
913
- )
914
- }
915
-
916
- override suspend fun getProfile(): Result<User> {
917
- return Result.success(User(1, "Test", "test@example.com", ""))
918
- }
919
-
920
- override fun observeUser(): Flow<User?> = flowOf(null)
921
- }
922
- ```
923
-
924
- ### UI Test
925
- ```kotlin
926
- @get:Rule
927
- val composeTestRule = createComposeRule()
928
-
929
- @Test
930
- fun loginFlow() {
931
- composeTestRule.setContent {
932
- LoginScreen(onLoginSuccess = {})
933
- }
934
-
935
- composeTestRule
936
- .onNodeWithText("Username")
937
- .performTextInput("testuser")
938
-
939
- composeTestRule
940
- .onNodeWithText("Password")
941
- .performTextInput("password123")
942
-
943
- composeTestRule
944
- .onNodeWithText("Login")
945
- .performClick()
946
-
947
- composeTestRule
948
- .onNodeWithText("Welcome")
949
- .assertIsDisplayed()
950
- }
951
- ```
952
-
953
- ## 工具清单
954
-
955
- | 工具 | 用途 |
956
- |------|------|
957
- | Android Studio | IDE |
958
- | Gradle | 构建工具 |
959
- | Hilt | 依赖注入 |
960
- | Retrofit | 网络请求 |
961
- | Room | 数据库 |
962
- | Coil | 图片加载 |
963
- | LeakCanary | 内存泄漏检测 |
964
- | Detekt | 代码规范 |
965
-
966
- ## 最佳实践
967
-
968
- - ✅ Jetpack Compose 优先,View 系统按需使用
969
- - ✅ MVVM 架构 + Repository 模式
970
- - ✅ StateFlow 替代 LiveData
971
- - ✅ Kotlin Coroutines 处理异步
972
- - ✅ Hilt 依赖注入
973
- - ✅ Room 本地持久化
974
- - ✅ 使用 key 优化 LazyColumn
975
- - ✅ remember/derivedStateOf 避免重组
976
- - ✅ 单元测试覆盖 ViewModel
977
- - ✅ UI 测试验证关键流程
978
-
979
- ---