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

Android Compose中异步任务的最佳处理方式咨询

最佳实现方案:将异步逻辑与状态管理移至ViewModel

核心思路

  • 把加载状态切换、异步任务(含延迟逻辑)全部封装到ViewModel的协程函数中,UI层仅负责触发动作和响应状态/事件
  • 用**一次性事件流(SharedFlow)**通知UI任务完成,避免重复触发回调
  • 严格遵循单向数据流:UI向ViewModel发送指令,ViewModel处理逻辑并更新状态/发送事件,UI监听状态/事件做出响应

代码重构实现

1. 重构ViewModel

import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch

class AddTodoViewModel : ViewModel() {
    // 私有可变状态,对外暴露只读版本,符合单向数据流原则
    private val _state = mutableStateOf(AddTodoState())
    val state = _state

    // 用于发送任务完成的一次性事件
    private val _onTaskCompleted = MutableSharedFlow<Todo>()
    val onTaskCompleted: SharedFlow<Todo> = _onTaskCompleted

    fun saveTodo() {
        viewModelScope.launch {
            // 开启加载状态
            _state.value = _state.value.copy(loading = true)
            
            // 执行异步任务(包含延迟逻辑,可替换为实际业务操作)
            doTask()
            
            // 关闭加载状态
            _state.value = _state.value.copy(loading = false)
            
            // 组装Todo对象并发送完成事件
            val todo = Todo(
                title = _state.value.title,
                date = _state.value.date,
                type = _state.value.category,
                time = _state.value.time,
                notes = _state.value.notes,
                isChecked = false
            )
            _onTaskCompleted.emit(todo)
        }
    }

    private suspend fun doTask() {
        delay(1000L)
        // 此处可替换为实际业务逻辑:比如网络请求、数据库存储等
    }

    // 示例:状态修改方法(保持原有逻辑即可,按需添加)
    fun updateTitle(newTitle: String) {
        _state.value = _state.value.copy(title = newTitle)
    }
}

// 状态类改为全val,确保不可变,仅通过copy修改
data class AddTodoState(
    val title: String = "",
    val notes: String = "",
    val date: String = "",
    val time: String = "",
    val category: Constants? = null,
    val loading: Boolean = false
)

2. 重构UI组件(Button部分)

// PRIMARY BUTTON
Button(
    modifier = Modifier
        .padding(bottom = 24.dp, start = 16.dp, end = 16.dp)
        .navigationBarsPadding()
        .fillMaxWidth(),
    contentPadding = PaddingValues(vertical = 16.dp),
    onClick = { viewModel.saveTodo() },
    // 加载状态下禁用按钮,防止重复点击
    enabled = !viewModel.state.value.loading
) {
    // 加载时显示进度条,否则显示Save文本
    if (viewModel.state.value.loading) {
        CircularProgressIndicator(
            modifier = Modifier.size(20.dp),
            color = MaterialTheme.colorScheme.surface,
            strokeWidth = 2.dp
        )
    } else {
        Text(
            text = "Save",
            style = MaterialTheme.typography.bodyLarge,
            color = MaterialTheme.colorScheme.surface,
        )
    }
}

// 在Composable顶层收集任务完成事件
LaunchedEffect(Unit) {
    viewModel.onTaskCompleted.collect { todo ->
        onSave.invoke(todo)
        onPop.invoke()
    }
}

3. 调用层代码(无需修改,保留原有逻辑)

AddTodo(viewModel = viewModel, onPop = {
        this.finish()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            overrideActivityTransition(
                OVERRIDE_TRANSITION_OPEN,
                R.anim.slide_out_top,
                R.anim.slide_in_top
            )
        } else {
            overridePendingTransition(
                R.anim.slide_out_top,
                R.anim.slide_in_top
            )
        }
    }, onSave = { todo ->
        val gson = Gson()
        val intent = Intent().apply {
            putExtra(
                "model",
                gson.toJson(todo)
            )
        }
        setResult(RESULT_OK, intent)
        this.finish()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            overrideActivityTransition(
                OVERRIDE_TRANSITION_OPEN,
                R.anim.slide_out_top,
                R.anim.slide_in_top
            )
        } else {
            overridePendingTransition(
                R.anim.slide_out_top,
                R.anim.slide_in_top
            )
        }
    })

方案优势

  • 职责清晰:UI层仅负责渲染和触发动作,ViewModel处理所有业务逻辑与状态管理
  • 避免重复代码:加载状态切换逻辑仅在ViewModel中实现一次
  • 防止误操作:加载状态下禁用按钮,避免用户重复触发任务
  • 事件安全:用SharedFlow发送一次性事件,确保任务完成后仅回调一次onSave与onPop
  • 符合MVVM规范:严格单向数据流,状态不可变,便于调试与维护

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 09:34:53