MVVM+Kotlin+Databinding最佳实践:如何在ViewModel中做手机号非空校验?
这问题我之前也碰到过,完全理解你想严格遵循MVVM职责分离的心情!把校验逻辑移到ViewModel其实很顺畅,我给你分享下我常用的最优实现方式:
最优实现方案
1. 改造ViewModel:承载数据与校验逻辑
首先在你的LoginViewModel里,定义用于绑定手机号的可观察字段、处理登录点击的方法,以及通知UI校验结果的反馈字段,所有业务逻辑都在这里实现:
class LoginViewModel : ViewModel() { // 双向绑定手机号输入框的可观察字段 val phoneNumber = MutableLiveData<String>("") // 用于向UI传递校验错误信息的LiveData val validationError = MutableLiveData<String?>() // 处理登录按钮点击的方法 fun onLoginClicked() { val phone = phoneNumber.value?.trim() when { phone.isNullOrEmpty() -> { validationError.value = "手机号不能为空哦" } // 可扩展:添加手机号格式校验逻辑 !isValidPhoneFormat(phone) -> { validationError.value = "请输入正确的手机号格式" } else -> { // 校验通过,执行后续登录逻辑(比如调用接口) validationError.value = null performLogin(phone) } } } private fun isValidPhoneFormat(phone: String): Boolean { // 示例:手机号正则校验 val phoneRegex = "^1\\d{10}$".toRegex() return phoneRegex.matches(phone) } private fun performLogin(phone: String) { // 这里调用Repository层的登录接口逻辑 } }
2. 布局文件:用Databinding完成UI与ViewModel绑定
在LoginFragment的布局中,通过Databinding实现输入框与ViewModel字段的双向绑定,按钮点击事件直接关联ViewModel的方法:
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="com.yourpackage.LoginViewModel" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入手机号" android:text="@={viewModel.phoneNumber}" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{viewModel.validationError}" android:textColor="@android:color/holo_red_light" android:visibility="@{viewModel.validationError != null ? View.VISIBLE : View.GONE}" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="登录" android:onClick="@{() -> viewModel.onLoginClicked()}" /> </LinearLayout> </layout>
这里的@=是双向绑定语法,能自动把输入框的文本同步到ViewModel的phoneNumber,无需手动在Fragment中获取输入值。
3. Fragment:仅负责UI初始化与生命周期绑定
Fragment里只需要完成ViewModel初始化、Databinding设置,以及观察校验反馈,完全不涉及任何业务逻辑:
class LoginFragment : Fragment() { private lateinit var binding: FragmentLoginBinding private lateinit var viewModel: LoginViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = DataBindingUtil.inflate(inflater, R.layout.fragment_login, container, false) viewModel = ViewModelProvider(this)[LoginViewModel::class.java] binding.viewModel = viewModel binding.lifecycleOwner = viewLifecycleOwner // 让LiveData感知Fragment生命周期 // 可选:如果需要额外的UI反馈(比如Toast),可以在这里观察错误信息 viewModel.validationError.observe(viewLifecycleOwner) { errorMsg -> errorMsg?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show() } } return binding.root } }
为什么这是最优方案?
- 严格遵循MVVM职责分离:View(Fragment)只做UI展示和事件绑定,ViewModel负责所有业务逻辑,数据流向清晰可追溯。
- 利用Databinding减少冗余代码:双向绑定省去了手动从输入框取文本的操作,让Fragment代码更简洁。
- 逻辑集中便于扩展与测试:校验逻辑都在ViewModel中,后续加验证码校验、密码校验等都很方便;同时ViewModel可以脱离UI进行单元测试,测试效率更高。
内容的提问来源于stack exchange,提问作者Abhishek Tiwari
相关产品推荐
相关产品推荐

