Home > Back-end >  ViewModel and Activity using Kotlin
ViewModel and Activity using Kotlin

Time:10-02

How do I get a variable from an edit text into a save variable in viewmodel? I want to get the below variable into my Viewmodel from MainActivity val age = (binding.age.text.toString()).toInt() I think I have to have a saved handle state in the activity and then a get() in the view model. I was hoping to get some pointers. Or am I missing the point and should have it in my mainactivity with OnResume?

I am preforming the below steps in my veiwmodel which work yet upon rotation the age is not saved:

private const val TAG = "age"
//const val age = "age"

class hrViewModel(private val state: SavedStateHandle) : ViewModel() {


    fun update(ageinput: Int): Int {
        Log.d(TAG,"age", Exception())
        val maxhr = 220 - ageinput
        return maxhr
    }
    fun concatenate(vararg digits: Int): String {
        return digits.joinToString(separator = " - ", transform = Int::toString)
    }
    fun target(agein: Int): String {
        val bank = update(agein)
        val high = bank * .5
        val low = bank * .85
        return concatenate(high.toInt(), low.toInt())

    }
}```

CodePudding user response:

If you want to use a SavedStateHandle the usual way is like this:

val age = savedStateHandle.getLiveData<Int>("age", 0) // or whatever default

fun setAge(age: Int) {
    savedStateHandle["age"] = age
}

That way you set the age value through that function, which stores it in the SavedStateHandle, and that value propagates through to the LiveData where you can read it with age.value, or observe it as normal. So you only need to set it once.


If you have any internal functions that need to read that value, just do

// a rare case where !! can make sense, since you -know- it won't be null
// since it has a default value
val currentAge = age.value!!
// do stuff with currentAge

Or you could follow the same reactive pattern, and make it so that when age updates, your other properties automatically do too, e.g.

val maxHr: LiveData<Int> = age.map { age -> 220 - age }

You can chain things like this so you keep all your values updated automatically, just by laying out the rules about what happens when something else changes.


To update that age value, just call setAge. Since you're trying to store the contents of your EditText, you need to update the ViewModel whenever those contents change. You can do that with a TextWatcher - the androidx.core-ktx libraries have some nice functions for that, so you can just implement the callback you need:

binding.age.doAfterTextChanged { it?.toString()?.toIntOrNull()?.run(model::setAge) }

So now, whenever the text contents change, it attempts to parse them as an Int and calls setAge on the VM with the result.


And since these are LiveData objects, your UI can observe them and display stuff whenever it updates, including when your Fragment is first connected. So if you observe that age LiveData, you can display it in your text box, and when the fragment is created it'll automatically display the current info

// in the fragment, in onViewCreated is best since you need the viewLifecycleOwner
model.age.observe(viewLifecycleOwner) { age ->
    ageText = age.toString()
    if (binding.age.text.toString() != ageText) binding.age.setText(ageText)
}

That check there is to avoid updating the EditText when it's already showing that value. That's important because remember, we have a TextWatcher that updates age whenever the EditText's contents are changed. But since updating age triggers the observer, and that updates the contents of the EditText... you could have an infinite circular loop!

So this is one approach to breaking that loop. You could also call distinctUntilChanged() on your LiveData to get one that doesn't update (or push a value to observers) if the new value is the current one:

val age = savedStateHandle.getLiveData<Int>("age", 0).distinctUntilChanged()

This is all a little awkward because EditTexts and other Android view widgets already maintain their own state (like the entered text) - but this is how you would push the state in the ViewModel to the UI, to make it display what the VM says it should (which is a recommended approach these days). That also enables you to e.g. load stored state (say getting data from a database or something) and letting the UI automatically display it, and it Just Works

  • Related