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

如何用Android架构组件实现登录活动架构及POST请求方案?

用Android架构组件实现登录活动的命令式方案

嗨,我来帮你梳理下用Android架构组件实现登录功能的思路和代码示例,刚好你已经掌握了ViewModel、Repository和Room的基础用法,咱们直接针对登录的POST请求和状态管理展开:

核心问题解答:isAuthorized LiveData该放哪?

简单说:isAuthorized的状态应该放在ViewModel中维护,但登录请求的执行和本地状态的读取交给Repository。

Repository的职责是处理数据来源(网络+本地),比如执行POST登录请求、保存/读取登录凭证;而ViewModel作为UI和数据的中间层,负责把数据状态转换成UI能直接使用的状态,比如isAuthorized就是UI需要的“是否已登录”状态,由ViewModel来暴露给UI观察。


分层实现代码示例(命令式风格)

咱们从底层到上层一步步写,保持命令式的“触发-响应”逻辑:

1. 定义网络API接口(Retrofit)

首先用Retrofit定义登录的POST接口:

interface AuthApi {
    @POST("auth/login")
    suspend fun login(@Body credentials: LoginCredentials): Response<LoginResponse>
}

// 登录请求体
data class LoginCredentials(val email: String, val password: String)
// 登录响应体
data class LoginResponse(val token: String, val userId: String)

2. 实现AuthorizationRepository

Repository负责执行登录请求、处理本地凭证存储,同时返回请求的状态(加载中/成功/失败):

class AuthorizationRepository(
    private val authApi: AuthApi,
    private val sharedPreferences: SharedPreferences
) {
    // 执行登录请求,返回带状态的LiveData
    fun login(email: String, password: String): LiveData<Resource<LoginResponse>> {
        val result = MutableLiveData<Resource<LoginResponse>>()
        result.postValue(Resource.Loading)
        
        // 用IO线程执行网络请求
        CoroutineScope(Dispatchers.IO).launch {
            try {
                val response = authApi.login(LoginCredentials(email, password))
                if (response.isSuccessful) {
                    response.body()?.let { loginRes ->
                        // 保存token到SharedPreferences
                        sharedPreferences.edit()
                            .putString("auth_token", loginRes.token)
                            .putString("user_id", loginRes.userId)
                            .apply()
                        result.postValue(Resource.Success(loginRes))
                    } ?: result.postValue(Resource.Error("Empty response from server"))
                } else {
                    result.postValue(Resource.Error("Login failed: ${response.message()}"))
                }
            } catch (e: Exception) {
                result.postValue(Resource.Error("Network error: ${e.message ?: "Unknown issue"}"))
            }
        }
        return result
    }

    // 检查本地是否有有效凭证
    fun isUserAuthorized(): Boolean {
        return sharedPreferences.getString("auth_token", null) != null
    }

    // 清除本地凭证(登出用)
    fun clearAuthToken() {
        sharedPreferences.edit().remove("auth_token").remove("user_id").apply()
    }
}

// 封装请求状态的密封类,方便UI处理不同场景
sealed class Resource<T> {
    object Loading : Resource<Nothing>()
    data class Success<T>(val data: T) : Resource<T>()
    data class Error(val message: String) : Resource<Nothing>()
}

3. 实现LoginViewModel

ViewModel负责维护UI状态(包括isAuthorized),转发Repository的请求结果给UI:

class LoginViewModel(
    private val authRepo: AuthorizationRepository
) : ViewModel() {
    // 登录请求的实时状态(加载/成功/失败)
    private val _loginStatus = MutableLiveData<Resource<LoginResponse>>()
    val loginStatus: LiveData<Resource<LoginResponse>> = _loginStatus

    // 是否已授权的状态,供UI观察
    private val _isAuthorized = MutableLiveData<Boolean>()
    val isAuthorized: LiveData<Boolean> = _isAuthorized

    init {
        // 初始化时检查本地登录状态
        _isAuthorized.value = authRepo.isUserAuthorized()
    }

    // 供UI调用的登录方法(命令式触发)
    fun triggerLogin(email: String, password: String) {
        authRepo.login(email, password).observeForever { resource ->
            _loginStatus.postValue(resource)
            // 登录成功后更新授权状态
            if (resource is Resource.Success) {
                _isAuthorized.postValue(true)
            }
        }
    }

    // 登出方法
    fun triggerLogout() {
        authRepo.clearAuthToken()
        _isAuthorized.postValue(false)
    }
}

4. 实现LoginActivity(UI层)

UI层通过观察ViewModel的状态来做出响应,触发登录操作:

class LoginActivity : AppCompatActivity() {
    private lateinit var viewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        // 初始化ViewModel(这里用工厂类创建,实际项目推荐用Hilt/Dagger注入)
        viewModel = ViewModelProvider(this, LoginViewModelFactory(
            AuthorizationRepository(
                RetrofitInstance.authApi,
                getSharedPreferences("auth_prefs", MODE_PRIVATE)
            )
        )).get(LoginViewModel::class.java)

        // 观察授权状态:如果已登录,直接跳转到主页面
        viewModel.isAuthorized.observe(this) { isLoggedIn ->
            if (isLoggedIn) {
                startActivity(Intent(this, MainActivity::class.java))
                finish()
            }
        }

        // 观察登录请求状态,更新UI反馈
        viewModel.loginStatus.observe(this) { resource ->
            when (resource) {
                is Resource.Loading -> {
                    progressBar.visibility = View.VISIBLE
                    btn_login.isEnabled = false
                }
                is Resource.Success -> {
                    progressBar.visibility = View.GONE
                    btn_login.isEnabled = true
                    Toast.makeText(this, "登录成功!", Toast.LENGTH_SHORT).show()
                }
                is Resource.Error -> {
                    progressBar.visibility = View.GONE
                    btn_login.isEnabled = true
                    Toast.makeText(this, resource.message, Toast.LENGTH_SHORT).show()
                }
            }
        }

        // 登录按钮点击事件:触发登录命令
        btn_login.setOnClickListener {
            val email = et_email.text.toString().trim()
            val password = et_password.text.toString().trim()
            if (email.isNotEmpty() && password.isNotEmpty()) {
                viewModel.triggerLogin(email, password)
            } else {
                Toast.makeText(this, "请填写完整信息", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

// ViewModel工厂类,用来创建带参数的ViewModel
class LoginViewModelFactory(private val authRepo: AuthorizationRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return LoginViewModel(authRepo) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

// Retrofit实例配置(示例)
object RetrofitInstance {
    private const val BASE_URL = "https://your-api-domain.com/"

    private val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    val authApi: AuthApi by lazy {
        retrofit.create(AuthApi::class.java)
    }
}

关键细节说明

  1. 命令式逻辑体现:UI通过点击按钮调用viewModel.triggerLogin()触发登录,ViewModel命令Repository执行请求,Repository完成后返回结果,ViewModel更新状态,UI观察状态做出响应——整个流程是“主动触发→被动响应”的命令式风格。
  2. 状态职责划分:Repository只处理数据的获取和持久化,ViewModel负责把数据状态转换成UI需要的状态(比如isAuthorized),UI只负责展示状态和触发命令。
  3. 避免内存泄漏:Repository中用CoroutineScope(Dispatchers.IO)执行网络请求,ViewModel中可结合viewModelScope管理协程,确保组件销毁时协程能被取消。

内容的提问来源于stack exchange,提问作者Алексей Мальченко

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:00:43