2 way Databinding with ViewModel-SavedState

580 Views Asked by At

I have a subscription form: Name, Address, Local, etc, etc... and I want to be able to use Data Binding, LiveData and SavedStateHandle to handle and persist data.

Let's assume: 1 way Data Binding, LiveData and SavedStateHandle

private val _name: MutableLiveData<Boolean> = stateHandle.getLiveData("_name", "")
val name: LiveData<String> = _name

fun onNameChanged(editable: Editable) {
    _name.value = editable.toString()
}

<EditText
    android:text="@{viewModel.name}"
    android:afterTextChanged="@{viewModel::onNameChanged}"
    ...
/>

This works but I think we can do better:

  • @Parcelize class Form to encapsulate all fields
  • 2-way Data Binding to reduce .xml code

Now the problems start to appear.
How can I be notified of a change inside Form class in order to be able to persist the object with SavedState? Is there actually an advantage trying to improve the code above or with 2-way Data Binding we are losing control? I'm also using some logic to enable the submit button like this:

private val _name: MutableLiveData<String> = stateHandle.getLiveData("_name", "")   
val name = _name.map { value -> {
    updateUi()
    return value}
}

private val _formValidLiveData = MutableLiveData<Unit>()
val submitEnable = _formValidLiveData.map { _ -> isFormValid() }
val submitAlpha = _formValidLiveData.map { _ -> if (isFormValid()) 1f else 0.5f }
...
private fun updateUi() {
    _formValidLiveData.value = Unit
}

Again, with 2-way Data Binding I'm losing control, how can I call updateUi() when a EditText field change?
Thanks for your time.

1

There are 1 best solutions below

1
On

Ok, was stuck with a similar problem - and tested this, which seems to work fine:

public class LoginViewModel extends AndroidViewModel {

  public final MutableLiveData<String> mUsername;
  private SavedStateHandle handle;

  public LoginViewModel(Application application, SavedStateHandle handle) {
    // If this is the first time entering the activity, mUsername will be created, populated by value 'BLAH' and stored in the handler.
    // else, this will will be the last set value
    mUsername = handle.getLiveData("mUsername", "BLAH");
  }
}

Now, I entered some values in the 2-way binded user name field, defined like so in xml:

<com.google.android.material.textfield.TextInputLayout
     android:id="@+id/username_wrapper"
     android:layout_below="@id/header_container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginBottom="@dimen/general_padding"
     android:theme="@style/TextInputLayoutAppearance"
     android:hint="@string/username">
       <com.google.android.material.textfield.TextInputEditText
          android:id="@+id/username_field"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:inputType="textEmailAddress"
           android:text="@={ viewModel.mUsername }"/>
      </com.google.android.material.textfield.TextInputLayout>

To test, I enetered some data into the field and I followed the answer linked below. The field was set to the LATEST entered value every time. To make sure, I also put a breakpoint in the constructor of my view model - and you can see that the value is populated correctly! So, it seems two way databinding works fine.

Link to trigger activity / viewmodel destroy on navigate away: Android viewModel savedStateHandle