如何在Jetpack Compose中展示API请求结果并存储到数据库
问题解决方案与优化建议
1. 修复接口调用(替换runBlocking,正确使用LaunchedEffect)
runBlocking会阻塞主线程,绝对不能在Compose UI线程中使用,必须用LaunchedEffect配合协程安全的网络调用逻辑。
修正Network.kt的挂起函数实现
确保fetchMenu是挂起函数,用协程友好的网络库扩展:
// Network.kt suspend fun fetchMenu(): List<MenuItem> { val client = OkHttpClient() val request = Request.Builder() .url("https://your-little-lemon-api-url.com/menu") .build() val response = client.newCall(request).await() if (!response.isSuccessful) throw IOException("请求失败: ${response.code}") val json = response.body?.string() ?: return emptyList() // 用Gson/Moshi解析JSON到MenuItem列表 return Gson().fromJson(json, Array<MenuItem>::class.java).toList() }
在Home.kt中正确触发请求
用LaunchedEffect在Composable首次渲染时发起请求,同时依赖ViewModel处理业务逻辑:
// Home.kt @Composable fun HomeScreen(menuViewModel: MenuViewModel) { val menuItems by menuViewModel.menuItems.collectAsState() val isLoading by menuViewModel.isLoading.collectAsState() // 首次进入页面触发一次请求 LaunchedEffect(Unit) { menuViewModel.fetchAndStoreMenu() } when { isLoading -> CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) menuItems.isEmpty() -> Text("暂无菜单数据", modifier = Modifier.padding(16.dp)) else -> MenuList(menuItems) } }
2. 实现Room数据库存储逻辑
确保Database.kt的实体与DAO定义正确
// Database.kt @Entity(tableName = "menu_items") data class MenuItem( @PrimaryKey val id: Int, val name: String, val description: String, val price: Double, val imageUrl: String ) @Dao interface MenuDao { @Query("SELECT * FROM menu_items") fun getAllMenuItems(): Flow<List<MenuItem>> @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertAll(items: List<MenuItem>) } @Database(entities = [MenuItem::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun menuDao(): MenuDao companion object { @Volatile private var INSTANCE: AppDatabase? = null fun getInstance(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "little_lemon_db" ).build() INSTANCE = instance instance } } } }
用ViewModel封装业务逻辑(解耦UI与数据层)
创建MenuViewModel统一处理网络请求、数据库存储与数据分发:
// MenuViewModel.kt class MenuViewModel(private val menuDao: MenuDao) : ViewModel() { private val _isLoading = MutableStateFlow(false) val isLoading = _isLoading.asStateFlow() private val _menuItems = MutableStateFlow<List<MenuItem>>(emptyList()) val menuItems = _menuItems.asStateFlow() init { // 先从本地数据库加载缓存数据 viewModelScope.launch { menuDao.getAllMenuItems().collect { items -> _menuItems.value = items } } } fun fetchAndStoreMenu() { viewModelScope.launch { _isLoading.value = true try { val remoteItems = fetchMenu() menuDao.insertAll(remoteItems) // 数据库更新后会自动通过Flow同步到menuItems } catch (e: Exception) { Log.e("MenuViewModel", "加载菜单失败: ${e.message}") } finally { _isLoading.value = false } } } }
3. Jetpack Compose界面展示(匹配示例样式)
实现菜单列表与列表项
// Home.kt @Composable fun MenuList(items: List<MenuItem>) { LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { items(items) { item -> MenuItemCard(item) } } } @Composable fun MenuItemCard(item: MenuItem) { Card( elevation = CardDefaults.cardElevation(4.dp), shape = RoundedCornerShape(8.dp), modifier = Modifier.fillMaxWidth() ) { Row( modifier = Modifier .fillMaxWidth() .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { Text( text = item.name, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold ) Text( text = item.description, style = MaterialTheme.typography.bodyMedium, color = Color.Gray, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(top = 4.dp) ) Text( text = "$${item.price}", style = MaterialTheme.typography.titleSmall, color = Color(0xFF4CAF50), modifier = Modifier.padding(top = 8.dp) ) } AsyncImage( model = item.imageUrl, contentDescription = item.name, modifier = Modifier .size(80.dp) .clip(RoundedCornerShape(8.dp)), contentScale = ContentScale.Crop, placeholder = painterResource(R.drawable.placeholder) ) } } }
4. Jetpack Compose新手优化建议
- 禁用
runBlocking:绝对不要在UI线程使用,所有异步操作用LaunchedEffect(UI层)或viewModelScope(ViewModel层)。 - 用ViewModel做数据中转:Composable只负责展示UI,网络、数据库等业务逻辑全部放在ViewModel中,避免UI层代码臃肿。
- 优先用Flow观察数据:Room的Flow会自动监听数据库变化,配合
collectAsState()实现数据驱动UI,无需手动刷新。 - 添加错误处理:网络请求必须加try-catch,可额外添加错误提示UI(比如重试按钮)提升用户体验。
- 遵循状态管理规范:用
StateFlow+collectAsState()管理UI状态,避免用可变变量直接控制UI。 - 尝试依赖注入:用Hilt/Koin管理ViewModel、Database等实例,避免手动创建导致的内存泄漏或重复实例问题。
内容的提问来源于stack exchange,提问作者Ishan Choudhary
相关产品推荐
相关产品推荐

