【问题标题】:Input validation with MVVM and Data binding使用 MVVM 和数据绑定进行输入验证
【发布时间】:2021-04-27 02:24:13
【问题描述】:

我尝试通过实现一个非常简单的应用程序来学习 MVVM 架构,该应用程序从用户那里获取三个输入并将它们存储在 房间数据库中,然后将数据显示在回收站视图。 从第一次尝试开始,它似乎运行良好,然后如果其中一个输入为空,则应用程序崩溃。现在,我想添加一些输入验证(现在验证必须只检查空字符串),但我无法弄清楚。我在 stackoverflow 和一些验证输入的库上找到了许多答案,但我无法将这些解决方案集成到我的应用程序中(很可能是由于我对 MVVM 的实施不佳)。 这是我的 ViewModel 的代码:

class MetricPointViewModel(private val repo: MetricPointRepo): ViewModel(), Observable {

    val points = repo.points

    @Bindable
    val inputDesignation = MutableLiveData<String>()

    @Bindable
    val inputX = MutableLiveData<String>()

    @Bindable
    val inputY = MutableLiveData<String>()



    fun addPoint(){
        val id = inputDesignation.value!!.trim()
        val x = inputX.value!!.trim().toFloat()
        val y = inputY.value!!.trim().toFloat()
        insert(MetricPoint(id, x , y))
        inputDesignation.value = null
        inputX.value = null
        inputY.value = null
    }

    private fun insert(point: MetricPoint) = viewModelScope.launch { repo.insert(point) }

    fun update(point: MetricPoint) = viewModelScope.launch { repo.update(point) }

    fun delete(point: MetricPoint) = viewModelScope.launch { repo.delete(point) }

    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
    }

    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
    }
}

这是发生一切的片段:

class FragmentList : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null

    //Binding object
    private lateinit var binding: FragmentListBinding
    //Reference to the ViewModel
    private lateinit var metricPointVm: MetricPointViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //Setting up the database
        val metricPointDao = MetricPointDB.getInstance(container!!.context).metricCoordDao
        val repo = MetricPointRepo(metricPointDao)
        val factory = MetricPointViewModelFactory(repo)
        metricPointVm = ViewModelProvider(this, factory).get(MetricPointViewModel::class.java)
        // Inflate the layout for this fragment
        binding = FragmentListBinding.inflate(inflater, container, false)
        binding.apply {
            lifecycleOwner = viewLifecycleOwner
            myViewModel = metricPointVm
        }

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initRecyclerview()
    }

    private fun displayPoints(){
        metricPointVm.points.observe(viewLifecycleOwner, Observer {
            binding.pointsRecyclerview.adapter = MyRecyclerViewAdapter(it) { selecteItem: MetricPoint -> listItemClicked(selecteItem) }
        })
    }

    private fun initRecyclerview(){
        binding.pointsRecyclerview.layoutManager = LinearLayoutManager(context)
        displayPoints()
    }

    private fun listItemClicked(point: MetricPoint){
        Toast.makeText(context, "Point: ${point._id}", Toast.LENGTH_SHORT).show()
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment FragmentList.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            FragmentList().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
    }
}

我还计划在 recyclerview 中添加长按并显示上下文菜单,以便从数据库中删除项目。任何帮助,将不胜感激。 我的回收站视图适配器实现:

class MyRecyclerViewAdapter(private val pointsList: List<MetricPoint>,
                            private val clickListener: (MetricPoint) -> Unit): RecyclerView.Adapter<MyViewHolder>(){
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding: RecyclerviewItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.recyclerview_item, parent, false)
        return MyViewHolder(binding)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bind(pointsList[position], clickListener)
    }

    override fun getItemCount(): Int {
        return pointsList.size
    }

}

class MyViewHolder(private val binding: RecyclerviewItemBinding): RecyclerView.ViewHolder(binding.root){
    fun bind(point: MetricPoint, clickListener: (MetricPoint) -> Unit){
        binding.idTv.text = point._id
        binding.xTv.text = point.x.toString()
        binding.yTv.text = point.y.toString()
        binding.listItemLayout.setOnClickListener{
            clickListener(point)
        }
    }
}

【问题讨论】:

    标签: android validation kotlin input mvvm


    【解决方案1】:

    试试下面的,

        fun addPoint(){
            val id = inputDesignation.value!!.trim()
            if(inputX.value == null)
                 return
    
            val x = inputX.value!!.trim().toFloat()
    
            if(inputY.value == null)
                return
    
            val y = inputY.value!!.trim().toFloat()
            insert(MetricPoint(id, x , y))
            inputDesignation.value = null
            inputX.value = null
            inputY.value = null
        }
    

    编辑:

    如果您希望让用户知道值是预期的值,您也可以尝试以下操作

    视图模型

    private val _isEmpty = MutableLiveData<Boolean>()
    val isEmpty : LiveData<Boolean>
    get() = _isEmpty
    
        fun addPoint(){
            val id = inputDesignation.value!!.trim()
            if(inputX.value == null){
                 _isEmpty.value = true
                 return
            }
    
            val x = inputX.value!!.trim().toFloat()
    
            if(inputY.value == null){
                 _isEmpty.value = true
                 return
            }
    
            val y = inputY.value!!.trim().toFloat()
            insert(MetricPoint(id, x , y))
            inputDesignation.value = null
            inputX.value = null
            inputY.value = null
        }
    
    //since showing a error message is an event and not a state, reset it once its done
    
       fun resetError(){
            _isEmpty.value = null
       }
    

    片段类

    metricPointVm.isEmpty.observe(viewLifecycleOwner){ isEmpty ->
        isEmpty?.apply{
             if(it){
                  // make a Toast
                  metricPointVm.resetError()
             }
        }
    }
    

    【讨论】:

    • 感谢@Sekiro,简单而有效的解决方案。
    • @abadil 检查编辑后的答案,如果您希望让用户知道预期值,如果它解决了您的问题,请接受答案,祝您编码愉快
    • 再次感谢@Sekiro,它运行良好。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-05
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-04
    相关资源
    最近更新 更多