Android Kotlin 开发
概览
使用 Kotlin 构建健壮的原生 Android 应用,采用现代架构模式、Jetpack 库和 Compose 进行声明式 UI 开发。
何时使用
- 创建遵循最佳实践的原生 Android 应用
- 使用 Kotlin 进行类型安全开发
- 实现 Jetpack 的 MVVM 架构
- 使用 Jetpack Compose 构建现代 UI
- 集成 Android 平台 API
指令
1. 模型与 API 服务
// 模型
data class User(
val id: String,
val name: String,
val email: String,
val avatarUrl: String? = null
)
data class Item(
val id: String,
val title: String,
val description: String,
val imageUrl: String? = null,
val price: Double
)
// 使用 Retrofit 的 API 服务
interface ApiService {
@GET("/users/{id}")
suspend fun getUser(@Path("id") userId: String): User
@PUT("/users/{id}")
suspend fun updateUser(
@Path("id") userId: String,
@Body user: User
): User
@GET("/items")
suspend fun getItems(@Query("filter") filter: String = "all"): List<Item>
@POST("/items")
suspend fun createItem(@Body item: Item): Item
}
// 网络客户端设置
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
val httpClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder()
val token = PreferencesManager.getToken()
if (token.isNotEmpty()) {
requestBuilder.addHeader("Authorization", "Bearer $token")
}
requestBuilder.addHeader("Content-Type", "application/json")
chain.proceed(requestBuilder.build())
}
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
return Retrofit.Builder()
.baseUrl("https://api.example.com")
.client(httpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
2. Jetpack MVVM ViewModels
@HiltViewModel
class UserViewModel @Inject constructor(
private val apiService: ApiService
) : ViewModel() {
private val _user = MutableStateFlow<User?>(null)
val user: StateFlow<User?> = _user.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private val _errorMessage = MutableStateFlow<String?>(null)
val errorMessage: StateFlow<String?> = _errorMessage.asStateFlow()
fun fetchUser(userId: String) {
viewModelScope.launch {
_isLoading.value = true
_errorMessage.value = null
try {
val user = apiService.getUser(userId)
_user.value = user
} catch (e: Exception) {
_errorMessage.value = e.message ?: "Unknown error"
} finally {
_isLoading.value = false
}
}
}
fun logout() {
_user.value = null
}
}
@HiltViewModel
class ItemsViewModel @Inject constructor(
private val apiService: ApiService
) : ViewModel() {
private val _items = MutableStateFlow<List<Item>>(emptyList())
val items: StateFlow<List<Item>> = _items.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
fun fetchItems(filter: String = "all") {
viewModelScope.launch {
_isLoading.value = true
try {
val items = apiService.getItems(filter)
_items.value = items
} catch (e: Exception) {
println("Error fetching items: ${e.message}")
} finally {
_isLoading.value = false
}
}
}
fun addItem(item: Item) {
viewModelScope.launch {
try {
val created = apiService.createItem(item)
_items.value = _items.value + created
} catch (e: Exception) {
println("Error creating item: ${e.message}")
}
}
}
}
3. Jetpack Compose UI
@Composable
fun MainScreen() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("profile") { ProfileScreen(navController) }
composable("details/{itemId}") { backStackEntry ->
val itemId = backStackEntry.arguments?.getString("itemId") ?: return@composable
DetailsScreen(itemId = itemId, navController = navController)
}
}
}
@Composable
fun HomeScreen(navController: NavController) {
val viewModel: ItemsViewModel = hiltViewModel()
val items by viewModel.items.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
LaunchedEffect(Unit) {
viewModel.fetchItems()
}
Scaffold(
topBar = { TopAppBar(title = { Text("Items") }) }
) { paddingValues ->
if (isLoading) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
} else {
LazyColumn(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize(),
contentPadding = PaddingValues(8.dp)
) {
items(items) { item ->
ItemCard(
item = item,
onClick = { navController.navigate("details/${item.id}") }
)
}
}
}
}
}
@Composable
fun ItemCard(item: Item, onClick: () -> Unit) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable { onClick() }
) {
Row(modifier = Modifier.padding(16.dp)) {
Column(modifier = Modifier.weight(1f)) {
Text(text = item.title, style = MaterialTheme.typography.headlineSmall)
Text(text = item.description, style = MaterialTheme.typography.bodyMedium)
Text(text = "$${item.price}", style = MaterialTheme.typography.bodySmall)
}
Icon(imageVector = Icons.Default.ArrowForward, contentDescription = null)
}
}
}
@Composable
fun ProfileScreen(navController: NavController) {
val viewModel: UserViewModel = hiltViewModel()
val user by viewModel.user.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
LaunchedEffect(Unit) {
viewModel.fetchUser("current-user")
}
Scaffold(
topBar = { TopAppBar(title = { Text("Profile") }) }
) { paddingValues ->
if (isLoading) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
} else if (user != null) {
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(16.dp)
) {
Text(text = user!!.name, style = MaterialTheme.typography.headlineMedium)
Text(text = user!!.email, style = MaterialTheme.typography.bodyMedium)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { viewModel.logout() },
modifier = Modifier.fillMaxWidth()
) {
Text("Logout")
}
}
}
}
}
@Composable
fun DetailsScreen(itemId: String, navController: NavController) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Details") },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("Item ID: $itemId", style = MaterialTheme.typography.headlineSmall)
}
}
}
最佳实践
✅ DO
- 使用 Kotlin 开发所有新的 Android 代码
- 使用 Jetpack 库实现 MVVM
- 使用 Jetpack Compose 进行 UI 开发
- 利用协程进行异步操作
- 使用 Room 进行本地数据持久化
- 实施适当的错误处理
- 使用 Hilt 进行依赖注入
- 使用 StateFlow 进行响应式状态管理
- 在多种设备类型上进行测试
- 遵循 Android 设计指南
❌ DON’T
- 不要在 SharedPreferences 中存储令牌
- 不要在主线程上进行网络调用
- 忽略生命周期管理
- 跳过空安全检查
- 硬编码字符串和资源
- 忽略配置更改
- 在代码中存储密码
- 未经设备测试就部署
- 使用过时的 API
- 累积内存泄漏