8

This Google sample calls observe on LiveData in a fragment and passes getActivity() as the LifecycleOwner.

mSeekBarViewModel.seekbarValue.observe(getActivity(), new Observer<Integer>() {
        @Override
        public void onChanged(@Nullable Integer value) {
            if (value != null) {
                mSeekBar.setProgress(value);
            }
        }
    });

https://github.com/googlecodelabs/android-lifecycles/blob/master/app/src/main/java/com/example/android/lifecycles/step5_solution/Fragment_step5.java

I can't wrap my head around any reasons to do that. I only want updates as long as the fragment is active, so why not scope it to the fragment? Are there any reasons to ever NOT scope it to the fragment?

Florian Walther
  • 6,237
  • 5
  • 46
  • 104
  • IMHO, that's probably a bug in the code lab. "Are there any reasons to ever NOT scope it to the fragment?" -- sure, if you're not in a fragment. :-) – CommonsWare Nov 02 '18 at 22:21
  • So you agree that there is no point in scoping LiveData to the activity? – Florian Walther Nov 02 '18 at 22:22
  • In this particular case, yes. My guess is that because they are using a shared `ViewModel`, which they obtained using `getActivity()`, that they set up the observer using `getActivity()` as well. In many circumstances, what they are doing probably actually works, but it is a code smell in my mind. – CommonsWare Nov 02 '18 at 22:25
  • But is there EVER a reason to do that? – Florian Walther Nov 02 '18 at 22:26
  • I can't think of any. However, "EVER" is quite expansive, and so I can't rule it out. – CommonsWare Nov 02 '18 at 22:28

1 Answers1

17

In this case, the Fragment is inflated from a <fragment> tag in the Activity's layout, so the lifecycle of the Fragment and the Activity is always the same so it doesn't make any difference.

However, there are two cases where this fails badly:

  • If you remove() or replace() the Fragment, using getActivity() for your LifecycleOwner will result in leaking the Fragment since the LiveData holds a strong reference to the Observer (and hence, the Fragment since it is a non-static inner class) until the Activity is destroyed
  • If you detach() and then attach() the Fragment (such as with a FragmentPagerAdapter), then using the Fragment's lifecycle in onCreateView() will result in multiple Observers since onCreateView() is called each time the Fragment's view is recreated upon attach and previous Observers are not destroyed since the Fragment's lifecycle has not been destroyed.

The correct LifecycleOwner to use in onCreateView() is always getViewLifecycleOwner() since this lifecycle is destroyed when the Fragment's View is destroyed:

mSeekBarViewModel.seekbarValue.observe(getViewLifecycleOwner(), new Observer<Integer>() {
    @Override
    public void onChanged(@Nullable Integer value) {
        if (value != null) {
            mSeekBar.setProgress(value);
        }
    }
});

