Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Observable field usage with two way binding (Removing property change listener)

I am using an Observable field in a ViewModel. When the Observable field gets updated, I change the UI visibility.

This can be done either done by

object : Observable.OnPropertyChangedCallback() {
                override fun onPropertyChanged(sender: Observable?, propertyId: Int) {

                }

            }

remove the callback in ondestroy.

or

directly mapping in XML like @{} using two-way binding.

Now the question is how do I remove the listener if using two-way binding? I know the Livedata can be a replacement for this.

like image 405
Rockin Avatar asked May 14 '20 16:05

Rockin


People also ask

What is 2 way data binding in Android?

Stay organized with collections Save and categorize content based on your preferences. The @={} notation, which importantly includes the "=" sign, receives data changes to the property and listen to user updates at the same time.

What is Baseobservable?

An observable class that holds a parcelable object. A convenience class that implements Observable interface and provides notifyPropertyChanged(int) and notifyChange() methods.

What is difference between observable and LiveData?

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.


1 Answers

I am not sure regarding which memory leak you are talking.

Memory leak in Java occur when one object exists long period of time and it contains strong references to other objects that should not be used anymore, thus should be destroyed by GC, but still persist because of that strong reference.

In Android specifically memory leaks usually occur when some long lasting object stores strong reference to an Activity (or in some cases Fragment). All the other memory leaks in android are not so impactful(except the ones with bitmaps - but it is a completely different topic)

So let us return to the data binding with an ObservableField and its callbacks inside the ViewModel or two way data binding via @={}. In most of the cases there will be no memory leak in both of those cases. To understand why - you will need to understand how does Android framework operates with UI and also understand now does view data binding works. So what happens when you are creating a callback via either ObservableField and callback or with @={}

When you write

    val someField: ObservabaleField = ObservableFiled<String>("someText")
    val someCallback = object : Observable.OnPropertyChangedCallback() {
                override fun onPropertyChanged(sender: Observable?, propertyId: Int) {

                }

    }
    someField.addOnPropertyChangedCallback(someCallback)

    // and in the layout
    android:text="@={viewModel.someField}"

In the generated file it does something like this

    androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, viewModelSomeFieldGet);

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                //...
                return onChangeViewModelSomeOtherStuff(object, fieldId);
            case 1 :
                return onChangeViewModelSomeField((androidx.databinding.ObservableField<java.lang.String>) object, fieldId);
        }
        return false;
    }

As you can see there is no context neither activity or fragment leaks since there is no strong reference to them stored anywhere. There is no references to context, activity or fragment in your ViewModel either(I hope!). Moreover it works the other way around - ui stores link to the ViewModel in the binding implementation thus our ViewModel may be leaking. It is rear case since the UI of an Activity or a Fragment usually gets destroyed along with its ActivityBindingImpl or FragmentBindingImpl bindings but...

To be sure you have manual way to clear references: in either Activity' onDestroy or Fragment' onDestroyView call

clearFindViewByIdCache()
binding.unbind()
binding = null
// if you store view link in your viewModel(which is bad and may cause leaks) this is the perfect place to nullify it
viewModel.view = null

Also to handle binding auto clearing you may use AutoClearedValue

the actual usage may look like(if you don't care about its type)

override var binding: ViewDataBinding? by autoCleared()// that is all - no need of onDestroy or onDestroyView

Edit

If you want to manually unregister all the callbacks from your ObservableFields you can do it. The best way to do it is in onCleared() method of ViewModel. You should call observableField.removeOnPropertyChangedCallback(callback) to handle the stuff. It will look like this considering ObservableField and callback declarations above:

class MyViewModel: ViewModel{
   //ObservableField and callback declarations
   ...
   override void onCleared(){
       someField.removeOnPropertyChangedCallback(someCallback)
   }

}

Edit end

This all things I've just described ensures absence of memory leaks while using ObservableFields and view data bindings. It is all about a correct implementation. Of course you can implement it with leaks, but you can implement it without ones.

Comment if something is still unclear - I will try to expand the answer.

A bit more info about Fragment dependent leaks here

Hope it helps.

like image 73
Pavlo Ostasha Avatar answered Oct 17 '22 00:10

Pavlo Ostasha