Home > database >  How to put a reference to different variables/class properties as method parameter instead of a valu
How to put a reference to different variables/class properties as method parameter instead of a valu

Time:12-26

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

  • Related