1

I'm creating a form on an android application using kotlin. Each field of the form is associated with a livedata, to validate or update the data instantly. To obtain this result, I used a textwatcher, which updates the associated livedata at each change of the text. The problem is that with each update, it places the cursor at the start of the field making it impossible to write continuously.

I paste here the intresting part of the code:

Activity

     viewModel.name.observe(lifecycleOwner, Observer { nameValue->
            binding.nameEditText.setText(
                nameValue?.toString()
            )
            binding.nameTextInputLayout.error =
                viewModel.nameError.value
        })
     viewModel.price.observe(lifecycleOwner, Observer { priceValue->
            binding.priceEditText.setText(
                price?.toString()
            )
            binding.priceTextInputLayout.error =
                viewModel.priceError.value
        })

binding.nameEditText.addTextChangedListener(
        TextFieldValidation(
            binding.nameEditText
        )
    )
binding.priceEditText.addTextChangedListener(
        TextFieldValidation(
            binding.priceEditText
        )
    )


  inner class TextFieldValidation(private val view: View) : TextWatcher {
        override fun afterTextChanged(s: Editable?) {}
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {

            when (view.id) {
                R.id.nameEditText-> {
                    viewModel.onNameChanged(s.toString())
                    viewModel.isNameValid()
                }
                R.id.priceEditText-> {
                    viewModel.onPriceChanged(s.toString())
                    viewModel.isPriceValid()
                }
     }
}

ViewModel

var name = MutableLiveData<String>()
var price = MutableLiveData<Double>()

var nameError = MutableLiveData<String>()
var priceError = MutableLiveData<String>()

fun onNameChanged(newValue: String?) {
    if ((name.value != newValue)) {
        name.value = newValue
    }
}
fun onPriceChanged(newValue: Double?) {
    if ((price.value != newValue)) {
        price.value = newValue
    }
}

fun isNameValid() : Boolean {

    return if ((name.value == null) || (name.value == "")) {
        nameError.value = "Error"
        false
    } else {
        nameError.value = ""
        true
    }
}
fun isPriceValid() : Boolean {

    return if ((price.value == null) || (price.value == "")) {
        priceError.value = "Error"
        false
    } else {
        priceError.value = ""
        true
    }
}

XML

<com.google.android.material.textfield.TextInputLayout

            android:id="@+id/nameTextInputLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/nameEditText"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_centerHorizontal="true"
                android:layout_centerVertical="true"
                android:backgroundTint="@color/white"
                android:inputType="text" />
        </com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout

            android:id="@+id/priceTextInputLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/priceEditText"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_centerHorizontal="true"
                android:layout_centerVertical="true"
                android:backgroundTint="@color/white"
                android:inputType="numberDecimal" />
        </com.google.android.material.textfield.TextInputLayout>

I tried using 'mEdittext.setSelection (mEdittext.length ());' but it doesn't work well, because if I make changes in the middle of the string, it brings the cursor to the end. And even in double fields, it doesn't behave correctly. I need to have the cursor always at the exact position? Either in case of adding, deleting or writing in the middle of the string. And both in the case of a string and in the case of a double.

Could someone help me?

Thank you for your patience and help!

qwerty123
  • 43
  • 4

2 Answers2

0

This is happening, because in your ViewModel observer you are setting nameValue and priceValue back to the EditText where they just came from. You could add a check and not set unchanged values back to the EditText:

if ((nameValue?.toString() == binding.nameEditText.getText().toString()) == false) {
   binding.nameEditText.setText(nameValue?.toString())
}
K-dari
  • 151
  • 4
  • No, i can't stop observe nameValue and priceValue because i have other fields linked to the value ot these. – qwerty123 May 27 '21 at 10:42
0

I also had the same problem that the cursor moved to the beginning, searching I found a first solution that served me to a certain extent allowing me to write a message in a row, which would be the following

viewModel.name.observe(lifecycleOwner, Observer { nameValue->
    binding.nameEditText.setText(
        nameValue?.toString()
    )
    binding.nameEditText.setSelection(
        nameValue?.toString().length
    )
})

I thought I had already solved it, but another problem arose and that is that if I wanted to edit any part of the center after typing a character the cursor would move to the end, until I found the correct solution https://stackoverflow.com/a/65794745/15798571

viewModel.name.observe(lifecycleOwner, Observer { nameValue->
    val selection = binding.nameEditText.selectionEnd
    val length = nameValue.length
    binding.nameEditText.setText(
        nameValue?.toString()
    )
    binding.nameEditText.setSelection(
        Math.min(selection, length)
    )
})

I hope it serves you as it served me