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

如何将Google登录逻辑迁移至Jetpack Compose的LoginScreen并集成导航?

解决方案:将Google登录逻辑迁移至Jetpack Compose可组合项并集成导航

核心思路

把登录业务逻辑抽离到ViewModel中,使用Compose专属的rememberLauncherForActivityResult替代Activity级别的registerForActivityResult,通过Navigation组件实现页面跳转,彻底解耦UI与业务逻辑。


步骤1:创建LoginViewModel处理业务逻辑

ViewModel负责管理登录状态、处理Google登录/登出逻辑、持有GoogleSignInClient,避免直接在可组合项中依赖Activity上下文。

import android.app.Application
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.api.ApiException
import kotlinx.coroutines.launch

class LoginViewModel(application: Application) : AndroidViewModel(application) {
    // 维护当前用户名状态
    val username = MutableLiveData<String>("anonymous")
    private val appContext = getApplication<Application>().applicationContext

    // 初始化GoogleSignInClient
    val googleSignInClient: GoogleSignInClient by lazy {
        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestEmail()
            .build()
        GoogleSignIn.getClient(appContext, gso)
    }

    // 处理登录结果
    fun handleSignInResult(task: com.google.android.gms.tasks.Task<com.google.android.gms.auth.api.signin.GoogleSignInAccount>) {
        viewModelScope.launch {
            try {
                val account = task.getResult(ApiException::class.java)
                username.postValue(account.displayName ?: "未知用户")
                Toast.makeText(appContext, "登录成功", Toast.LENGTH_SHORT).show()
            } catch (e: ApiException) {
                Toast.makeText(appContext, "登录失败:${e.statusCode}", Toast.LENGTH_SHORT).show()
                username.postValue("anonymous")
            }
        }
    }

    // 执行登出操作
    fun signOut() {
        googleSignInClient.signOut()
            .addOnCompleteListener {
                username.postValue("anonymous")
                Toast.makeText(appContext, "登出成功", Toast.LENGTH_SHORT).show()
            }
    }

    // 检查当前已登录用户
    fun checkCurrentUser() {
        val account = GoogleSignIn.getLastSignedInAccount(appContext)
        account?.let {
            username.postValue(it.displayName ?: "未知用户")
        } ?: run {
            username.postValue("anonymous")
        }
    }
}

步骤2:编写LoginScreen可组合项

在可组合项中使用rememberLauncherForActivityResult注册登录回调,接收NavController实现导航,仅负责UI渲染和触发操作。

import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.google.android.gms.auth.api.signin.GoogleSignIn
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts

@Composable
fun LoginScreen(navController: NavController, viewModel: LoginViewModel = viewModel()) {
    val username by viewModel.username.collectAsState()
    val context = LocalContext.current

    // 在Compose中注册Activity结果回调
    val signInLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
        viewModel.handleSignInResult(task)
        // 登录成功后跳转到主页,清除登录页栈
        if (GoogleSignIn.getLastSignedInAccount(context) != null) {
            navController.navigate("/home") {
                popUpTo("/login") { inclusive = true }
            }
        }
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        // Google登录按钮
        Button(
            onClick = {
                val signInIntent = viewModel.googleSignInClient.signInIntent
                signInLauncher.launch(signInIntent)
            },
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp, vertical = 10.dp),
            colors = ButtonDefaults.buttonColors(
                containerColor = Color.Black,
                contentColor = Color.White
            )
        ) {
            Image(
                painter = painterResource(id = R.drawable.icons8_google),
                contentDescription = "Google图标"
            )
            Text(text = "使用Google登录", modifier = Modifier.padding(start = 6.dp))
        }

        // 登出按钮
        Button(
            onClick = { viewModel.signOut() },
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp),
            colors = ButtonDefaults.buttonColors(
                containerColor = Color.Black,
                contentColor = Color.White
            )
        ) {
            Image(
                painter = painterResource(id = R.drawable.icons8_google),
                contentDescription = "Google图标"
            )
            Text(text = "登出", modifier = Modifier.padding(start = 6.dp))
        }

        Spacer(modifier = Modifier.height(24.dp))
        Text(text = "当前用户:$username", fontSize = 30.sp)
    }
}

步骤3:修改MainActivity集成Navigation

将Activity简化为导航容器,初始化NavHost并定义页面路由。

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MeauComposeTheme {
                val navController = rememberNavController()
                NavHost(navController = navController, startDestination = "/login") {
                    // 登录页路由
                    composable("/login") {
                        LoginScreen(navController = navController)
                    }
                    // 主页路由
                    composable("/home") {
                        HomeScreen()
                    }
                }
            }
        }
    }

    override fun onStart() {
        super.onStart()
        // 检查当前已登录用户状态
        val viewModel = viewModel<LoginViewModel>()
        viewModel.checkCurrentUser()
    }
}

// 示例主页可组合项
@Composable
fun HomeScreen() {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(text = "欢迎来到主页!", fontSize = 30.sp)
    }
}

关键注意事项

  1. 依赖配置:确保项目中已添加Compose Navigation和ViewModel依赖:
    implementation "androidx.navigation:navigation-compose:2.7.6"
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2"
    
  2. 上下文安全:ViewModel使用Application Context避免内存泄漏,Compose中通过LocalContext.current获取临时上下文。
  3. 路由管理:使用popUpTo清除登录页栈,防止用户通过返回键回到登录页。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 14:16:47