diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiDialogFragment.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiDialogFragment.java index 7be714f2..b21f184d 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiDialogFragment.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiDialogFragment.java @@ -1,62 +1,53 @@ package net.grandcentrix.thirtyinch; +import net.grandcentrix.thirtyinch.internal.DelegatedTiFragment; +import net.grandcentrix.thirtyinch.internal.InterceptableViewBinder; +import net.grandcentrix.thirtyinch.internal.TiFragmentDelegate; +import net.grandcentrix.thirtyinch.internal.TiLoggingTagProvider; +import net.grandcentrix.thirtyinch.internal.TiPresenterProvider; +import net.grandcentrix.thirtyinch.internal.TiViewProvider; +import net.grandcentrix.thirtyinch.util.AndroidDeveloperOptions; +import net.grandcentrix.thirtyinch.util.AnnotationUtil; + import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.app.FragmentActivity; import android.support.v7.app.AppCompatDialogFragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThreadInterceptor; -import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChangedInterceptor; -import net.grandcentrix.thirtyinch.internal.InterceptableViewBinder; -import net.grandcentrix.thirtyinch.internal.PresenterSavior; -import net.grandcentrix.thirtyinch.internal.PresenterViewBinder; -import net.grandcentrix.thirtyinch.internal.TiLoggingTagProvider; -import net.grandcentrix.thirtyinch.internal.TiPresenterProvider; -import net.grandcentrix.thirtyinch.internal.TiViewProvider; -import net.grandcentrix.thirtyinch.util.AndroidDeveloperOptions; -import net.grandcentrix.thirtyinch.util.AnnotationUtil; - import java.util.List; public abstract class TiDialogFragment

, V extends TiView> - extends AppCompatDialogFragment implements TiPresenterProvider

, TiLoggingTagProvider, + extends AppCompatDialogFragment + implements DelegatedTiFragment, TiPresenterProvider

