android production workflow
2
总安装量
0
周安装量
#65002
全站排名
安装命令
npx skills add https://github.com/wj071842154/android-production-workflow-skill --skill 'Android Production Workflow'
Skill 文档
Android Production Workflow – Jetpack Compose + MVVM + Material Design 3
Version: 1.0.0
Author: wj071842154
Last Updated: 2026-02-13
ð Overview
This skill provides a complete, production-ready Android development workflow using:
- Jetpack Compose for declarative UI
- MVVM + Clean Architecture for scalable structure
- Material Design 3 for modern UI/UX
- Hilt for dependency injection
- Kotlin Coroutines + Flow for async operations
- Retrofit for networking
- Room for local database
ðï¸ Architecture Principles
Clean Architecture Layers
âââââââââââââââââââââââââââââââââââââââââââ
â Presentation Layer â
â (UI, ViewModel, Compose Screens) â
âââââââââââââââ¬ââââââââââââââââââââââââââââ
â
âââââââââââââââ¼ââââââââââââââââââââââââââââ
â Domain Layer â
â (Use Cases, Domain Models, Repo â
â Interfaces - Pure Kotlin) â
âââââââââââââââ¬ââââââââââââââââââââââââââââ
â
âââââââââââââââ¼ââââââââââââââââââââââââââââ
â Data Layer â
â (Repository Impl, API, Database, â
â Data Models, Mappers) â
âââââââââââââââââââââââââââââââââââââââââââ
MVVM Pattern
ViewModel holds UI state and business logic
View (Composable) observes state and renders UI
Model (Domain/Data) provides data through repositories
User Action â Composable â ViewModel â Use Case â Repository â API/Database
â â
ââââââââââââââ StateFlow ââââââââââââââââââââââââââââ
ð Project Structure
app/
âââ src/main/java/com/yourapp/
â âââ MyApplication.kt # Hilt Application
â â
â âââ di/ # Dependency Injection
â â âââ AppModule.kt
â â âââ NetworkModule.kt
â â âââ DatabaseModule.kt
â â
â âââ domain/ # Domain Layer (Pure Kotlin)
â â âââ model/ # Domain Models
â â âââ repository/ # Repository Interfaces
â â âââ usecase/ # Business Logic Use Cases
â â
â âââ data/ # Data Layer
â â âââ repository/ # Repository Implementations
â â âââ remote/ # Remote Data Source
â â â âââ api/ # Retrofit API interfaces
â â â âââ dto/ # Data Transfer Objects
â â âââ local/ # Local Data Source
â â â âââ database/ # Room Database
â â â âââ dao/ # Data Access Objects
â â â âââ entity/ # Database Entities
â â âââ mapper/ # Data Mappers (DTO â Domain)
â â
â âââ presentation/ # Presentation Layer
â âââ theme/ # Material 3 Theme
â â âââ Color.kt
â â âââ Theme.kt
â â âââ Type.kt
â âââ navigation/
â â âââ NavGraph.kt
â âââ components/ # Reusable Composables
â âââ screens/ # Feature Screens
â âââ [feature]/
â âââ [Feature]Screen.kt
â âââ [Feature]ViewModel.kt
â âââ [Feature]UiState.kt
ð¯ Core Patterns
1. StateFlow + UI State Pattern
Always use immutable data classes for UI state:
data class HomeUiState(
val isLoading: Boolean = false,
val data: List<Item> = emptyList(),
val error: String? = null
)
@HiltViewModel
class HomeViewModel @Inject constructor(
private val useCase: GetItemsUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
init {
loadData()
}
fun loadData() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
useCase().collect { result ->
result.fold(
onSuccess = { data ->
_uiState.update {
it.copy(isLoading = false, data = data, error = null)
}
},
onFailure = { exception ->
_uiState.update {
it.copy(isLoading = false, error = exception.message)
}
}
)
}
}
}
}
2. Repository Pattern with Flow
// Domain Layer - Interface
interface UserRepository {
fun getUsers(): Flow<Result<List<User>>>
suspend fun getUserById(id: Int): Result<User>
}
// Data Layer - Implementation
class UserRepositoryImpl @Inject constructor(
private val apiService: ApiService,
private val userDao: UserDao
) : UserRepository {
override fun getUsers(): Flow<Result<List<User>>> = flow {
try {
// Try remote first
val response = apiService.getUsers()
if (response.isSuccessful) {
val users = response.body()?.map { it.toDomain() } ?: emptyList()
// Cache locally
userDao.insertAll(users.map { it.toEntity() })
emit(Result.success(users))
} else {
// Fallback to local cache
val cachedUsers = userDao.getAll().map { it.toDomain() }
emit(Result.success(cachedUsers))
}
} catch (e: Exception) {
// Return cached data on error
val cachedUsers = userDao.getAll().map { it.toDomain() }
if (cachedUsers.isNotEmpty()) {
emit(Result.success(cachedUsers))
} else {
emit(Result.failure(e))
}
}
}
}
3. Use Case Pattern
Single Responsibility – One use case per business action:
class GetUsersUseCase @Inject constructor(
private val repository: UserRepository
) {
operator fun invoke(): Flow<Result<List<User>>> {
return repository.getUsers()
}
}
class LoginUseCase @Inject constructor(
private val repository: AuthRepository
) {
suspend operator fun invoke(email: String, password: String): Result<User> {
// Add business logic validation here
if (email.isBlank() || password.isBlank()) {
return Result.failure(Exception("Email and password required"))
}
return repository.login(email, password)
}
}
4. Hilt Dependency Injection
// Application
@HiltAndroidApp
class MyApplication : Application()
// Network Module
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
// Repository Binding
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindUserRepository(
impl: UserRepositoryImpl
): UserRepository
}
5. Compose UI with State Collection
@Composable
fun HomeScreen(
viewModel: HomeViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Scaffold(
topBar = { TopBar() }
) { paddingValues ->
when {
uiState.isLoading -> LoadingIndicator()
uiState.error != null -> ErrorView(uiState.error!!)
else -> ContentView(uiState.data)
}
}
}
ð¨ Material Design 3 Guidelines
Color Scheme Structure
// Use Material 3 color roles
val LightColorScheme = lightColorScheme(
primary = Color(0xFF6750A4),
onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFEADDFF),
onPrimaryContainer = Color(0xFF21005D),
secondary = Color(0xFF625B71),
// ... complete color scheme
)
Typography System
val Typography = Typography(
displayLarge = TextStyle(fontSize = 57.sp, lineHeight = 64.sp),
headlineMedium = TextStyle(fontSize = 28.sp, lineHeight = 36.sp),
titleLarge = TextStyle(fontSize = 22.sp, lineHeight = 28.sp),
bodyLarge = TextStyle(fontSize = 16.sp, lineHeight = 24.sp),
labelMedium = TextStyle(fontSize = 12.sp, lineHeight = 16.sp)
)
Component Usage
// Cards
Card(
onClick = { /* action */ },
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) { Content() }
// Buttons
Button(
onClick = { /* action */ },
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
)
) { Text("Action") }
// Top App Bar
TopAppBar(
title = { Text("Title") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
)
)
â¡ Performance Best Practices
1. Recomposition Optimization
// â
GOOD: Use keys for lists
LazyColumn {
items(items = users, key = { it.id }) { user ->
UserItem(user)
}
}
// â
GOOD: Use derivedStateOf for expensive calculations
val sortedUsers by remember(users) {
derivedStateOf { users.sortedBy { it.name } }
}
// â BAD: Don't perform heavy work during composition
@Composable
fun MyScreen(users: List<User>) {
val sorted = users.sortedBy { it.name } // Runs on every recomposition!
}
2. State Hoisting
// â
GOOD: Stateless composables
@Composable
fun SearchBar(
query: String,
onQueryChange: (String) -> Unit
) {
TextField(value = query, onValueChange = onQueryChange)
}
// Usage in parent
@Composable
fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {
val searchQuery by viewModel.searchQuery.collectAsState()
SearchBar(
query = searchQuery,
onQueryChange = { viewModel.updateSearchQuery(it) }
)
}
3. Side Effects Management
// LaunchedEffect - For coroutine-based side effects
LaunchedEffect(userId) {
viewModel.loadUser(userId)
}
// DisposableEffect - For cleanup
DisposableEffect(Unit) {
val listener = SomeListener()
registerListener(listener)
onDispose {
unregisterListener(listener)
}
}
// SideEffect - For non-suspend side effects
SideEffect {
analytics.logScreenView("HomeScreen")
}
ð Code Quality Checklist
ViewModel
- Uses
StateFlowfor UI state, notLiveData - Exposes immutable state (
StateFlow, notMutableStateFlow) - Uses
viewModelScopefor coroutines - Handles all error cases
- Uses
update {}for state modifications - No UI logic (View references, Context)
Repository
- Returns
Flow<Result<T>>orResult<T> - Implements caching strategy (remote + local)
- Maps DTOs to domain models
- Handles network errors gracefully
- Uses Kotlin coroutines, not callbacks
Composables
- Uses
collectAsStateWithLifecycle()for Flow collection - Keys are provided for
LazyColumn/LazyRowitems - State hoisting applied for reusability
- No side effects in composition (use
LaunchedEffect) - Previews provided with
@Preview
Dependency Injection
-
@HiltViewModelfor ViewModels -
@Singletonfor app-level dependencies - Interface binding with
@Binds - Proper scoping (
SingletonComponent,ActivityComponent)
ð Development Workflow
Step 1: Define Feature Requirements
- Identify screens needed
- Define user actions and navigation
- List data requirements (API, database)
Step 2: Domain Layer First
- Create domain models (pure Kotlin data classes)
- Define repository interfaces
- Write use cases with business logic
Step 3: Data Layer
- Create API service with Retrofit
- Define DTOs and mappers
- Implement repository with error handling
- Set up Room database if needed
Step 4: Dependency Injection
- Create Hilt modules for network/database
- Bind repository implementations
- Verify dependency graph
Step 5: Presentation Layer
- Define UI state data class
- Create ViewModel with StateFlow
- Build Composable screens
- Set up navigation
Step 6: Testing
- Unit test ViewModels (verify state transitions)
- Unit test Use Cases (verify business logic)
- Test repositories with mock data sources
- Compose UI tests for critical flows
𧪠Testing Patterns
ViewModel Testing
@Test
fun `loadUsers should update state with success`() = runTest {
// Given
val mockUsers = listOf(User(1, "Test"))
coEvery { useCase() } returns flowOf(Result.success(mockUsers))
val viewModel = HomeViewModel(useCase)
// When
viewModel.loadUsers()
// Then
val state = viewModel.uiState.value
assertEquals(false, state.isLoading)
assertEquals(mockUsers, state.users)
assertNull(state.error)
}
Compose UI Testing
@Test
fun homeScreen_displaysUserList() {
composeTestRule.setContent {
HomeScreen()
}
composeTestRule
.onNodeWithText("User Name")
.assertIsDisplayed()
}
ð Dependencies Template
// Compose BOM
val composeBom = platform("androidx.compose:compose-bom:2024.01.00")
implementation(composeBom)
// Core Compose
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.8.2")
// ViewModel + Lifecycle
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")
// Navigation
implementation("androidx.navigation:navigation-compose:2.7.6")
// Hilt
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
// Coroutines
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
// Retrofit
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
// Room
val roomVersion = "2.6.1"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
kapt("androidx.room:room-compiler:$roomVersion")
// Testing
testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
testImplementation("io.mockk:mockk:1.13.8")
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
â ï¸ Common Pitfalls to Avoid
â DON’T
// Don't expose MutableStateFlow
val uiState: MutableStateFlow<UiState> = MutableStateFlow(UiState())
// Don't use LiveData in new projects
val data: LiveData<List<Item>> = liveData { }
// Don't perform heavy work during composition
@Composable
fun MyScreen(items: List<Item>) {
val sorted = items.sortedBy { it.name } // RECOMPUTES EVERY TIME!
}
// Don't use viewModelScope without error handling
viewModelScope.launch {
val result = repository.getData() // Can crash on exception!
}
// Don't mix concerns - ViewModels shouldn't know about Views
class MyViewModel : ViewModel() {
fun updateUI(context: Context) { /* NO! */ }
}
â DO
// Expose immutable StateFlow
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// Use StateFlow with Kotlin Flow
val data: Flow<List<Item>> = repository.getItems()
// Use derivedStateOf for expensive calculations
val sorted by remember(items) {
derivedStateOf { items.sortedBy { it.name } }
}
// Handle errors gracefully
viewModelScope.launch {
try {
val result = repository.getData()
_uiState.update { it.copy(data = result) }
} catch (e: Exception) {
_uiState.update { it.copy(error = e.message) }
}
}
// Keep ViewModels pure - no Android dependencies
class MyViewModel @Inject constructor(
private val useCase: GetDataUseCase
) : ViewModel()
ð References
- Android Architecture Guide
- Jetpack Compose Documentation
- Material Design 3
- Hilt Dependency Injection
- Kotlin Coroutines
ð License
MIT License – Free to use and modify for your projects.