package com.kickstarter.libs;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.kickstarter.libs.qualifiers.RequiresFragmentViewModel;
import com.kickstarter.libs.utils.BundleUtils;
import com.trello.rxlifecycle.FragmentEvent;
import com.trello.rxlifecycle.RxLifecycle;
import com.trello.rxlifecycle.components.FragmentLifecycleProvider;
import rx.Observable;
import rx.subjects.BehaviorSubject;
import timber.log.Timber;
public class BaseFragment<ViewModelType extends FragmentViewModel> extends Fragment implements FragmentLifecycleProvider,
FragmentLifecycleType {
private final BehaviorSubject<FragmentEvent> lifecycle = BehaviorSubject.create();
private static final String VIEW_MODEL_KEY = "FragmentViewModel";
protected ViewModelType viewModel;
/**
* Returns an observable of the fragment's lifecycle events.
*/
@Override
public final @NonNull Observable<FragmentEvent> lifecycle() {
return lifecycle.asObservable();
}
/**
* Completes an observable when an {@link FragmentEvent} occurs in the fragment's lifecycle.
*/
@Override
public final @NonNull <T> Observable.Transformer<T, T> bindUntilEvent(final @NonNull FragmentEvent event) {
return RxLifecycle.bindUntilFragmentEvent(lifecycle, event);
}
/**
* Completes an observable when the lifecycle event opposing the current lifecyle event is emitted.
* For example, if a subscription is made during {@link FragmentEvent#CREATE}, the observable will be completed
* in {@link FragmentEvent#DESTROY}.
*/
@Override
public final @NonNull <T> Observable.Transformer<T, T> bindToLifecycle() {
return RxLifecycle.bindFragment(lifecycle);
}
/**
* Called before `onCreate`, when a fragment is attached to its context.
*/
@CallSuper
@Override
public void onAttach(final @NonNull Context context) {
super.onAttach(context);
Timber.d("onAttach %s", this.toString());
lifecycle.onNext(FragmentEvent.ATTACH);
}
@CallSuper
@Override
public void onCreate(final @Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Timber.d("onCreate %s", this.toString());
lifecycle.onNext(FragmentEvent.CREATE);
assignViewModel(savedInstanceState);
viewModel.arguments(getArguments());
}
/**
* Called when a fragment instantiates its user interface view, between `onCreate` and `onActivityCreated`.
* Can return null for non-graphical fragments.
*/
@CallSuper
@Override
public @Nullable View onCreateView(final @NonNull LayoutInflater inflater, final @Nullable ViewGroup container,
final @Nullable Bundle savedInstanceState) {
final View view = super.onCreateView(inflater, container, savedInstanceState);
Timber.d("onCreateView %s", this.toString());
lifecycle.onNext(FragmentEvent.CREATE_VIEW);
return view;
}
@CallSuper
@Override
public void onStart() {
super.onStart();
Timber.d("onStart %s", this.toString());
lifecycle.onNext(FragmentEvent.START);
}
@CallSuper
@Override
public void onResume() {
super.onResume();
Timber.d("onResume %s", this.toString());
lifecycle.onNext(FragmentEvent.RESUME);
assignViewModel(null);
if (viewModel != null) {
viewModel.onResume(this);
}
}
@CallSuper
@Override
public void onPause() {
lifecycle.onNext(FragmentEvent.PAUSE);
super.onPause();
Timber.d("onPause %s", this.toString());
if (viewModel != null) {
viewModel.onPause();
}
}
@CallSuper
@Override
public void onStop() {
lifecycle.onNext(FragmentEvent.STOP);
super.onStop();
Timber.d("onStop %s", this.toString());
}
/**
* Called when the view created by `onCreateView` has been detached from the fragment.
* The lifecycle subject must be pinged before it is destroyed by the fragment.
*/
@CallSuper
@Override
public void onDestroyView() {
lifecycle.onNext(FragmentEvent.DESTROY_VIEW);
super.onDestroyView();
}
@CallSuper
@Override
public void onDestroy() {
lifecycle.onNext(FragmentEvent.DESTROY);
super.onDestroy();
Timber.d("onDestroy %s", this.toString());
if (viewModel != null) {
viewModel.onDestroy();
}
}
/**
* Called after `onDestroy` when the fragment is no longer attached to its activity.
*/
@CallSuper
@Override
public void onDetach() {
Timber.d("onDetach %s", this.toString());
super.onDetach();
if (getActivity().isFinishing()) {
if (viewModel != null) {
// Order of the next two lines is important: the lifecycle should update before we
// complete the view publish subject in the view model.
lifecycle.onNext(FragmentEvent.DETACH);
viewModel.onDetach();
FragmentViewModelManager.getInstance().destroy(viewModel);
viewModel = null;
}
}
}
@CallSuper
@Override
public void onSaveInstanceState(final @NonNull Bundle outState) {
super.onSaveInstanceState(outState);
final Bundle viewModelEnvelope = new Bundle();
if (viewModel != null) {
FragmentViewModelManager.getInstance().save(viewModel, viewModelEnvelope);
}
outState.putBundle(VIEW_MODEL_KEY, viewModelEnvelope);
}
private void assignViewModel(final @Nullable Bundle viewModelEnvelope) {
if (viewModel == null) {
final RequiresFragmentViewModel annotation = getClass().getAnnotation(RequiresFragmentViewModel.class);
final Class<ViewModelType> viewModelClass = annotation == null ? null : (Class<ViewModelType>) annotation.value();
if (viewModelClass != null) {
viewModel = FragmentViewModelManager.getInstance().fetch(getContext(),
viewModelClass,
BundleUtils.maybeGetBundle(viewModelEnvelope, VIEW_MODEL_KEY));
}
}
}
}