package com.thebluealliance.androidclient.fragments; import com.google.android.gms.analytics.Tracker; import com.thebluealliance.androidclient.activities.DatafeedActivity; import com.thebluealliance.androidclient.api.ApiConstants; import com.thebluealliance.androidclient.binders.AbstractDataBinder; import com.thebluealliance.androidclient.binders.NoDataBinder; import com.thebluealliance.androidclient.datafeed.CacheableDatafeed; import com.thebluealliance.androidclient.datafeed.refresh.RefreshController; import com.thebluealliance.androidclient.datafeed.refresh.RefreshController.RefreshType; import com.thebluealliance.androidclient.datafeed.refresh.Refreshable; import com.thebluealliance.androidclient.di.components.FragmentComponent; import com.thebluealliance.androidclient.di.components.HasFragmentComponent; import com.thebluealliance.androidclient.models.NoDataViewParams; import com.thebluealliance.androidclient.subscribers.BaseAPISubscriber; import com.thebluealliance.androidclient.subscribers.EventBusSubscriber; import org.greenrobot.eventbus.EventBus; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.v4.app.Fragment; import javax.inject.Inject; import dagger.Lazy; import rx.Observable; import rx.schedulers.Schedulers; /** * Easy abstraction of Fragment datafeed bindings * * @param <T> Type returned by the API * @param <V> Type to be bound to a view * @param <S> {@link BaseAPISubscriber} that will take API Data -> prepare data to render * @param <B> {@link AbstractDataBinder} that will take prepared data -> view */ public abstract class DatafeedFragment <T, V, S extends BaseAPISubscriber<T, V>, B extends AbstractDataBinder<V>> extends Fragment implements Refreshable { @Inject protected S mSubscriber; @Inject protected B mBinder; @Inject protected EventBus mEventBus; @Inject protected Lazy<EventBusSubscriber> mEventBusSubscriber; @Inject protected NoDataBinder mNoDataBinder; @Inject protected Tracker mAnalyticsTracker; @Inject protected CacheableDatafeed mDatafeed; protected @Nullable RefreshController mRefreshController; protected Observable<? extends T> mObservable; protected FragmentComponent mComponent; protected String mRefreshTag; protected boolean isCurrentlyVisible; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getActivity() instanceof HasFragmentComponent) { mComponent = ((HasFragmentComponent) getActivity()).getComponent(); } inject(); if (getActivity() instanceof DatafeedActivity) { mRefreshController = ((DatafeedActivity) getActivity()).getRefreshController(); } else { mRefreshController = null; } isCurrentlyVisible = false; mRefreshTag = getRefreshTag(); mSubscriber.setConsumer(mBinder); mSubscriber.setRefreshController(mRefreshController); mSubscriber.setRefreshTag(mRefreshTag); mSubscriber.setTracker(mAnalyticsTracker); mBinder.setActivity(getActivity()); mBinder.setNoDataBinder(mNoDataBinder); mBinder.setNoDataParams(getNoDataParams()); } @Override public void onResume() { super.onResume(); getNewObservables(RefreshController.NOT_REQUESTED_BY_USER); if (mRefreshController != null) { mRefreshController.registerRefreshable(mRefreshTag, this); } if (shouldRegisterSubscriberToEventBus()) { mEventBus.register(mSubscriber); } if (shouldRegisterBinderToEventBus()) { mEventBus.register(mBinder); } } @Override public void onPause() { super.onPause(); if (mRefreshController != null) { mRefreshController.unregisterRefreshable(mRefreshTag); } if (mSubscriber != null) { if (shouldRegisterSubscriberToEventBus()) { mEventBus.unregister(mSubscriber); } } if (mBinder != null) { if (shouldRegisterBinderToEventBus()) { mEventBus.unregister(mBinder); } } } @Override public void onStop() { super.onStop(); if (mSubscriber != null) { mSubscriber.onParentStop(); } } @Override public void onDestroyView() { super.onDestroyView(); mBinder.unbind(true); } /** * Allows other things to bind this instance */ public void bind() { if (mSubscriber != null) { mSubscriber.bindData(); } } public boolean isBound() { return mBinder != null && mBinder.isDataBound(); } public void setShouldBindImmediately(boolean shouldBind) { if (mSubscriber != null) { mSubscriber.setShouldBindImmediately(shouldBind); } } public void setShouldBindOnce(boolean shouldBind) { if (mSubscriber != null) { mSubscriber.setShouldBindOnce(shouldBind); } } public void setIsCurrentlyVisible(boolean visible) { isCurrentlyVisible = visible; } /** * Registers and subscribes new observables */ private void getNewObservables(@RefreshType int refreshType) { if (mSubscriber != null) { mObservable = getObservable( refreshType == RefreshController.REQUESTED_BY_USER ? ApiConstants.TBA_CACHE_WEB : null); if (mObservable != null) { mObservable.subscribeOn(Schedulers.io()) .observeOn(Schedulers.computation()) .subscribe(mSubscriber); mSubscriber.onRefreshStart(refreshType); } } } @Override public void onRefreshStart(@RefreshType int refreshType) { if (mSubscriber != null && mBinder != null) { mBinder.unbind(false); setShouldBindOnce(isCurrentlyVisible); getNewObservables(refreshType); } } /** * Fragments should inject themselves (to preserve Dagger's strong typing) * They just need to have the following line: mComponent.inject(this); * Plus, whatever else they want * If the types don't match, add the fragment to * {@link com.thebluealliance.androidclient.subscribers.SubscriberModule} and rebuild * Called in {@link #onCreate(Bundle)} */ protected abstract void inject(); /** * For child to make a call to return the Observable containing the main data model * Called in {@link #onResume()} * * @param tbaCacheHeader String param to tell the datafeed how to load the data. Use * {@link ApiConstants#TBA_CACHE_WEB}, {@link ApiConstants#TBA_CACHE_LOCAL}, or {@code * null} for regular usage */ protected abstract Observable<? extends T> getObservable(String tbaCacheHeader); /** * @return A string identifying what data this fragment is loading */ protected abstract String getRefreshTag(); @VisibleForTesting public NoDataViewParams getNoDataParams() { return null; } @VisibleForTesting public B getBinder() { return mBinder; } protected boolean shouldRegisterSubscriberToEventBus() { return false; } protected boolean shouldRegisterBinderToEventBus() { return false; } }