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

Android Kotlin:合并Flow数据后Google Map仅显示单源标记问题求助

问题描述

代码托管在GitHub仓库,我正在开发一款地震地图App,该App从两个Web API获取地震信息,并在Google Map上显示其位置。在MainScreen.kt中,我获取数据列表并通过forEach()在地图上添加标记。

MainScreen.kt

@Composable
fun MainScreen(
    viewModel: MainViewModel = hiltViewModel()
) {
    val items = viewModel.uiState.collectAsStateWithLifecycle()

    if (items.value is MainActivityUiState.Success) {
        MainScreen((items.value as MainActivityUiState.Success).earthquakeData)
    } else {
        val noItemList = emptyList<EarthquakeInfo>()
        MainScreen(noItemList)
    }
}

@Composable
internal fun MainScreen(
    items: List<EarthquakeInfo>
) {
    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        Column {
            val tokyo = LatLng(35.6812, 139.7671)
            val cameraPositionState = rememberCameraPositionState {
                position = CameraPosition.fromLatLngZoom(tokyo, 0f)
            }

            GoogleMap(
                modifier = Modifier.weight(1f),
                cameraPositionState = cameraPositionState
            ) {
                items.forEach {
                    Marker(
                        position = LatLng(it.latitude, it.longitude),
                        title = it.place,
                        snippet = it.place
                    )
                }
            }
            ...
        }
    }
}

MainViewModel.kt

@HiltViewModel
class MainViewModel @Inject constructor(
    usgsEarthquakeRepository: UsgsEarthquakeRepository,
    p2pquakeRepository: P2pquakeRepository,
    @DefaultDispatcher defaultDispatcher: CoroutineDispatcher,
) : ViewModel() {
    val uiState: StateFlow<MainActivityUiState> =
        try {
            GetLatestEarthquakeInfoUseCase(
                usgsEarthquakeRepository,
                p2pquakeRepository,
            )
                .invoke()
                .flowOn(defaultDispatcher)
                .map<List<EarthquakeInfo>, MainActivityUiState>(::Success)
                .stateIn(
                    scope = viewModelScope,
                    started = SharingStarted.WhileSubscribed(5000),
                    initialValue = MainActivityUiState.Loading,
                )
        } catch (e: IOException) {
            MutableStateFlow(MainActivityUiState.Error).asStateFlow()
        }
}

sealed interface MainActivityUiState {
    object Loading: MainActivityUiState
    data class Success(val earthquakeData: List<EarthquakeInfo>): MainActivityUiState
    object Error: MainActivityUiState
}

在GetLatestEarthquakeInfoUseCase.kt中,我需要合并来自两个API的数据,但它们的数据结构不同,因此我使用map()创建EarthquakeInfo数据类并提取所需数据,随后使用merge()返回数据列表。

GetLatestEarthquakeInfoUseCase.kt

class GetLatestEarthquakeInfoUseCase @Inject constructor(
    private val usgsEarthquakeRepository: UsgsEarthquakeRepository,
    private val p2pquakeRepository: P2pquakeRepository,
) {
    operator fun invoke(): Flow<List<EarthquakeInfo>> {
        val usgsEarthquakeFlow =
            usgsEarthquakeRepository
                .getInfo("geojson", 20, "time")
                .map { usgsEarthquakeInfo ->
                    usgsEarthquakeInfo.features
                        .map { feature ->
                        EarthquakeInfo(
                            feature.properties.getDatetime(),
                            feature.properties.place ?: "",
                            feature.geometry.coordinates[1],
                            feature.geometry.coordinates[0],
                            feature.properties.mag,
                            feature.geometry.getDepth(),
                        )
                    }
                }

        val p2pEarthquakeFlow =
            p2pquakeRepository
                .getInfo(10, 0)
                .map { p2pquakeInfo ->
                    p2pquakeInfo.map { data ->
                        EarthquakeInfo(
                            data.earthquake.getDatetime(),
                            data.earthquake.hypocenter.name,
                            data.earthquake.hypocenter.latitude,
                            data.earthquake.hypocenter.longitude,
                            data.earthquake.hypocenter.magnitude,
                            data.earthquake.hypocenter.depth,
                        )
                    }
                }

        return merge(usgsEarthquakeFlow, p2pEarthquakeFlow)
    }
}

运行代码时,p2pEarthquakeFlow的标记会显示约1秒后消失,最终仅显示usgsEarthquakeFlow的标记。我希望同时显示两个来源的标记,请求帮助解决该问题。

我是Android Kotlin新手,英语是我的第二语言,若有表述错误请告知,我会修正。


解决方案

问题出在merge操作符的使用逻辑上:merge会分别发射两个Flow的结果——先发射p2p的地震列表,再发射usgs的列表,而UI每次只会接收最新的列表并渲染,所以当usgs的数据返回后,UI就会替换成usgs的标记,p2p的标记自然消失。

要解决这个问题,需要把两个Flow的结果合并成一个完整列表后再发射,而非分别发射。可以使用combine操作符实现:

1. 修改GetLatestEarthquakeInfoUseCase的合并逻辑

operator fun invoke(): Flow<List<EarthquakeInfo>> {
    val usgsEarthquakeFlow =
        usgsEarthquakeRepository
            .getInfo("geojson", 20, "time")
            .map { usgsEarthquakeInfo ->
                usgsEarthquakeInfo.features
                    .map { feature ->
                    EarthquakeInfo(
                        feature.properties.getDatetime(),
                        feature.properties.place ?: "",
                        feature.geometry.coordinates[1],
                        feature.geometry.coordinates[0],
                        feature.properties.mag,
                        feature.geometry.getDepth(),
                    )
                }
            }

    val p2pEarthquakeFlow =
        p2pquakeRepository
            .getInfo(10, 0)
            .map { p2pquakeInfo ->
                p2pquakeInfo.map { data ->
                    EarthquakeInfo(
                        data.earthquake.getDatetime(),
                        data.earthquake.hypocenter.name,
                        data.earthquake.hypocenter.latitude,
                        data.earthquake.hypocenter.longitude,
                        data.earthquake.hypocenter.magnitude,
                        data.earthquake.hypocenter.depth,
                    )
                }
            }

    // 使用combine合并两个列表,拼接成一个整体列表发射
    return combine(usgsEarthquakeFlow, p2pEarthquakeFlow) { usgsList, p2pList ->
        usgsList + p2pList
    }
}

2. 可选:添加去重逻辑(避免重复标记)

如果两个API可能返回同一地震的重复数据,可以通过distinctBy根据地震的时间、经纬度等唯一标识去重:

return combine(usgsEarthquakeFlow, p2pEarthquakeFlow) { usgsList, p2pList ->
    (usgsList + p2pList).distinctBy { it.datetime to it.latitude to it.longitude }
}

3. 修复ViewModel的异常捕获逻辑

当前的try-catch仅能捕获UseCase创建时的异常,无法处理API请求过程中的异常。需要在Flow链中添加catch操作符:

val uiState: StateFlow<MainActivityUiState> =
    GetLatestEarthquakeInfoUseCase(
        usgsEarthquakeRepository,
        p2pquakeRepository,
    )
        .invoke()
        .flowOn(defaultDispatcher)
        .map<List<EarthquakeInfo>, MainActivityUiState>(::Success)
        .catch { emit(MainActivityUiState.Error) } // 捕获Flow发射过程中的异常
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = MainActivityUiState.Loading,
        )

修改完成后,两个API的数据会合并成一个完整列表发射,UI就能同时显示所有来源的标记了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 01:13:11