https://kotlinlang.org logo
#android
Title
# android
j

John

10/02/2019, 12:44 PM
Does calling
observeForever
on a LiveData object inside the viewModel where it’s initiated, leak it, or is the subscription ready to be recycled when the viewmodel is?
v

voben

10/02/2019, 2:04 PM
I would assume so. The documentation recommends manually calling
removeObserver
each time an
observeForever
call is added https://developer.android.com/reference/android/arch/lifecycle/LiveData
In addition,
observeForever
takes an anonymous inner class as a parameter which means its holding a strong reference to the outer class and could potentially cause a memory leak
r

rkeazor

10/03/2019, 2:06 AM
why would younuse observeForever?
w

wasyl

10/03/2019, 6:17 AM
@rkeazor for example to observe
LiveData
updated by the databinding that uses two-way binding (for example `android:text=“@={viewModel.textLiveData}“). ViewModel should react to text changes so it needs to subscribe to it
1
1
r

rkeazor

10/03/2019, 11:36 AM
@wasyl you definitely shouldnt observeForever databinding... That would be a super memory leak waiting to happen.... for databinding all you need to do is bind the lifecycle
For ex: BR.lifecycleOwner = this.
w

wasyl

10/03/2019, 11:42 AM
@rkeazor so how do you propose I react in the ViewModel to something changing in the edit text for example? Assuming I want to use two-way binding, which means I’d pass
MutableLiveData<String>
to binding and do
android:text="@={textLiveData}"
r

rkeazor

10/03/2019, 11:47 AM
Umm in your fragment or activity bind the lifecycleowner and bind the viewmodel
And two way databinding will work
That's all you need to do for both 1 way and 2 way databinding
As long as there is a lifecycle owner updates will work automatically
w

wasyl

10/03/2019, 3:59 PM
Well no, because I need to perform some logic with this new text and I want logic to be in the view model. If I bind to that live data in fragment, I still can’t pass it to the view model
r

rkeazor

10/04/2019, 12:43 AM
Huh
I think you mistaken how livedata works . You can do the logic in the viewmodel regardless
w

wasyl

10/04/2019, 8:19 AM
@John Seems like it’s not a problem, unless there’s some wrong assumption in my test:
viewModelObserverForeverTest.kt
Output is this:
Untitled
Which means that after nothing holds a reference to neither view model nor live data, everything is garbage collected without issues. And if I hadn’t used
classField = it
then view model would be garbage collected even earlier, right after there are no reference to it.
So yes, it is safe to use
observeForever
in the view model which owns the live data. Assuming both view model and live data aren’t referenced anymore from the outside, everything is garbage collected nicely.
j

John

10/04/2019, 9:03 AM
Thanks for the quick test!
👍 1
r

rkeazor

10/04/2019, 11:45 AM
This test doesnt accurately describe what could cause a leak. Of course if it's not referencing anything it will be garbage collected. The issue is when something is observing it that has a different lifecycle.for instance a view lifecycle. If you do use observeForever you will have to manually remove that observer or you will potentially have a memory leak. That's why there is the concept of lifecycleowner. Which exists in fragments, activities. And you can set the lifecycle owner in databinding... Unless your dealing with something that you cant access a lifecycle owner. But even then that's what Transformations are for
j

John

10/04/2019, 11:46 AM
The question was if a ViewModel would leak itself by using observeForever on a LiveData object that exists inside itself.
r

rkeazor

10/04/2019, 11:53 AM
Why would the viewmodel call observeForever on it's own livedata object? He asked does calling observeForever on a livedata object leak it after the viewmodel is destroyed
I'm saying that where leaks usually happens is when the observing object has a different lifecycle
So you should manually remove it.
👍 1
w

wasyl

10/04/2019, 12:41 PM
Why would the viewmodel call observeForever on it’s own livedata object?
There are use cases for it and I explained one before. If you use databinding then you expose live data from the view model and want to react to changes to them also in the view model.
He asked does calling observeForever on a livedata object leak it after the viewmodel is destroyed
You do realize you corrected OP about what he asked about? 😄
I’m saying that where leaks usually happens is when the observing object has a different lifecycle
I think important thing is that view model typically has the broadest lifecycle you can have. It lives longer than activity, fragment, view, view binding, anything. So I don’t really see how you could have a situation in which something still references the live data while view model should already be garbage collected.
It’s really difficult to just agree that there can be a memory leak if there isn’t a reasonable example of how it can happen. As I said, as long as both view model and live data aren’t referenced, everything is fine. What would be a situation in which view model should be garbage collected (so its owner is destroyed) while something still has a reference to the live data (and what would it be?)
r

rkeazor

10/04/2019, 1:14 PM
There are no usecases for it. If you need to react to changes in the viewmodel based on two way databinding, than you use LiveData Transformations or MediatorLiveData. You dont not observeForever in the viewmodel.. I mean you could , but that would just be misusing the component or at best a anti pattern. Livedata and viewmodel where made with this observation pattern in mind.
I mean feel free to use it the way you want. I just think it's best to go with the approach described in the android documentation
Here is a really good talk on how lifecycle and livedata architecture work https://m.youtube.com/watch?v=U6Lgym1XEBI
w

wasyl

10/04/2019, 1:38 PM
Using a Transformation would be the one that makes sense, but also it’s not well scalable if you’re not using live data for all layers of your app. Anyway it doesn’t really answer the question of what would be a possible scenario of memory leak if I observer forever in a view model
j

John

10/04/2019, 2:19 PM
@rkeazor If you use Transformations to react to a two-way binding MutableLiveData, then you need to observe to that Transformations somewhere for it to react to the MutableLiveData, which is why you need to call observeForever in the viewmodel
👆 1
w

wasyl

10/04/2019, 2:35 PM
I suppose if you want to be double-sure you can also simply call
removeObserver
. Again, I don’t think it’s necessary as long as you observe live data which has narrower scope/lifecycle
r

rkeazor

10/04/2019, 4:14 PM
Lol you never need observe forever in a viewmodel guys .
That's just a anti pattern. @John just have your activity or fragment observe the change and make the ness call in the viewmodel
j

John

10/04/2019, 4:15 PM
Okay an example: You have a MutableLiveData<String> with two way binding on an edittext, and you want to call
Logger.logEvent(text)
every time the text input matches a certain condition.
You shouldn’d have to go through your view to do this
👆 1
r

rkeazor

10/04/2019, 4:17 PM
Why not?
j

John

10/04/2019, 4:17 PM
Because the view’s responsibility is to take formatted data and put it on an interface
👍 1
And logging events is outside of that
And it’s not the view’s job to decide what to do with the data
👌 1
r

rkeazor

10/04/2019, 5:12 PM
@john than use a meditatorLiveData , that updates logger onChange
The viewmodel doesnt need to observeForever
j

John

10/04/2019, 5:13 PM
How would you use a mediatorLiveData that gets called on onChange?
without adding a subscriber to it
r

rkeazor

10/04/2019, 5:15 PM
You would add the liveData that is being used for 2 way data binding as a subscription
j

John

10/04/2019, 5:15 PM
Can you share example
r

rkeazor

10/04/2019, 5:17 PM
val twoWayBinding: MutableLiveData<String>
Val dataLogger: MediatorLiveData<String> dataLogger.addSource(twoWayBinding) { Logger.log(it)
Or you can make a custom mutableLiveData that overrides setValue lol
It all works
v

voben

10/04/2019, 5:49 PM
@rkeazor If
dataLogger
MediatorLiveData is observing
twoWayBinding
livedata. Wouldn't someone also need to observe the
twoWayBinding
livedata?
👆 1
r

rkeazor

10/04/2019, 7:08 PM
No, databinding act as a active observer under the hood
I will create a code snippet when I get off from work
d

dewildte

10/04/2019, 7:51 PM
@John
If you use Transformations to react to a two-way binding MutableLiveData, then you need to observe to that Transformations somewhere for it to react to the MutableLiveData, which is why you need to call observeForever in the viewmodel
The two way
MutableLiveData
should be converted into a
MediatorLiveData
, observeForever is reserved for unit testing purposes.
Please read the
LiveData
documentation .
w

wasyl

10/04/2019, 10:09 PM
observeForever is reserved for unit testing purposes
Can’t find it anywhere in the documentation. There is mention that transformations will usually let you perform things like mapping live data to another live data (e.g. show button only when edit text is not empty). My question is, what if you want to perform some action, in the view model, every time a letter is changed? Say, log it to logcat, so no other live data involved. Would you say “you shouldn’t do that” (even though documentation explicitly says live data should be held in view models, so it looks like a valid use case)? What would be the alternative? I consider view model the place for UI-related logic, so I would want this logic exactly there.
And another question about my case: I have an object with `LiveData`s for the entire view injected into the view model. Thus, I don’t create live datas. I have
showButton
live data and
loginText
live data (bound using two-way binding). Assuming both live datas are handed to the view model by DI, how would you implement the logic of enabling/disabling the button based on
loginText
contents?
Quick reminder: the original question was “does calling
observeForever
on a LiveData object inside the viewModel where it’s initiated, leak it”.
(I will hand it to Transformations that it has benefits, like being a bit more explicit about the intent and lazy behavior. I just don’t think it’s a silver bullet, and I’m not convinced there is anything wrong with using
observeForever
in certain circumstances, like when ViewModel is observing its own live data)
j

John

10/05/2019, 8:15 AM
@rkeazor MediatorLiveData doesn’t behave that way unfortunately
If there’s no subscriber on the MediatorLiveData then it will never trigger the chain
Copy code
@ExtendWith(InstantExecutorExtension::class)
class TestTe {
    @Test
    fun test() {
        val vm = Vm()
        vm.a.observeForever {  }
        vm.a.value = "hi"
    }
}

class Vm {
    val a = MutableLiveData("")
    val medi: LiveData<Boolean> = MediatorLiveData<Boolean>().apply {
        addSource(a) {
            println("a onChange") // never called
            this.value = true
        }
    }
}
d

dewildte

10/05/2019, 11:46 AM
Copy code
@ExtendWith(InstantExecutorExtension::class)
class TestTe {
    @Test
    fun test() {
        val vm = Vm()
        vm.a.observeForever {  }
        vm.a.value = "hi"
    }
}

class Vm {
    val a = MediatorLiveData("")
    private val backingBoolData =  MutableLiveData<Boolean>()
    val boolData: LiveData<Boolean> = backingBoolData
init {

a.addSource(backingBoolData) {
            println("a onChange") // now this is called
            backingBoolData.value = true
        }
    }
}
j

John

10/05/2019, 12:31 PM
backingBoolData
is never dispatching any value
println
isn’t called there either, I just ran it
d

dewildte

10/05/2019, 12:53 PM
Copy code
class ExampleLiveDataUnitTest {

    @get:Rule
    val rule = InstantTaskExecutorRule()

    val textData = MediatorLiveData<String>()
    val boolData = MutableLiveData<Boolean>()

    var actual = false

    @Before
    fun setUp() {
        textData.addSource(textData) {
            boolData.value = it == "hi"
        }
        textData.addSource(boolData) {
            actual = it
        }
        textData.observeForever {  }
    }

    @Test
    fun addition_isCorrect() {
        val expected = true

        textData.value = "hi"

        assertEquals(expected, actual)
    }
}
My bad, this is correct but not as good as just using
Transformations.map(...) { ... }
For example:
Copy code
class ExampleLiveDataUnitTest {

    @get:Rule
    val rule = InstantTaskExecutorRule()

    val textData = MediatorLiveData<String>()
    val boolData = Transformations.map(textData) {
        it == "hi"
    }.apply { textData.addSource(this) { actual = it } }

    var actual = false

    @Before
    fun setUp() {

        textData.observeForever { }
    }

    @Test
    fun addition_isCorrect() {
        val expected = true

        textData.value = "hi"

        assertEquals(expected, actual)
    }
}
w

wasyl

10/05/2019, 3:20 PM
Every time in tests you’re doing
observeForever
though. What if you don’t want any observer outside of the view model though? What if you want to react to data in the view model and not pass it anywhere further?
r

rkeazor

10/05/2019, 3:25 PM
Than you @bindable or binding adapters
@wasyl That's a databinding issue , so bindingAdapter will solve your problem. That's not the usecase of Livedata
w

wasyl

10/05/2019, 3:34 PM
Definitely not, it’s view model’s responsibility to hold view logic, not binding adapter’s. Why wouldn’t I do
observeForever
if there’s no memory leak and it does what I need? And there’s no way to react to live data without subscribing to it
r

rkeazor

10/05/2019, 3:38 PM
If you want to do it that way it's fine, but you should remove observers during onClear of your viewholder ...
It's just a anti pattern. But if your ok with it than no problem
Viewmodel
The same way you cancel other Subscriptions
You dont even need a Livedata object at this point , @Bindable would work just fine.
j

John

10/06/2019, 7:51 AM
Copy code
textData.addSource(textData) {
            boolData.value = it == "hi"
        }
Yes
This looks like a good way to do it