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