如何在 MVVM 架构中使用 livedata
How to use livedata in an MVVM architecture
TLDR:如何使用 LiveData 正确实现 MVVM 架构?
我有一个片段 class 观察 viewModel 公开的实时数据:
viewModel.loginResultLiveData.observe
class LoginFragment : Fragment() {
private lateinit var binding: FragmentLoginBinding
private val viewModel by fragmentScopedViewModel { injector.loginViewModel }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentLoginBinding.inflate(inflater, container, false)
val username = binding.loginInputField.toString()
val password = binding.passwordInputField.toString()
binding.loginSignInButton.setOnClickListener { viewModel.login(
username,
password
) }
viewModel.loginResultLiveData.observe(viewLifecycleOwner){
when(it){
is LoginResult.Success -> doSmth()
}
}
return binding.root
}
}
查看模型class 只需请求一个映射的实时数据对象。
class LoginViewModel @Inject internal constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
lateinit var loginResultLiveData: MutableLiveData<LoginResult>
fun login(username: String, password: String) {
loginResultLiveData = loginUseCase.login(username, password)
}
}
模型使用用例,从原始格式映射结果,也会映射错误:
class LoginUseCase @Inject internal constructor(
private val authRepository: AuthEmailRepository
) {
var loginResultLiveData = MutableLiveData<LoginResult>()
fun login(userName: String, password: String): MutableLiveData<LoginResult> {
authRepository.login(userName, password)
.addOnCompleteListener {
if (it.isSuccessful) {
loginResultLiveData.postValue(LoginResult.Success)
} else {
loginResultLiveData.postValue(LoginResult.Fail(it.exception.toString()))
}
}
return loginResultLiveData
}
}
问题是只有在点击 loginSignInButton
之后,模型才会创建一个 liveData 对象。但是我在设置 onClickListener 后立即开始观察这个对象。此外,每次单击按钮时,都会创建一个新的 viewModel.loginResultLiveData
实例,这没有意义。
binding.loginSignInButton.setOnClickListener { viewModel.login(
username,
password
) }
viewModel.loginResultLiveData.observe(viewLifecycleOwner){
when(it){
is LoginResult.Success -> doSmth()
}
}
在这种情况下,如何使用 LiveData 正确实施 MVVM 架构?
我也可以将我现在在 LoginUseCase
中的逻辑移动到 ModelView,然后有类似这样的东西,这避免了前面描述的问题。但是我不能将 mapping/error 处理委托给用例。
class LoginViewModel @Inject internal constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
val loginResult: MutableLiveData<LoginResult> = MutableLiveData()
fun login(username: String, password: String) = loginUseCase.login(username, password)
.addOnCompleteListener {
if (it.isSuccessful) {
loginResult.postValue(LoginResult.Success)
} else {
loginResult.postValue(LoginResult.Fail(it.exception.toString()))
}
}
}
您正在尝试观察仅在 onClickListener
之后才初始化的可变 LiveData,因此您无法使其正常工作,而且您还有一个 lateinit
属性仅当您调用会抛出异常的登录方法时才初始化。
要解决您的问题,您可以使用 MediatorLiveData
来观察您的其他实时数据并将结果传回您的片段观察器。
您可以尝试以下方法:
class LoginViewModel @Inject internal constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
private var _loginResultLiveData = MediatorLiveData<LoginResult>()
val loginResultLiveData: LiveData<LoginResult> = _loginResultLiveData
fun login(username: String, password: String) {
val loginUseCaseLiveData = loginUseCase.login(username, password)
_loginResultLiveData.addSource(loginUseCaseLiveData) {
_loginResultLiveData.value = it
}
}
}
TLDR:如何使用 LiveData 正确实现 MVVM 架构?
我有一个片段 class 观察 viewModel 公开的实时数据:
viewModel.loginResultLiveData.observe
class LoginFragment : Fragment() {
private lateinit var binding: FragmentLoginBinding
private val viewModel by fragmentScopedViewModel { injector.loginViewModel }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentLoginBinding.inflate(inflater, container, false)
val username = binding.loginInputField.toString()
val password = binding.passwordInputField.toString()
binding.loginSignInButton.setOnClickListener { viewModel.login(
username,
password
) }
viewModel.loginResultLiveData.observe(viewLifecycleOwner){
when(it){
is LoginResult.Success -> doSmth()
}
}
return binding.root
}
}
查看模型class 只需请求一个映射的实时数据对象。
class LoginViewModel @Inject internal constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
lateinit var loginResultLiveData: MutableLiveData<LoginResult>
fun login(username: String, password: String) {
loginResultLiveData = loginUseCase.login(username, password)
}
}
模型使用用例,从原始格式映射结果,也会映射错误:
class LoginUseCase @Inject internal constructor(
private val authRepository: AuthEmailRepository
) {
var loginResultLiveData = MutableLiveData<LoginResult>()
fun login(userName: String, password: String): MutableLiveData<LoginResult> {
authRepository.login(userName, password)
.addOnCompleteListener {
if (it.isSuccessful) {
loginResultLiveData.postValue(LoginResult.Success)
} else {
loginResultLiveData.postValue(LoginResult.Fail(it.exception.toString()))
}
}
return loginResultLiveData
}
}
问题是只有在点击 loginSignInButton
之后,模型才会创建一个 liveData 对象。但是我在设置 onClickListener 后立即开始观察这个对象。此外,每次单击按钮时,都会创建一个新的 viewModel.loginResultLiveData
实例,这没有意义。
binding.loginSignInButton.setOnClickListener { viewModel.login(
username,
password
) }
viewModel.loginResultLiveData.observe(viewLifecycleOwner){
when(it){
is LoginResult.Success -> doSmth()
}
}
在这种情况下,如何使用 LiveData 正确实施 MVVM 架构?
我也可以将我现在在 LoginUseCase
中的逻辑移动到 ModelView,然后有类似这样的东西,这避免了前面描述的问题。但是我不能将 mapping/error 处理委托给用例。
class LoginViewModel @Inject internal constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
val loginResult: MutableLiveData<LoginResult> = MutableLiveData()
fun login(username: String, password: String) = loginUseCase.login(username, password)
.addOnCompleteListener {
if (it.isSuccessful) {
loginResult.postValue(LoginResult.Success)
} else {
loginResult.postValue(LoginResult.Fail(it.exception.toString()))
}
}
}
您正在尝试观察仅在 onClickListener
之后才初始化的可变 LiveData,因此您无法使其正常工作,而且您还有一个 lateinit
属性仅当您调用会抛出异常的登录方法时才初始化。
要解决您的问题,您可以使用 MediatorLiveData
来观察您的其他实时数据并将结果传回您的片段观察器。
您可以尝试以下方法:
class LoginViewModel @Inject internal constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
private var _loginResultLiveData = MediatorLiveData<LoginResult>()
val loginResultLiveData: LiveData<LoginResult> = _loginResultLiveData
fun login(username: String, password: String) {
val loginUseCaseLiveData = loginUseCase.login(username, password)
_loginResultLiveData.addSource(loginUseCaseLiveData) {
_loginResultLiveData.value = it
}
}
}