14

I am using 2-way data binding to update a LiveData String object from my ViewModel with a string set in the EditText:

<android.support.design.widget.TextInputEditText
  android:id="@+id/writeReviewTitle"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@={viewModel.liveReviewTitle}"
/>

So, from my understanding, the ViewModel would have its liveReviewTitle attribute updated every time the text changed in the EditText. I assume this is happening through the usage of a TextWatcher or some sort of listening mechanism that is being taken care of for me by the library. I also thought that when the text needed to be updated, it would have its setter called. Which does not seem to be the case! When the text changes, I need to do some more stuff in my ViewModel, therefore I implemented a custom setter for liveReviewTitle, but it is not being called (I tried debugging). This is how it looks like in the ViewModel class:

var liveReviewTitle: MutableLiveData<String> = MutableLiveData()
        set(value) {
            field = value
            customLogicHere()
        }

Tried debugging this setter but it never seems to be called! What is happening here? Feels a little confusing. The text is being updated, and it is saved in the ViewModel, it is just the setter that is not called.

EpicPandaForce
  • 71,034
  • 25
  • 221
  • 371
  • Just follow this example : https://medium.com/@fabioCollini/android-data-binding-f9f9d3afc761 – NSimon Jun 15 '18 at 13:24
  • 3
    @NSimon that example is not using the real 2-way data binding provided by the library. The developer is creating the listeners and attaching them hilself, which is something I want to avoid, as the library should be taking care of this. – Felipe Ribeiro R. Magalhaes Jun 15 '18 at 13:27

3 Answers3

14

Of course it's never called, you're not setting a new MutableLiveData, you're setting a new String value inside the MutableLiveData (possibly with setValue).

However, you should be able to intercept the value that's being set and execute custom logic after setting the value if you expose a MediatorLiveData instead of the MutableLiveData directly.

EDIT: the following should work as expected:

val liveReviewTitle: MutableLiveData<String> = MutableLiveData()
private val mediator = MediatorLiveData<String>().apply {
    addSource(liveReviewTitle) { value ->
        setValue(value)
        customLogicHere()
    }
}.also { it.observeForever { /* empty */ } }
EpicPandaForce
  • 71,034
  • 25
  • 221
  • 371
  • That makes sense. I cant directly observe the live data (lets say, in the constructor for example), because I need a life cycle owner for that, right? That is why I need this mediator? – Felipe Ribeiro R. Magalhaes Jun 15 '18 at 14:16
  • When I get back from Kotliners conf. I'll write a sample – EpicPandaForce Jun 15 '18 at 14:47
  • @EpicPandaForce can we still count on promised sample? I'm curious :) – Karol Kulbaka Aug 20 '18 at 20:19
  • I forgot. But I'll put something together when I get home from vacation. Remind me tomorrow, I never find this question XD – EpicPandaForce Aug 24 '18 at 06:50
  • @KarolKulbaka man that was a bit dumber and more complicated than I expected. If you expose the MediatorLiveData to the databinding, then you cannot easily make the private MutableLiveData have the new value. If you expose the MutableLiveData, then MediatorLiveData is not active, so it does not subscribe to listen for changes of the MutableLiveData. So I had to add a dummy observer with `observeForever` to ensure that Mediator is active and properly intercepts the new values set for the MutableLiveData. – EpicPandaForce Aug 26 '18 at 16:11
  • How do I clean up the "observerForever"? Is it with: mediator.removeObserver{ mediator } ? – Minsky Feb 11 '19 at 15:31
  • 1
    @Minsky you don't need to clean it up I think because this is created once and dies along with the ViewModel, but if you feel icky about it then you'd have to store a reference to the empty lambda I passed in and then remove *that* in `onCleared()`. – EpicPandaForce Feb 11 '19 at 17:06
7

@EpicPandaForce solution is proper but in EditText two way binding can be obtained in much simpler way. Add attribute afterTextChanged to your widget as below:

android:afterTextChanged="@{viewModel::doLogic}"

Then in your ViewModel class just write method:

fun doLogic(s: Editable) { //update Livedata or do other logic }

EDIT

I have missed important documentation note. Much easier (and far more proprer) will be:

android:text="@={viewModel.someLivedata}

and then in our LifecycleOwner class we can update value of liveData everywhe when we need, and of course we can react on changes from registered observer.

Karol Kulbaka
  • 957
  • 7
  • 19
  • This is the simplest most correct answer. I can't believe that with so documentation I read, they fail to mention this. it is always @{} – chitgoks Jul 14 '19 at 04:07
  • Omg!!! I lost 2 days trying to know what happened and my problem was the "=" after @. Thanks @chitgoks – Jorge Casariego Apr 25 '20 at 13:25
  • Dang it! I did the same. Guys please note that `"@={viewModel.someLivedata}"` instead of just `"@{viewModel.someLivedata}"`. – Paresh P. Sep 04 '20 at 07:31
3

@EpicPandaForce is right about your setter, it's for the MutableLiveData itself and not the value it's holding. So your LiveData should be a val, no need for it to be a var, and the framework should do the right thing as long as you set a LifecycleOwner on the binding. You could add another Observer to your LiveData in place of a custom setter to add your custom logic.

Mateo
  • 369
  • 2
  • 4
  • I can't observe the live data from within the View Model, as it is not a life cycle owner. Will have the use the Mediator for that, as the accepted answer suggested! – Felipe Ribeiro R. Magalhaes Jun 17 '18 at 14:55
  • You could use `observeForever` if your internal custom logic doesn't need to be lifecycle aware. But `MediatorLiveData` might be the more robust approach depending on your use case. – Mateo Jun 18 '18 at 14:51
  • 1
    Since the mediator is also not life cycle aware the observe forever might just do the job easier. – findusl Nov 16 '18 at 17:44
  • I had not set the LifecycleOwner of the binding. This fixed it. Thank you! – Kenji Dec 16 '20 at 20:27