如何在Compose中将StartActivityResult回调转为协程线性风格?
解决方案:Compose中结合协程实现线性风格获取Activity返回结果
核心思路是将挂起逻辑封装在ViewModel中(ViewModel支持协程),同时遵守registerForActivityResult/rememberLauncherForActivityResult的初始化规范,通过状态流(StateFlow)连接Activity结果回调与协程挂起逻辑,最终在Compose中通过协程Scope触发调用,实现线性代码风格。
方案一:Activity中初始化Launcher(符合规范)
1. Activity层:无条件初始化Launcher
class MainActivity : ComponentActivity() { // 符合规范:在Activity字段初始化阶段无条件注册Launcher private val otherAppLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> viewModel.onOtherAppResult(result) } private val viewModel by viewModels<MainViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyAppTheme { MainScreen( viewModel = viewModel, launchOtherApp = { intent -> otherAppLauncher.launch(intent) } ) } } } }
2. ViewModel层:封装挂起函数实现线性等待
class MainViewModel : ViewModel() { private val resultFlow = MutableStateFlow<ActivityResult?>(null) /** * 挂起函数:启动外部App并等待返回结果 * @param launchIntent 启动外部App的Intent * @param launchAction 实际触发启动的lambda(由Activity层提供) */ suspend fun launchOtherAppAndWaitResult( launchIntent: Intent, launchAction: (Intent) -> Unit ): ActivityResult { // 清空历史结果,避免干扰 resultFlow.value = null // 触发外部App启动 launchAction(launchIntent) // 挂起等待结果,实现线性代码 return suspendCancellableCoroutine { cont -> val collectJob = viewModelScope.launch { resultFlow.filterNotNull().collect { result -> cont.resume(result) } } // 协程取消时清理收集任务 cont.invokeOnCancellation { collectJob.cancel() } } } // 接收Activity返回的结果并发送到流中 fun onOtherAppResult(result: ActivityResult) { resultFlow.value = result } }
3. Compose层:触发协程调用
@Composable fun MainScreen( viewModel: MainViewModel, launchOtherApp: (Intent) -> Unit ) { // 获取Compose生命周期绑定的协程Scope val coroutineScope = rememberCoroutineScope() Button(onClick = { coroutineScope.launch { // 线性代码风格:启动App -> 等待结果 -> 处理结果 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("your_app_scheme://target")) val result = viewModel.launchOtherAppAndWaitResult(intent, launchOtherApp) // 处理返回结果 if (result.resultCode == Activity.RESULT_OK) { val data = result.data // 业务逻辑处理,如解析返回数据 } } }) { Text("启动外部App") } }
方案二:Compose中直接使用rememberLauncherForActivityResult(需遵守初始化规范)
如果希望完全在Compose层处理Launcher,需确保rememberLauncherForActivityResult在Composable顶层无条件调用(符合初始化规范):
@Composable fun MainScreen(viewModel: MainViewModel) { val resultFlow = remember { MutableStateFlow<ActivityResult?>(null) } // 符合规范:无条件在Composable顶层注册Launcher val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> resultFlow.value = result } val coroutineScope = rememberCoroutineScope() Button(onClick = { coroutineScope.launch { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("your_app_scheme://target")) val result = viewModel.waitForActivityResult(resultFlow) { launcher.launch(intent) } // 处理返回结果 if (result.resultCode == Activity.RESULT_OK) { // ... } } }) { Text("启动外部App") } } // ViewModel中对应的挂起函数 fun MainViewModel.waitForActivityResult( resultFlow: StateFlow<ActivityResult?>, launchAction: () -> Unit ): ActivityResult = suspendCancellableCoroutine { cont -> resultFlow.value = null launchAction() val collectJob = viewModelScope.launch { resultFlow.filterNotNull().collect { result -> cont.resume(result) } } cont.invokeOnCancellation { collectJob.cancel() } }
关键注意点
- 可组合函数不能挂起:必须通过
rememberCoroutineScope或LaunchedEffect在协程中调用挂起函数。 - Launcher初始化规范:
registerForActivityResult/rememberLauncherForActivityResult必须在初始化路径(Activity字段、Composable顶层)无条件调用,不能放在if、LaunchedEffect等条件分支内。 - 协程安全:通过
viewModelScope管理协程生命周期,避免内存泄漏;协程取消时及时清理结果收集任务。
内容的提问来源于stack exchange,提问作者Timothy
相关产品推荐
相关产品推荐