This prevents leaking the Fragment by using a potentially longer lifespan LifecycleOwner (like the Activity) and prevents multiple Observers being registered when using patterns like those employed by FragmentPagerAdapter.

ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • Thank you for that detailed answer. Say, is there ever a reason to start observing in `onCreate` (in the fragment)? For me, `onCreateView`/`onActivityCreated` in combination with `getViewLifecycleOwner` seems like the all around solution. – Florian Walther Nov 02 '18 at 22:39
  • `getViewLifecycleOwner` can't be called in `onCreate()` (the view and hence its lifecycle doesn't exist yet), so you'd want to pair `getViewLifecycleOwner()` with `onCreateView()` or `onViewCreated()`. – ianhanniballake Nov 02 '18 at 22:44
  • As an alternative, you can use observe `[getViewLifecycleOwnerLiveData()`](https://developer.android.com/reference/androidx/fragment/app/Fragment#getviewlifecycleownerlivedata) in `onCreate()` (using the Fragment as the LifecycleOwner for that observe call) - then you'll get a callback whenever the view `LifecycleOwner` changes and can register a view specific observer there. That's more useful for listeners external to the Fragment who can't just use `onViewCreated()` though. – ianhanniballake Nov 02 '18 at 22:45
  • Is there ever a scenario where I would register the `Observer` in `onCreate` and use `this` as the `LifecycleOwner`? To me it seems that this has only downsides – Florian Walther Nov 02 '18 at 22:47
  • One side effect of using `getViewLifecycleOwner()` is that you get a callback to your Observer every time the Fragment's view is created (even if the data hasn't changed - you still need a callback to populate the new Views). That isn't necessary or even preferred if your Observer doesn't touch the UI, so you might consider still using `onCreate()` and `this` for those cases. – ianhanniballake Nov 02 '18 at 22:49
  • Note that `getViewLifecycleOwner()` only exists on the AndroidX edition of `Fragment`, not `android.support.v4.app.Fragment`. – CommonsWare Nov 02 '18 at 23:02
  • Actually, it does exist in the 28.0.0 `android.support.v4.app.Fragment` - the Javadoc on developer.android.com is just not updated – ianhanniballake Nov 02 '18 at 23:10
  • There's just 1 more uncertainty for me. Is `onCreateView` or `onActivityCreated` the right place to add a view-scoped `Observer`? Or maybe `onViewCreated`? – Florian Walther Nov 02 '18 at 23:24
  • `onCreateView` or `onViewCreated()` are both equally valid (I personally prefer to have `onCreateView()` always just be a one-liner `inflate` call and have my logic in `onViewCreated()`, but YMMV). `onActivityCreated()` suffers the same problem as `onCreate()` and shouldn't be used with a view-scoped Observer. – ianhanniballake Nov 02 '18 at 23:27
  • But `onActivityCreated` is called directly after `onCreateView` or am I missing something? – Florian Walther Nov 02 '18 at 23:50
  • There's a super minor edge case regarding Fragments inflated from a layout (i.e., a `` tag where you may get calls to `onViewCreated()` but not `onActivityCreated()`, but you're right that in the vast majority of times, that would also be safe to use. – ianhanniballake Nov 03 '18 at 00:10
  • So in your previous comment, you were mistaken about `onActivityCreated`? I just want to make sure that I understand it correctly. – Florian Walther Nov 03 '18 at 07:10
  • Actually, if I call `observe` in `onCreate` and then replace the fragment (and add it back to the container), I completely stop getting updates from the `LiveData` in this fragment. – Florian Walther Nov 03 '18 at 11:22
  • Since you know a lot about `LiveData`, maybe you can take a look at this question: https://stackoverflow.com/questions/53130047/fragment-not-receiving-livedata-updates-after-remove-add – Florian Walther Nov 03 '18 at 11:35
  • Might be interesting to read. It is also referring to this question: https://proandroiddev.com/5-common-mistakes-when-using-architecture-components-403e9899f4cb – Javatar May 23 '19 at 09:55
  • Interesting info. I remember early day, based on the tutorial, I was using Fragment itself as live data observer. Later, I realize there are some weird behavior (observer being triggered multiple times), which I need to workaround by first `removeObservers(Fragment)` before I `observe(Fragment)`. So, now to official guideline is using `getViewLifecycleOwner`? – Cheok Yan Cheng Jun 25 '19 at 17:57
  • @CheokYanCheng - or register your observers in `onCreate()`, which is only called once – ianhanniballake Jun 25 '19 at 18:03
  • @ianhanniballake My gold reference source so far is "Lyla Fujiwara's presentation in India 17". I guess, info may a bit outdated. The reason I do not observe in `onCreate` is that, view during that time is not ready. So, I feel it might not make sense to start observing. Is observing using Fragment still fine (As I have a lot of legacy code doing so, not sure I need to revise them and change to getViewLifecycleOwner?)? As long as they are not (1) Part of FragmentPagerAdapter (2) Retained(true) fragment ? – Cheok Yan Cheng Jun 25 '19 at 18:12
  • @CheokYanCheng - LiveData only gives you results when you are started, at which time the view is already created - that's why it is fine to start observing in `onCreate()`. Being put on the back stack is the other case where `onCreateView()`/`onViewCreated()` will be called multiple times for the same Fragment instance. – ianhanniballake Jun 25 '19 at 18:21