, TiLoggingTagProvider, TiViewProvider, InterceptableViewBinder { - private static final String SAVED_STATE_PRESENTER_ID = "presenter_id"; - private final String TAG = this.getClass().getSimpleName() + ":" + TiDialogFragment.class.getSimpleName() + "@" + Integer.toHexString(this.hashCode()); - private volatile boolean mActivityStarted = false; - - private P mPresenter; - - private String mPresenterId; - - private PresenterViewBinder mViewBinder = new PresenterViewBinder<>(this); + private final TiFragmentDelegate mDelegate = + new TiFragmentDelegate<>(this, this, this, this); @NonNull @Override public Removable addBindViewInterceptor(@NonNull final BindViewInterceptor interceptor) { - return mViewBinder.addBindViewInterceptor(interceptor); + return mDelegate.addBindViewInterceptor(interceptor); } @Nullable @Override public V getInterceptedViewOf(@NonNull final BindViewInterceptor interceptor) { - return mViewBinder.getInterceptedViewOf(interceptor); + return mDelegate.getInterceptedViewOf(interceptor); } @NonNull @Override public List getInterceptors( @NonNull final Filter predicate) { - return mViewBinder.getInterceptors(predicate); + return mDelegate.getInterceptors(predicate); } @Override @@ -65,7 +56,7 @@ public String getLoggingTag() { } public P getPresenter() { - return mPresenter; + return mDelegate.getPresenter(); } /** @@ -74,144 +65,83 @@ public P getPresenter() { */ @Override public void invalidateView() { - mViewBinder.invalidateView(); + mDelegate.invalidateView(); } @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public boolean isDontKeepActivitiesEnabled() { + return AndroidDeveloperOptions.isDontKeepActivitiesEnabled(getActivity()); + } - if (mPresenter == null && savedInstanceState != null) { - // recover with Savior - // this should always work. - final String recoveredPresenterId = savedInstanceState - .getString(SAVED_STATE_PRESENTER_ID); - if (recoveredPresenterId != null) { - TiLog.v(TAG, "try to recover Presenter with id: " + recoveredPresenterId); - //noinspection unchecked - mPresenter = (P) PresenterSavior.INSTANCE.recover(recoveredPresenterId); - if (mPresenter != null) { - // save recovered presenter with new id. No other instance of this activity, - // holding the presenter before, is now able to remove the reference to - // this presenter from the savior - PresenterSavior.INSTANCE.free(recoveredPresenterId); - mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); - } - TiLog.v(TAG, "recovered Presenter " + mPresenter); - } - } + @Override + public boolean isFragmentAdded() { + return isAdded(); + } - if (mPresenter == null) { - mPresenter = providePresenter(); - TiLog.v(TAG, "created Presenter: " + mPresenter); - final TiConfiguration config = mPresenter.getConfig(); - if (config.shouldRetainPresenter() && config.useStaticSaviorToRetain()) { - mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); - } - mPresenter.create(); - } + @Override + public boolean isFragmentDetached() { + return isDetached(); + } - final TiConfiguration config = mPresenter.getConfig(); - if (config.isCallOnMainThreadInterceptorEnabled()) { - addBindViewInterceptor(new CallOnMainThreadInterceptor()); - } + @Override + public boolean isHostingActivityChangingConfigurations() { + return getActivity().isChangingConfigurations(); + } - if (config.isDistinctUntilChangedInterceptorEnabled()) { - addBindViewInterceptor(new DistinctUntilChangedInterceptor()); - } + @Override + public boolean isHostingActivityFinishing() { + return getActivity().isFinishing(); + } - if (config.shouldRetainPresenter()) { - setRetainInstance(true); - } + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mDelegate.onCreate_afterSuper(savedInstanceState); } @Nullable @Override public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, - @Nullable final Bundle savedInstanceState) { - mViewBinder.invalidateView(); + @Nullable final Bundle savedInstanceState) { + mDelegate.invalidateView(); return super.onCreateView(inflater, container, savedInstanceState); } @Override public void onDestroy() { super.onDestroy(); - final FragmentActivity activity = getActivity(); - - boolean destroyPresenter = false; - if (activity.isFinishing()) { - // Probably a backpress and not a configuration change - // Activity will not be recreated and finally destroyed, also destroyed the presenter - destroyPresenter = true; - TiLog.v(TAG, "Activity is finishing, destroying presenter " + mPresenter); - } - - final TiConfiguration config = mPresenter.getConfig(); - if (!destroyPresenter && - !config.shouldRetainPresenter()) { - // configuration says the presenter should not be retained, a new presenter instance - // will be created and the current presenter should be destroyed - destroyPresenter = true; - TiLog.v(TAG, "presenter configured as not retaining, destroying " + mPresenter); - } - - if (!destroyPresenter && - !config.useStaticSaviorToRetain() - && AndroidDeveloperOptions.isDontKeepActivitiesEnabled(getActivity())) { - // configuration says the PresenterSavior should not be used. Retaining the presenter - // relays on the Activity nonConfigurationInstance which is always null when - // "don't keep activities" is enabled. - // a new presenter instance will be created and the current presenter should be destroyed - destroyPresenter = true; - TiLog.v(TAG, "the PresenterSavior is disabled and \"don\'t keep activities\" is " - + "activated. The presenter can't be retained. Destroying " + mPresenter); - } - - if (destroyPresenter) { - mPresenter.destroy(); - PresenterSavior.INSTANCE.free(mPresenterId); - } else { - TiLog.v(TAG, "not destroying " + mPresenter - + " which will be reused by the next Activity instance, recreating..."); - } + mDelegate.onDestroy_afterSuper(); } @Override public void onDestroyView() { - mPresenter.detachView(); + mDelegate.onDestroyView_beforeSuper(); super.onDestroyView(); } @Override public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); - outState.putString(SAVED_STATE_PRESENTER_ID, mPresenterId); + mDelegate.onSaveInstanceState_afterSuper(outState); } @Override public void onStart() { super.onStart(); - mActivityStarted = true; - - if (isUiPossible()) { - getActivity().getWindow().getDecorView().post(new Runnable() { - @Override - public void run() { - if (isUiPossible() && mActivityStarted) { - mViewBinder.bindView(mPresenter, TiDialogFragment.this); - } - } - }); - } + mDelegate.onStart_afterSuper(); } @Override public void onStop() { - mActivityStarted = false; - mPresenter.detachView(); + mDelegate.onStop_beforeSuper(); super.onStop(); } + @Override + public boolean postToMessageQueue(final Runnable runnable) { + return getActivity().getWindow().getDecorView().post(runnable); + } + /** * the default implementation assumes that the fragment is the view and implements the {@link * TiView} interface. Override this method for a different behaviour. @@ -222,11 +152,11 @@ public void onStop() { public V provideView() { final Class foundViewInterface = AnnotationUtil - .getInterfaceOfClassExtendingGivenInterface(this.getClass(), TiView.class); + .getInterfaceOfClassExtendingGivenInterface(getClass(), TiView.class); if (foundViewInterface == null) { throw new IllegalArgumentException( - "This Fragment doesn't implement a TiView interface. " + "This DialogFragment doesn't implement a TiView interface. " + "This is the default behaviour. Override provideView() to explicitly change this."); } else { if (foundViewInterface.getSimpleName().equals("TiView")) { @@ -241,6 +171,11 @@ public V provideView() { } } + @Override + public void setFragmentRetainInstance(final boolean retain) { + setRetainInstance(retain); + } + @Override public String toString() { String presenter = getPresenter() == null ? "null" : @@ -248,12 +183,7 @@ public String toString() { + "@" + Integer.toHexString(getPresenter().hashCode()); return getClass().getSimpleName() - + ":" + TiDialogFragment.class.getSimpleName() + "@" + Integer.toHexString(hashCode()) + "{presenter=" + presenter + "}"; } - - private boolean isUiPossible() { - return isAdded() && !isDetached(); - } } diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiFragment.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiFragment.java index 342b8a7b..9c598f07 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiFragment.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiFragment.java @@ -15,11 +15,9 @@ package net.grandcentrix.thirtyinch; -import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThreadInterceptor; -import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChangedInterceptor; +import net.grandcentrix.thirtyinch.internal.DelegatedTiFragment; import net.grandcentrix.thirtyinch.internal.InterceptableViewBinder; -import net.grandcentrix.thirtyinch.internal.PresenterSavior; -import net.grandcentrix.thirtyinch.internal.PresenterViewBinder; +import net.grandcentrix.thirtyinch.internal.TiFragmentDelegate; import net.grandcentrix.thirtyinch.internal.TiLoggingTagProvider; import net.grandcentrix.thirtyinch.internal.TiPresenterProvider; import net.grandcentrix.thirtyinch.internal.TiViewProvider; @@ -36,46 +34,34 @@ import java.util.List; -public abstract class TiFragment

, V extends TiView> - extends Fragment implements TiPresenterProvider

, TiLoggingTagProvider, +public abstract class TiFragment

, V extends TiView> extends Fragment + implements DelegatedTiFragment, TiPresenterProvider

, TiLoggingTagProvider, TiViewProvider, InterceptableViewBinder { - private static final String SAVED_STATE_PRESENTER_ID = "presenter_id"; - - /** - * enables debug logging during development - */ - private static final boolean ENABLE_DEBUG_LOGGING = false; - private final String TAG = this.getClass().getSimpleName() + ":" + TiFragment.class.getSimpleName() + "@" + Integer.toHexString(this.hashCode()); - private volatile boolean mActivityStarted = false; - - private P mPresenter; - - private String mPresenterId; - - private PresenterViewBinder mViewBinder = new PresenterViewBinder<>(this); + private final TiFragmentDelegate mDelegate = + new TiFragmentDelegate<>(this, this, this, this); @NonNull @Override public Removable addBindViewInterceptor(@NonNull final BindViewInterceptor interceptor) { - return mViewBinder.addBindViewInterceptor(interceptor); + return mDelegate.addBindViewInterceptor(interceptor); } @Nullable @Override public V getInterceptedViewOf(@NonNull final BindViewInterceptor interceptor) { - return mViewBinder.getInterceptedViewOf(interceptor); + return mDelegate.getInterceptedViewOf(interceptor); } @NonNull @Override public List getInterceptors( @NonNull final Filter predicate) { - return mViewBinder.getInterceptors(predicate); + return mDelegate.getInterceptors(predicate); } @Override @@ -84,7 +70,7 @@ public String getLoggingTag() { } public P getPresenter() { - return mPresenter; + return mDelegate.getPresenter(); } /** @@ -93,147 +79,83 @@ public P getPresenter() { */ @Override public void invalidateView() { - mViewBinder.invalidateView(); + mDelegate.invalidateView(); } @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public boolean isDontKeepActivitiesEnabled() { + return AndroidDeveloperOptions.isDontKeepActivitiesEnabled(getActivity()); + } - if (mPresenter == null && savedInstanceState != null) { - // recover with Savior - // this should always work. - final String recoveredPresenterId = savedInstanceState - .getString(SAVED_STATE_PRESENTER_ID); - if (recoveredPresenterId != null) { - TiLog.v(TAG, "try to recover Presenter with id: " + recoveredPresenterId); - //noinspection unchecked - mPresenter = (P) PresenterSavior.INSTANCE.recover(recoveredPresenterId); - if (mPresenter != null) { - // save recovered presenter with new id. No other instance of this activity, - // holding the presenter before, is now able to remove the reference to - // this presenter from the savior - PresenterSavior.INSTANCE.free(recoveredPresenterId); - mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); - } - TiLog.v(TAG, "recovered Presenter " + mPresenter); - } - } + @Override + public boolean isFragmentAdded() { + return isAdded(); + } - if (mPresenter == null) { - mPresenter = providePresenter(); - TiLog.v(TAG, "created Presenter: " + mPresenter); - final TiConfiguration config = mPresenter.getConfig(); - if (config.shouldRetainPresenter() && config.useStaticSaviorToRetain()) { - mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); - } - mPresenter.create(); - } + @Override + public boolean isFragmentDetached() { + return isDetached(); + } - final TiConfiguration config = mPresenter.getConfig(); - if (config.isCallOnMainThreadInterceptorEnabled()) { - addBindViewInterceptor(new CallOnMainThreadInterceptor()); - } + @Override + public boolean isHostingActivityChangingConfigurations() { + return getActivity().isChangingConfigurations(); + } - if (config.isDistinctUntilChangedInterceptorEnabled()) { - addBindViewInterceptor(new DistinctUntilChangedInterceptor()); - } + @Override + public boolean isHostingActivityFinishing() { + return getActivity().isFinishing(); + } - if (config.shouldRetainPresenter()) { - setRetainInstance(true); - } + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mDelegate.onCreate_afterSuper(savedInstanceState); } @Nullable @Override public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { - mViewBinder.invalidateView(); + mDelegate.onCreateView_beforeSuper(inflater, container, savedInstanceState); return super.onCreateView(inflater, container, savedInstanceState); } - @Override public void onDestroy() { super.onDestroy(); - //FIXME handle attach/detach state - - logState(); - - boolean destroyPresenter = false; - if (getActivity().isFinishing()) { - // Probably a backpress and not a configuration change - // Activity will not be recreated and finally destroyed, also destroyed the presenter - destroyPresenter = true; - TiLog.v(TAG, "Activity is finishing, destroying presenter " + mPresenter); - } - - final TiConfiguration config = mPresenter.getConfig(); - if (!destroyPresenter && - !config.shouldRetainPresenter()) { - // configuration says the presenter should not be retained, a new presenter instance - // will be created and the current presenter should be destroyed - destroyPresenter = true; - TiLog.v(TAG, "presenter configured as not retaining, destroying " + mPresenter); - } - - if (!destroyPresenter && - !config.useStaticSaviorToRetain() - && AndroidDeveloperOptions.isDontKeepActivitiesEnabled(getActivity())) { - // configuration says the PresenterSavior should not be used. Retaining the presenter - // relays on the Activity nonConfigurationInstance which is always null when - // "don't keep activities" is enabled. - // a new presenter instance will be created and the current presenter should be destroyed - destroyPresenter = true; - TiLog.v(TAG, "the PresenterSavior is disabled and \"don\'t keep activities\" is " - + "activated. The presenter can't be retained. Destroying " + mPresenter); - } - - if (destroyPresenter) { - mPresenter.destroy(); - PresenterSavior.INSTANCE.free(mPresenterId); - } else { - TiLog.v(TAG, "not destroying " + mPresenter - + " which will be reused by the next Activity instance, recreating..."); - } + mDelegate.onDestroy_afterSuper(); } @Override public void onDestroyView() { - mPresenter.detachView(); + mDelegate.onDestroyView_beforeSuper(); super.onDestroyView(); } @Override public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); - outState.putString(SAVED_STATE_PRESENTER_ID, mPresenterId); + mDelegate.onSaveInstanceState_afterSuper(outState); } @Override public void onStart() { super.onStart(); - mActivityStarted = true; - - if (isUiPossible()) { - getActivity().getWindow().getDecorView().post(new Runnable() { - @Override - public void run() { - if (isUiPossible() && mActivityStarted) { - mViewBinder.bindView(mPresenter, TiFragment.this); - } - } - }); - } + mDelegate.onStart_afterSuper(); } @Override public void onStop() { - mActivityStarted = false; - mPresenter.detachView(); + mDelegate.onStop_beforeSuper(); super.onStop(); } + @Override + public boolean postToMessageQueue(final Runnable runnable) { + return getActivity().getWindow().getDecorView().post(runnable); + } + /** * the default implementation assumes that the fragment is the view and implements the {@link * TiView} interface. Override this method for a different behaviour. @@ -244,7 +166,7 @@ public void onStop() { public V provideView() { final Class foundViewInterface = AnnotationUtil - .getInterfaceOfClassExtendingGivenInterface(this.getClass(), TiView.class); + .getInterfaceOfClassExtendingGivenInterface(getClass(), TiView.class); if (foundViewInterface == null) { throw new IllegalArgumentException( @@ -263,6 +185,11 @@ public V provideView() { } } + @Override + public void setFragmentRetainInstance(final boolean retain) { + setRetainInstance(retain); + } + @Override public String toString() { String presenter = getPresenter() == null ? "null" : @@ -270,27 +197,7 @@ public String toString() { + "@" + Integer.toHexString(getPresenter().hashCode()); return getClass().getSimpleName() - + ":" + TiFragment.class.getSimpleName() + "@" + Integer.toHexString(hashCode()) + "{presenter=" + presenter + "}"; } - - private boolean isUiPossible() { - return isAdded() && !isDetached(); - } - - private void logState() { - if (ENABLE_DEBUG_LOGGING) { - TiLog.v(TAG, "isChangingConfigurations = " + getActivity().isChangingConfigurations()); - TiLog.v(TAG, "isActivityFinishing = " + getActivity().isFinishing()); - TiLog.v(TAG, "isAdded = " + isAdded()); - TiLog.v(TAG, "isDetached = " + isDetached()); - TiLog.v(TAG, "isDontKeepActivitiesEnabled = " + AndroidDeveloperOptions - .isDontKeepActivitiesEnabled(getActivity())); - - final TiConfiguration config = mPresenter.getConfig(); - TiLog.v(TAG, "shouldRetain = " + config.shouldRetainPresenter()); - TiLog.v(TAG, "useStaticSavior = " + config.useStaticSaviorToRetain()); - } - } } \ No newline at end of file diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/DelegatedTiFragment.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/DelegatedTiFragment.java new file mode 100644 index 00000000..808c1354 --- /dev/null +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/DelegatedTiFragment.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 grandcentrix GmbH + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.grandcentrix.thirtyinch.internal; + +import android.app.Activity; +import android.support.v4.app.Fragment; + +public interface DelegatedTiFragment { + + /** + * @return true when the developer option "Don't keep Activities" is enabled + */ + boolean isDontKeepActivitiesEnabled(); + + /** + * @return {@link Fragment#isAdded()} + */ + boolean isFragmentAdded(); + + /** + * @return {@link Fragment#isDetached()} + */ + boolean isFragmentDetached(); + + /** + * @return {@link Activity#isChangingConfigurations()} + */ + boolean isHostingActivityChangingConfigurations(); + + /** + * @return {@link Activity#isFinishing()} + */ + boolean isHostingActivityFinishing(); + + /** + * Post the runnable on the UI queue + */ + boolean postToMessageQueue(Runnable runnable); + + /** + * Call {@link Fragment#setRetainInstance(boolean)} + */ + void setFragmentRetainInstance(final boolean retain); +} diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java new file mode 100644 index 00000000..cb25366b --- /dev/null +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/internal/TiFragmentDelegate.java @@ -0,0 +1,260 @@ +package net.grandcentrix.thirtyinch.internal; + +import net.grandcentrix.thirtyinch.BindViewInterceptor; +import net.grandcentrix.thirtyinch.Removable; +import net.grandcentrix.thirtyinch.TiConfiguration; +import net.grandcentrix.thirtyinch.TiDialogFragment; +import net.grandcentrix.thirtyinch.TiFragment; +import net.grandcentrix.thirtyinch.TiLog; +import net.grandcentrix.thirtyinch.TiPresenter; +import net.grandcentrix.thirtyinch.TiView; +import net.grandcentrix.thirtyinch.callonmainthread.CallOnMainThreadInterceptor; +import net.grandcentrix.thirtyinch.distinctuntilchanged.DistinctUntilChangedInterceptor; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import java.util.List; + +/** + * This delegate allows sharing the fragment code between the {@link TiFragment}, + * {@link TiDialogFragment}, {@code TiFragmentPlugin} and {@code TiDialogFragmentPlugin}. + *

+ * It also allows 3rd party developers do add this delegate to other Fragments using composition. + */ +public class TiFragmentDelegate

, V extends TiView> + implements InterceptableViewBinder { + + private static final String SAVED_STATE_PRESENTER_ID = "presenter_id"; + + /** + * enables debug logging during development + */ + private static final boolean ENABLE_DEBUG_LOGGING = false; + + private volatile boolean mActivityStarted = false; + + private final TiLoggingTagProvider mLogTag; + + private P mPresenter; + + private String mPresenterId; + + private final TiPresenterProvider

mPresenterProvider; + + private final DelegatedTiFragment mTiFragment; + + private final PresenterViewBinder mViewBinder; + + private final TiViewProvider mViewProvider; + + public TiFragmentDelegate(final DelegatedTiFragment fragmentProvider, + final TiViewProvider viewProvider, + final TiPresenterProvider

presenterProvider, + final TiLoggingTagProvider logTag) { + mTiFragment = fragmentProvider; + mViewProvider = viewProvider; + mPresenterProvider = presenterProvider; + mLogTag = logTag; + mViewBinder = new PresenterViewBinder<>(logTag); + } + + @NonNull + @Override + public Removable addBindViewInterceptor(@NonNull final BindViewInterceptor interceptor) { + return mViewBinder.addBindViewInterceptor(interceptor); + } + + @Nullable + @Override + public V getInterceptedViewOf(@NonNull final BindViewInterceptor interceptor) { + return mViewBinder.getInterceptedViewOf(interceptor); + } + + @NonNull + @Override + public List getInterceptors( + @NonNull final Filter predicate) { + return mViewBinder.getInterceptors(predicate); + } + + public P getPresenter() { + return mPresenter; + } + + /** + * Invalidates the cache of the latest bound view. Forces the next binding of the view to run + * through all the interceptors (again). + */ + @Override + public void invalidateView() { + mViewBinder.invalidateView(); + } + + @SuppressWarnings("UnusedParameters") + public void onCreateView_beforeSuper(final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { + mViewBinder.invalidateView(); + } + + public void onCreate_afterSuper(final Bundle savedInstanceState) { + if (mPresenter == null && savedInstanceState != null) { + // recover with Savior + // this should always work. + final String recoveredPresenterId = savedInstanceState + .getString(SAVED_STATE_PRESENTER_ID); + if (recoveredPresenterId != null) { + TiLog.v(mLogTag.getLoggingTag(), + "try to recover Presenter with id: " + recoveredPresenterId); + //noinspection unchecked + mPresenter = (P) PresenterSavior.INSTANCE.recover(recoveredPresenterId); + if (mPresenter != null) { + // save recovered presenter with new id. No other instance of this activity, + // holding the presenter before, is now able to remove the reference to + // this presenter from the savior + PresenterSavior.INSTANCE.free(recoveredPresenterId); + mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); + } + TiLog.v(mLogTag.getLoggingTag(), "recovered Presenter " + mPresenter); + } + } + + if (mPresenter == null) { + mPresenter = mPresenterProvider.providePresenter(); + TiLog.v(mLogTag.getLoggingTag(), "created Presenter: " + mPresenter); + final TiConfiguration config = mPresenter.getConfig(); + if (config.shouldRetainPresenter() && config.useStaticSaviorToRetain()) { + mPresenterId = PresenterSavior.INSTANCE.safe(mPresenter); + } + mPresenter.create(); + } + + final TiConfiguration config = mPresenter.getConfig(); + if (config.isCallOnMainThreadInterceptorEnabled()) { + addBindViewInterceptor(new CallOnMainThreadInterceptor()); + } + + if (config.isDistinctUntilChangedInterceptorEnabled()) { + addBindViewInterceptor(new DistinctUntilChangedInterceptor()); + } + + if (config.shouldRetainPresenter()) { + mTiFragment.setFragmentRetainInstance(true); + } + } + + public void onDestroyView_beforeSuper() { + mPresenter.detachView(); + } + + public void onDestroy_afterSuper() { + //FIXME handle attach/detach state + + logState(); + + boolean destroyPresenter = false; + if (mTiFragment.isHostingActivityFinishing()) { + // Probably a backpress and not a configuration change + // Activity will not be recreated and finally destroyed, also destroyed the presenter + destroyPresenter = true; + TiLog.v(mLogTag.getLoggingTag(), + "Activity is finishing, destroying presenter " + mPresenter); + } + + final TiConfiguration config = mPresenter.getConfig(); + if (!destroyPresenter && + !config.shouldRetainPresenter()) { + // configuration says the presenter should not be retained, a new presenter instance + // will be created and the current presenter should be destroyed + destroyPresenter = true; + TiLog.v(mLogTag.getLoggingTag(), + "presenter configured as not retaining, destroying " + mPresenter); + } + + if (!destroyPresenter && + !config.useStaticSaviorToRetain() && mTiFragment.isDontKeepActivitiesEnabled()) { + // configuration says the PresenterSavior should not be used. Retaining the presenter + // relays on the Activity nonConfigurationInstance which is always null when + // "don't keep activities" is enabled. + // a new presenter instance will be created and the current presenter should be destroyed + destroyPresenter = true; + TiLog.v(mLogTag.getLoggingTag(), + "the PresenterSavior is disabled and \"don\'t keep activities\" is " + + "activated. The presenter can't be retained. Destroying " + + mPresenter); + } + + if (destroyPresenter) { + mPresenter.destroy(); + PresenterSavior.INSTANCE.free(mPresenterId); + } else { + TiLog.v(mLogTag.getLoggingTag(), "not destroying " + mPresenter + + " which will be reused by the next Activity instance, recreating..."); + } + } + + public void onSaveInstanceState_afterSuper(final Bundle outState) { + outState.putString(SAVED_STATE_PRESENTER_ID, mPresenterId); + } + + public void onStart_afterSuper() { + mActivityStarted = true; + + if (isUiPossible()) { + mTiFragment.postToMessageQueue(new Runnable() { + @Override + public void run() { + if (isUiPossible() && mActivityStarted) { + mViewBinder.bindView(mPresenter, mViewProvider); + } + } + }); + } + } + + public void onStop_beforeSuper() { + mActivityStarted = false; + mPresenter.detachView(); + } + + @Override + public String toString() { + String presenter = getPresenter() == null ? "null" : + getPresenter().getClass().getSimpleName() + + "@" + Integer.toHexString(getPresenter().hashCode()); + + return getClass().getSimpleName() + + ":" + TiFragmentDelegate.class.getSimpleName() + + "@" + Integer.toHexString(hashCode()) + + "{presenter=" + presenter + "}"; + } + + private boolean isUiPossible() { + return mTiFragment.isFragmentAdded() && !mTiFragment.isFragmentDetached(); + } + + private void logState() { + if (ENABLE_DEBUG_LOGGING) { + TiLog.v(mLogTag.getLoggingTag(), "isChangingConfigurations = " + + mTiFragment.isHostingActivityChangingConfigurations()); + TiLog.v(mLogTag.getLoggingTag(), + "isHostingActivityFinishing = " + mTiFragment.isHostingActivityFinishing()); + TiLog.v(mLogTag.getLoggingTag(), + "isAdded = " + mTiFragment.isFragmentAdded()); + TiLog.v(mLogTag.getLoggingTag(), + "isDetached = " + mTiFragment.isFragmentDetached()); + TiLog.v(mLogTag.getLoggingTag(), + "isDontKeepActivitiesEnabled = " + mTiFragment.isDontKeepActivitiesEnabled()); + + final TiConfiguration config = mPresenter.getConfig(); + TiLog.v(mLogTag.getLoggingTag(), + "shouldRetain = " + config.shouldRetainPresenter()); + TiLog.v(mLogTag.getLoggingTag(), + "useStaticSavior = " + config.useStaticSaviorToRetain()); + } + } +} \ No newline at end of file