Solvedarchitecture components samples Fragments create new instances of Observers after onActivityCreated

First of all I want to say thanks for the three samples which enlighten working with these new components significantly, looking at the code there is a lot to learn.

After trying to create a project, making use of the new arch components, I noticed that one of my Fragments received more and more events from LiveData within the Observer code after navigating back and forth in the UI.

This happens due to the Fragment instance being retained and popped back from the stack after "back" navigation.

In these examples typically in onActivityCreated LiveData is observed and a new Observer is created. In order to solve recreating new Observers, checking if onActivityCreated has been called on the instance of Fragment before seems to be the goto solution for the moment.

@yigit how would you go about this? check if savedInstanceState is null and then create Observers? I also noticed that declaring the Observers as final fields seems to solve the issue as well, meaning that registering the same Observer instance several times seems to be fine, is this something you would recommend?

Thanks a lot and keep up the good work!
Manuel

UPDATE 12 Oct 2019

using viewLifecycleOwner as LifecycleOwner as proposed seems to solve my issue. Typically what I do now is the following:

class MainFragment : Fragment() {
// ... declare viewmodel lazy
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel.liveData.observe(viewLifecycleOwner, Observer { item ->
           // ... code that deals with item / state goes here
        })
    }
//...
}
50 Answers

✔️Accepted Answer

After some thinking I realized fragments actually provide 2 distinct lifecycles:

  • The lifecycle of the fragment itself
  • The lifecycle of each view hierarchy.

My proposed solution is to create a fragment which allows accessing the lifecycle of the current view hierarchy in addition to its own.

/**
 * Fragment providing separate lifecycle owners for each created view hierarchy.
 * <p>
 * This is one possible way to solve issue https://github.com/googlesamples/android-architecture-components/issues/47
 *
 * @author Christophe Beyls
 */
public class ViewLifecycleFragment extends Fragment {

	static class ViewLifecycleOwner implements LifecycleOwner {
		private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);

		@Override
		public LifecycleRegistry getLifecycle() {
			return lifecycleRegistry;
		}
	}

	@Nullable
	private ViewLifecycleOwner viewLifecycleOwner;

	/**
	 * @return the Lifecycle owner of the current view hierarchy,
	 * or null if there is no current view hierarchy.
	 */
	@Nullable
	public LifecycleOwner getViewLifeCycleOwner() {
		return viewLifecycleOwner;
	}

	@Override
	public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
		super.onViewCreated(view, savedInstanceState);
		viewLifecycleOwner = new ViewLifecycleOwner();
		viewLifecycleOwner.getLifecycle().handleLifecycleEvent(Event.ON_CREATE);
	}

	@Override
	public void onStart() {
		super.onStart();
		if (viewLifecycleOwner != null) {
			viewLifecycleOwner.getLifecycle().handleLifecycleEvent(Event.ON_START);
		}
	}

	@Override
	public void onResume() {
		super.onResume();
		if (viewLifecycleOwner != null) {
			viewLifecycleOwner.getLifecycle().handleLifecycleEvent(Event.ON_RESUME);
		}
	}

	@Override
	public void onPause() {
		if (viewLifecycleOwner != null) {
			viewLifecycleOwner.getLifecycle().handleLifecycleEvent(Event.ON_PAUSE);
		}
		super.onPause();
	}

	@Override
	public void onStop() {
		if (viewLifecycleOwner != null) {
			viewLifecycleOwner.getLifecycle().handleLifecycleEvent(Event.ON_STOP);
		}
		super.onStop();
	}

	@Override
	public void onDestroyView() {
		if (viewLifecycleOwner != null) {
			viewLifecycleOwner.getLifecycle().handleLifecycleEvent(Event.ON_DESTROY);
			viewLifecycleOwner = null;
		}
		super.onDestroyView();
	}
}

It can be used to register an observer in onActivityCreated() that will be properly unregistered in onDestroyView() automatically:

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
	super.onActivityCreated(savedInstanceState);

	viewModel.getSampleData().observe(getViewLifeCycleOwner(), new Observer<String>() {
		@Override
		public void onChanged(@Nullable String result) {
			// Update views
		}
	});
}

When the fragment is detached and re-attached, the last result will be pushed to the new view hierarchy automatically as expected.

@yigit Do you think this is the right approach to solve the problem in the official library?

Other Answers:

The proper fix is to use Fragment.getViewLifecycleOwner() which is already part of AndroidX.

btw, @@brenoptsouza about 1, great news is that it won't be a problem because LiveData callbacks are not called outside onStart-onStop so view will be ready for sure.

Yup, same happened to me, the two best solutions came across were:

1 - Register your observers in onCreate instead of onActivityCreated. Though I imagine this may create some errors if events are received before the views are created.

2 - Create a helper method - or if you are in Kotlin land, an extension function :) - that removes any observers previously registered to your lifecycle before observing a LiveData again. And then using it instead of LiveData.observe(). My example:

inline fun <T> LiveData<T>.reobserve(owner: LifecycleOwner, crossinline func: (T?) -> (Unit)) { removeObservers(owner) observe(owner, Observer<T> { t -> func(t) }) }

I don't know if this is the best solution either.

It's not a bug in itself, but the official samples show an incorrect use of LiveData in fragments and the documentation should inform about this confusion between Fragment lifecycle and View lifecycle. I wrote a full article about this last week.