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

Composable初始组合时调用远程API并避免无限重组的最佳实践

问题分析

当前Composable函数Device中,直接在组件内部调用vm.getDevices()会引发无限重组:每次调用该方法会更新uiState,触发UI重组,重组后又会再次调用vm.getDevices(),形成循环。

原始代码

Composable组件代码

@Composable
fun Device(contentPadding: PaddingValues, modifier: Modifier = Modifier) {
    val vm:DeviceList = viewModel()
    vm.getDevices()
    var devices = vm.uiState.collectAsState();

    LazyColumn(contentPadding = contentPadding) {
        items(devices.value) { device -> DeviceItem(device) }
    }
}

ViewModel代码

class DeviceList : ViewModel() {

    private var deviceListUIState: MutableStateFlow<List<Device>> = MutableStateFlow(
        listOf()
    )

    val uiState
        get() = deviceListUIState.asStateFlow()

    fun getDevices() {
        viewModelScope.launch {
            try {
                val result: List<Device> = myApi.retrofitService.getDevices()
                deviceListUIState.value = result
            } catch (e: Exception) {
                Log.e(this.toString(), e.message ?: "")
            }
        }
    }
}
最佳实践方案

以下是几种简洁优雅的解决方案,避免无限重组并符合Jetpack Compose的设计规范:

方案一:ViewModel初始化时自动加载数据

直接在ViewModel的初始化块中调用getDevices(),数据只会在ViewModel创建时加载一次,不会随UI重组重复执行。

修改后的ViewModel:

class DeviceList : ViewModel() {

    private var deviceListUIState: MutableStateFlow<List<Device>> = MutableStateFlow(
        listOf()
    )

    val uiState
        get() = deviceListUIState.asStateFlow()

    // 初始化时自动触发数据加载
    init {
        getDevices()
    }

    fun getDevices() {
        viewModelScope.launch {
            try {
                val result: List<Device> = myApi.retrofitService.getDevices()
                deviceListUIState.value = result
            } catch (e: Exception) {
                Log.e(this.toString(), e.message ?: "")
            }
        }
    }
}

对应的Composable组件移除vm.getDevices()调用:

@Composable
fun Device(contentPadding: PaddingValues, modifier: Modifier = Modifier) {
    val vm:DeviceList = viewModel()
    val devices = vm.uiState.collectAsState();

    LazyColumn(contentPadding = contentPadding) {
        items(devices.value) { device -> DeviceItem(device) }
    }
}

方案二:用LaunchedEffect触发单次加载

如果需要在Composable首次组合时才加载数据(而非ViewModel创建时),可以使用LaunchedEffect,传入固定不变的键(如Unit),确保代码仅执行一次。

修改后的Composable组件:

@Composable
fun Device(contentPadding: PaddingValues, modifier: Modifier = Modifier) {
    val vm:DeviceList = viewModel()
    val devices = vm.uiState.collectAsState();

    // 仅在组件首次组合时执行一次数据加载
    LaunchedEffect(Unit) {
        vm.getDevices()
    }

    LazyColumn(contentPadding = contentPadding) {
        items(devices.value) { device -> DeviceItem(device) }
    }
}

方案三:添加加载状态控制(支持手动刷新)

如果需要支持手动刷新,同时避免重复请求,可以在ViewModel中维护加载状态,确保同一时间只有一个请求在执行。

修改后的ViewModel:

class DeviceList : ViewModel() {

    private var deviceListUIState: MutableStateFlow<List<Device>> = MutableStateFlow(
        listOf()
    )
    // 维护加载状态,防止重复请求
    private val _isLoading = MutableStateFlow(false)
    val isLoading = _isLoading.asStateFlow()

    val uiState
        get() = deviceListUIState.asStateFlow()

    fun getDevices() {
        // 正在加载时直接返回,避免重复触发
        if (_isLoading.value) return
        
        viewModelScope.launch {
            _isLoading.value = true
            try {
                val result: List<Device> = myApi.retrofitService.getDevices()
                deviceListUIState.value = result
            } catch (e: Exception) {
                Log.e(this.toString(), e.message ?: "")
            } finally {
                _isLoading.value = false
            }
        }
    }
}

对应的Composable组件:

@Composable
fun Device(contentPadding: PaddingValues, modifier: Modifier = Modifier) {
    val vm:DeviceList = viewModel()
    val devices = vm.uiState.collectAsState()
    val isLoading = vm.isLoading.collectAsState()

    // 首次组合加载数据
    LaunchedEffect(Unit) {
        vm.getDevices()
    }

    Column(modifier = modifier) {
        // 显示加载状态
        if (isLoading.value) {
            CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
        }
        LazyColumn(contentPadding = contentPadding) {
            items(devices.value) { device -> DeviceItem(device) }
        }
        // 手动刷新按钮
        Button(
            onClick = { vm.getDevices() },
            modifier = Modifier.align(Alignment.CenterHorizontally)
        ) {
            Text("刷新设备列表")
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 20:37:41