I need to create multiple editTexts
with logic to handle the format of the input. The different editTexts
will bind to different variables and save to different properties of viewModel.home
. The editTexts
will, however, use the same logic (although different for strings, ints, doubles, etc.) and thus I want to avoid copy-pasting code.
For each editText
I will have to set up an addTextChangedListener
and override the afterTextChanged
method (see below).
I want to generalize as much as possible through calling another method (customAfterTextChanged()
) from each of the overrides of afterTextChanged
:
viewBinding.editTextAdress.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
customAfterTextChanged()
}})
Some other places in the code I will define the method:
fun customAfterTextChanged () {
if (viewBinding.adressEditText.text.toString() != "") {
viewModel.home.adress = viewBinding.adressEditText.text.toString()
}
}
In the code above, home
and viewModel
are instances of custom classes, where home
exists as an instance within viewModel
.
My question now is: I want to be able to pass in, for example, viewBinding.cityEditText
instead of viewBinding.adressEditText
into the method. Or viewModel.home.city
instead of viewModel.home.adress
. To achieve this I think need to replace viewBinding.editTextAdress
and viewModel.home.adress
with method-specific variables, and somehow pass in references to for example viewModel.home.city
into the variables.
However, I can't wrap my head around how to do it. Can someone edit my code to make it work in this particular instance? I think I would then be able to translate to all the other logic if I can just get one working example.
CodePudding user response:
There are different ways to accomplish what you are looking for, but you are limited by the fact that you cannot pass in something like a string from your ViewModel "by reference" and modify it like you might in c . (Relevant discussion). You'll have to use a slightly different pattern in Kotlin/Java.
Option 1 - Use a Callback
fun customAfterTextChanged(et: EditText, onResult: (String)->Unit) {
val s = et.text.toString()
if (s.isNotEmpty()) {
onResult(s)
}
}
then you can call it like this
customAfterTextChanged(viewBinding.adressEditText) { s ->
viewModel.home.adress = s
}
customAfterTextChanged(viewBinding.cityEditText) { s ->
viewModel.home.city = s
}
Option 2 - Move to ViewModel (better)
It is generally better to try to move all your logic to your ViewModel to make it easier to test. If you do that, your view layer would end up looking like this (with no logic - it simply calls through to the ViewModel when the text changes) if using the ktx extensions
viewBinding.adressEditText.doAfterTextChanged { e ->
e?.let { viewModel.changedAddress(it.toString()) }
}
and in the ViewModel you would have
fun changedAddress(a: String) {
if( a.isNotEmpty() ) home.adress = a
}
The advantage of this is that it is much easier to unit test your app's behavior. For example, to test that the user deleting the text has the intended effect you would only need something like this (instead of a full UI test)
model.changedAddress("123")
assertEqual("123", model.home.address)
model.changedAddress("")
assertEqual("123", model.home.address)
If your checker logic is more complicated, you can always combine that logic within the ViewModel still like with Option 1 - but it need not have any tie back to an actual View