#android #validation #kotlin #input #mvvm
#Android #проверка #kotlin #ввод #mvvm
Вопрос:
Я пытаюсь изучить архитектуру MVVM, реализуя очень простое приложение, которое принимает три входных данных от пользователя и сохраняет их в базе данных Room, а затем отображает данные в RecyclerView. С первой попытки кажется, что все работает хорошо, затем приложение вылетает, если один из входных данных остается пустым. Теперь я хочу добавить некоторые проверки ввода (на данный момент проверки должны просто проверять наличие пустой строки), но я не могу понять это. Я нашел много ответов на 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 и отобразить контекстное меню, чтобы удалить элементы из базы данных. Любая помощь будет оценена.
Моя реализация адаптера представления recycler:
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)
}
}
}
Ответ №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
}
Редактировать:
вы также можете попробовать следующее, если хотите сообщить пользователю, что ожидается значение a value
ViewModel
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()
}
}
}
Комментарии:
1. Спасибо @Sekiro, простое, но эффективное решение.
2. @abadil проверьте отредактированный ответ, если вы хотите сообщить пользователю ожидаемое значение, и если это решит вашу проблему, примите ответ, счастливого кодирования
3. еще раз спасибо @Sekiro, все работает отлично.