You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 13:13:20