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

如何在Composable中查询Room数据库获取收藏航班的机场详情

问题描述

我开发了一个基于Room与Compose的应用,当前在一个Composable组件中需要从数据库获取数据渲染界面。该组件展示收藏航班列表,但收藏项仅存储起飞机场和目的地机场的IATA编码,需要从Airport表查询每个机场的完整数据来渲染,且要批量处理多个条目。

我原本考虑在ViewModel中编写查询函数,但发现该函数返回Flow而非实体对象实例,同时当前Composable未持有ViewModel引用,无法调用其函数。

相关代码如下:

数据库实体类

@Entity
data class Airport(
    @PrimaryKey
    val id: Int,
    @ColumnInfo(name = "iata_code")
    val iataCode: String,
    val name: String,
    val passengers: Int
)

@Entity
data class Favorite(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    @ColumnInfo(name = "departure_code")
    val departureCode: String,
    @ColumnInfo(name = "destination_code")
    val destinationCode: String
)

Composable代码

LazyColumn(
    modifier = Modifier.padding(8.dp)
) {
    items(uiState.favorites) { favorite ->
        // TODO:需要通过IATA编码获取两个机场的数据,拿到后渲染收藏航班的详细信息
    }
}

ViewModel中的查询函数

fun getAirportByIata(iata: String): Flow<Airport> {
    return flightRepository.getAirport(iata)
}

解决方案

方案一:在ViewModel层预组合完整的收藏航班数据(推荐)

核心逻辑是把数据组合的工作放在ViewModel/Repository层,让Composable只专注于渲染UI,避免在UI层发起数据库查询。

  1. 定义UI专用数据类
    创建包含收藏项及对应机场信息的UI模型,方便直接渲染:
data class FavoriteFlightUiModel(
    val favoriteId: Int,
    val departureAirport: Airport,
    val destinationAirport: Airport
)
  1. 实现批量查询与数据组合
    先在Dao层添加批量查询机场的方法:
@Query("SELECT * FROM Airport WHERE iata_code IN (:iataCodes)")
fun getAirportsByIataCodes(iataCodes: List<String>): Flow<List<Airport>>

Repository层暴露该方法:

fun getAirportsByIataCodes(iataCodes: List<String>): Flow<List<Airport>> {
    return airportDao.getAirportsByIataCodes(iataCodes)
}

然后在ViewModel中,通过combine函数关联收藏列表和机场数据,组合成UI模型:

private val _uiState = MutableStateFlow(FavoriteUiState())
val uiState: StateFlow<FavoriteUiState> = _uiState.asStateFlow()

init {
    combine(
        flightRepository.getFavorites(), // 假设这是获取收藏列表的Flow
        flightRepository.getFavorites()
            .map { favorites ->
                favorites.flatMap { listOf(it.departureCode, it.destinationCode) }
            }
            .flatMapLatest { iataCodes ->
                flightRepository.getAirportsByIataCodes(iataCodes)
            }
    ) { favorites, airports ->
        val airportMap = airports.associateBy { it.iataCode }
        val favoriteFlightUiModels = favorites.map { favorite ->
            FavoriteFlightUiModel(
                favoriteId = favorite.id,
                departureAirport = airportMap[favorite.departureCode] ?: Airport(0, favorite.departureCode, "未知机场", 0),
                destinationAirport = airportMap[favorite.destinationCode] ?: Airport(0, favorite.destinationCode, "未知机场", 0)
            )
        }
        _uiState.update { it.copy(favoriteFlights = favoriteFlightUiModels) }
    }.launchIn(viewModelScope)
}

// 对应的UI状态类
data class FavoriteUiState(
    val favoriteFlights: List<FavoriteFlightUiModel> = emptyList()
)
  1. Composable直接渲染组合后的数据
    现在Composable可以直接使用预处理好的UI模型,无需单独查询:
LazyColumn(
    modifier = Modifier.padding(8.dp)
) {
    items(uiState.favoriteFlights) { flight ->
        Column(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
            Text("出发地:${flight.departureAirport.name} (${flight.departureAirport.iataCode})")
            Text("目的地:${flight.destinationAirport.name} (${flight.destinationAirport.iataCode})")
        }
    }
}

方案二:在Composable中获取ViewModel并单独查询(不推荐,仅应急)

如果暂时无法修改ViewModel层,可以让Composable通过viewModel()获取引用,在列表项中单独收集Flow:

// 在Composable中获取ViewModel实例
val viewModel: FlightViewModel = viewModel()

LazyColumn(
    modifier = Modifier.padding(8.dp)
) {
    items(uiState.favorites) { favorite ->
        // 收集起飞机场数据
        val departureAirport by viewModel.getAirportByIata(favorite.departureCode)
            .collectAsStateWithLifecycle(
                initialValue = Airport(0, favorite.departureCode, "加载中...", 0)
            )
        // 收集目的地机场数据
        val destinationAirport by viewModel.getAirportByIata(favorite.destinationCode)
            .collectAsStateWithLifecycle(
                initialValue = Airport(0, favorite.destinationCode, "加载中...", 0)
            )

        Column(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
            Text("出发地:${departureAirport.name} (${departureAirport.iataCode})")
            Text("目的地:${destinationAirport.name} (${destinationAirport.iataCode})")
        }
    }
}

⚠️ 注意:该方案会为每个列表项发起单独的数据库查询,当收藏列表数量较多时,会导致大量重复IO,性能较差,仅适合临时场景。

关键注意事项

  • 遵循单一数据源原则,数据处理逻辑尽量放在ViewModel/Repository层,Composable只负责渲染。
  • 使用collectAsStateWithLifecycle()替代collectAsState(),确保组件非活跃时停止收集Flow,避免内存泄漏。
  • 批量查询时使用Room的IN语句,一次性获取多个机场数据,减少数据库访问次数。

内容的提问来源于stack exchange,提问作者NullPointerException

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 12:43:17