如何用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) } }
关键细节说明
- 命令式逻辑体现:UI通过点击按钮调用
viewModel.triggerLogin()触发登录,ViewModel命令Repository执行请求,Repository完成后返回结果,ViewModel更新状态,UI观察状态做出响应——整个流程是“主动触发→被动响应”的命令式风格。 - 状态职责划分:Repository只处理数据的获取和持久化,ViewModel负责把数据状态转换成UI需要的状态(比如
isAuthorized),UI只负责展示状态和触发命令。 - 避免内存泄漏:Repository中用
CoroutineScope(Dispatchers.IO)执行网络请求,ViewModel中可结合viewModelScope管理协程,确保组件销毁时协程能被取消。
内容的提问来源于stack exchange,提问作者Алексей Мальченко
相关产品推荐
相关产品推荐

