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

Android:如何实现点击按钮后才开始收集Flow数据流?

解决方案

核心思路是将按钮点击事件与认证请求Flow绑定,仅在用户触发点击后才启动认证流程并更新UI状态,初始状态保持按钮的正常文本显示。

1. ViewModel层处理事件与状态

首先在ViewModel中定义用户操作意图、事件接收流,以及整合后的UI状态流:

// 定义用户操作意图
sealed class AuthIntent {
    data class Login(val email: String, val password: String) : AuthIntent()
    data class SignUp(val email: String, val password: String) : AuthIntent()
}

// 定义UI状态,初始为Idle
sealed class AuthUiState {
    object Idle : AuthUiState()
    object Loading : AuthUiState()
    object Success : AuthUiState()
    data class Error(val message: String) : AuthUiState()
}

class LoginViewModel @Inject constructor(private val auth: FirebaseAuth) : ViewModel() {
    // 用于接收UI发送的点击事件
    private val _authIntent = MutableSharedFlow<AuthIntent>()
    val authIntent = _authIntent.asSharedFlow()

    // 整合后的UI状态流
    val uiState: StateFlow<AuthUiState> = authIntent
        .flatMapLatest { intent ->
            flow {
                emit(AuthUiState.Loading)
                try {
                    when (intent) {
                        is AuthIntent.Login -> {
                            auth.signInWithEmailAndPassword(intent.email, intent.password).await()
                        }
                        is AuthIntent.SignUp -> {
                            auth.createUserWithEmailAndPassword(intent.email, intent.password).await()
                        }
                    }
                    emit(AuthUiState.Success)
                } catch (e: Exception) {
                    emit(AuthUiState.Error(e.localizedMessage ?: "Unknown error"))
                }
            }
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = AuthUiState.Idle
        )

    // UI调用此方法发送点击事件
    fun sendAuthIntent(intent: AuthIntent) {
        viewModelScope.launch {
            _authIntent.emit(intent)
        }
    }
}

2. UI层实现交互与状态收集

在Compose界面中,点击按钮时发送事件,根据uiState更新按钮显示:

@Composable
LoginScreen(
    modifier: Modifier = Modifier,
    viewModel: LoginViewModel = hiltViewModel()
) {
    val uiState by viewModel.uiState.collectAsState()
    var email by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }

    Column(modifier = modifier.padding(16.dp)) {
        // 邮箱输入框
        TextField(
            value = email,
            onValueChange = { email = it },
            label = { Text("Email") }
        )
        // 密码输入框
        TextField(
            value = password,
            onValueChange = { password = it },
            label = { Text("Password") },
            visualTransformation = PasswordVisualTransformation()
        )

        // 登录按钮
        Button(
            onClick = {
                viewModel.sendAuthIntent(AuthIntent.Login(email, password))
            },
            modifier = Modifier.fillMaxWidth().padding(top = 16.dp)
        ) {
            when (uiState) {
                is AuthUiState.Loading -> CircularProgressIndicator(modifier = Modifier.size(20.dp))
                else -> Text("Login")
            }
        }

        // 注册按钮
        Button(
            onClick = {
                viewModel.sendAuthIntent(AuthIntent.SignUp(email, password))
            },
            modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
        ) {
            when (uiState) {
                is AuthUiState.Loading -> CircularProgressIndicator(modifier = Modifier.size(20.dp))
                else -> Text("Sign up")
            }
        }

        // 错误信息展示
        (uiState as? AuthUiState.Error)?.let { errorState ->
            Text(
                text = errorState.message,
                color = Color.Red,
                modifier = Modifier.padding(top = 8.dp)
            )
        }
    }

    // 处理成功状态(比如跳转主页)
    LaunchedEffect(uiState) {
        if (uiState is AuthUiState.Success) {
            // 这里执行登录/注册成功后的操作,如导航到主页
        }
    }
}

关键说明

  • 用MutableSharedFlow接收点击事件:确保每次点击都能触发新的认证请求,且旧的请求会被flatMapLatest取消,避免多次点击导致的并发请求。
  • stateIn初始化状态为Idle:保证界面初始时显示正常按钮文本,而非加载状态。
  • LaunchedEffect处理成功状态:避免在Compose重组中重复执行跳转逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 13:37:51