diff --git a/app/build.gradle b/app/build.gradle index 3b36c4c74..8d0c42c47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -141,9 +141,6 @@ dependencies { compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION" kapt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION" - // Model View Presenter - compile 'info.android15.nucleus:nucleus:2.0.5' - // Dependency injection compile "com.google.dagger:dagger:$DAGGER_VERSION" kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.java b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.java index ec5a12b5b..ee3003300 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.java @@ -87,8 +87,9 @@ public abstract class BaseRxActivity
extends BaseActivity i } @Override - protected void onPause() { - super.onPause(); - presenterDelegate.onPause(isFinishing()); + protected void onDestroy() { + super.onDestroy(); + presenterDelegate.onDropView(); + presenterDelegate.onDestroy(!isChangingConfigurations()); } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.java index 84044cdc8..e03e6a1ca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.java +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/fragment/BaseRxFragment.java @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.ui.base.fragment; import android.os.Bundle; -import android.support.v4.app.Fragment; import eu.kanade.tachiyomi.App; import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter; @@ -85,13 +84,14 @@ public abstract class BaseRxFragment
extends BaseFragment i } @Override - public void onPause() { - super.onPause(); - presenterDelegate.onPause(getActivity().isFinishing() || isRemoving(this)); + public void onDestroyView() { + super.onDestroyView(); + presenterDelegate.onDropView(); } - private static boolean isRemoving(Fragment fragment) { - Fragment parent = fragment.getParentFragment(); - return fragment.isRemoving() || (parent != null && isRemoving(parent)); + @Override + public void onDestroy() { + super.onDestroy(); + presenterDelegate.onDestroy(!getActivity().isChangingConfigurations()); } } diff --git a/app/src/main/java/nucleus/factory/PresenterFactory.java b/app/src/main/java/nucleus/factory/PresenterFactory.java new file mode 100644 index 000000000..22c0d1a3d --- /dev/null +++ b/app/src/main/java/nucleus/factory/PresenterFactory.java @@ -0,0 +1,7 @@ +package nucleus.factory; + +import nucleus.presenter.Presenter; + +public interface PresenterFactory
{
+ P createPresenter();
+}
diff --git a/app/src/main/java/nucleus/factory/PresenterStorage.java b/app/src/main/java/nucleus/factory/PresenterStorage.java
new file mode 100644
index 000000000..8c3692678
--- /dev/null
+++ b/app/src/main/java/nucleus/factory/PresenterStorage.java
@@ -0,0 +1,64 @@
+package nucleus.factory;
+
+import java.util.HashMap;
+
+import nucleus.presenter.Presenter;
+
+/**
+ * This is the singleton where all presenters are stored.
+ */
+public enum PresenterStorage {
+
+ INSTANCE;
+
+ private HashMap a type of presenter
+ * @return a presenter or null
+ */
+ public P getPresenter(String id) {
+ //noinspection unchecked
+ return (P)idToPresenter.get(id);
+ }
+
+ /**
+ * Returns id of a given presenter.
+ *
+ * @param presenter a presenter to get id for.
+ * @return if of the presenter.
+ */
+ public String getId(Presenter presenter) {
+ return presenterToId.get(presenter);
+ }
+
+ /**
+ * Removes all presenters from the storage.
+ * Use this method for testing purposes only.
+ */
+ public void clear() {
+ idToPresenter.clear();
+ presenterToId.clear();
+ }
+}
diff --git a/app/src/main/java/nucleus/factory/ReflectionPresenterFactory.java b/app/src/main/java/nucleus/factory/ReflectionPresenterFactory.java
new file mode 100644
index 000000000..b84d4fd0c
--- /dev/null
+++ b/app/src/main/java/nucleus/factory/ReflectionPresenterFactory.java
@@ -0,0 +1,45 @@
+package nucleus.factory;
+
+import android.support.annotation.Nullable;
+
+import nucleus.presenter.Presenter;
+
+/**
+ * This class represents a {@link PresenterFactory} that creates a presenter using {@link Class#newInstance()} method.
+ *
+ * @param the type of the presenter.
+ */
+public class ReflectionPresenterFactory implements PresenterFactory {
+
+ private Class presenterClass;
+
+ /**
+ * This method returns a {@link ReflectionPresenterFactory} instance if a given view class has
+ * a {@link RequiresPresenter} annotation, or null otherwise.
+ *
+ * @param viewClass a class of the view
+ * @param a type of the presenter
+ * @return a {@link ReflectionPresenterFactory} instance that is supposed to create a presenter from {@link RequiresPresenter} annotation.
+ */
+ @Nullable
+ public static ReflectionPresenterFactory fromViewClass(Class> viewClass) {
+ RequiresPresenter annotation = viewClass.getAnnotation(RequiresPresenter.class);
+ //noinspection unchecked
+ Class presenterClass = annotation == null ? null : (Class )annotation.value();
+ return presenterClass == null ? null : new ReflectionPresenterFactory<>(presenterClass);
+ }
+
+ public ReflectionPresenterFactory(Class presenterClass) {
+ this.presenterClass = presenterClass;
+ }
+
+ @Override
+ public P createPresenter() {
+ try {
+ return presenterClass.newInstance();
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/app/src/main/java/nucleus/factory/RequiresPresenter.java b/app/src/main/java/nucleus/factory/RequiresPresenter.java
new file mode 100644
index 000000000..081b47701
--- /dev/null
+++ b/app/src/main/java/nucleus/factory/RequiresPresenter.java
@@ -0,0 +1,13 @@
+package nucleus.factory;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import nucleus.presenter.Presenter;
+
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequiresPresenter {
+ Class extends Presenter> value();
+}
diff --git a/app/src/main/java/nucleus/presenter/Presenter.java b/app/src/main/java/nucleus/presenter/Presenter.java
new file mode 100644
index 000000000..0c986dfb0
--- /dev/null
+++ b/app/src/main/java/nucleus/presenter/Presenter.java
@@ -0,0 +1,164 @@
+package nucleus.presenter;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * This is a base class for all presenters. Subclasses can override
+ * {@link #onCreate}, {@link #onDestroy}, {@link #onSave},
+ * {@link #onTakeView}, {@link #onDropView}.
+ * a type of presenter to return with {@link #getPresenter}.
+ */
+public abstract class NucleusActivity extends Activity implements ViewWithPresenter {
+
+ private static final String PRESENTER_STATE_KEY = "presenter_state";
+
+ private PresenterLifecycleDelegate presenterDelegate =
+ new PresenterLifecycleDelegate<>(ReflectionPresenterFactory. fromViewClass(getClass()));
+
+ /**
+ * Returns a current presenter factory.
+ */
+ public PresenterFactory getPresenterFactory() {
+ return presenterDelegate.getPresenterFactory();
+ }
+
+ /**
+ * Sets a presenter factory.
+ * Call this method before onCreate/onFinishInflate to override default {@link ReflectionPresenterFactory} presenter factory.
+ * Use this method for presenter dependency injection.
+ */
+ @Override
+ public void setPresenterFactory(PresenterFactory presenterFactory) {
+ presenterDelegate.setPresenterFactory(presenterFactory);
+ }
+
+ /**
+ * Returns a current attached presenter.
+ * This method is guaranteed to return a non-null value between
+ * onResume/onPause and onAttachedToWindow/onDetachedFromWindow calls
+ * if the presenter factory returns a non-null value.
+ *
+ * @return a currently attached presenter or null.
+ */
+ public P getPresenter() {
+ return presenterDelegate.getPresenter();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null)
+ presenterDelegate.onRestoreInstanceState(savedInstanceState.getBundle(PRESENTER_STATE_KEY));
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBundle(PRESENTER_STATE_KEY, presenterDelegate.onSaveInstanceState());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ presenterDelegate.onResume(this);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ presenterDelegate.onDropView();
+ presenterDelegate.onDestroy(!isChangingConfigurations());
+ }
+}
diff --git a/app/src/main/java/nucleus/view/NucleusFragment.java b/app/src/main/java/nucleus/view/NucleusFragment.java
new file mode 100644
index 000000000..9979a2a8d
--- /dev/null
+++ b/app/src/main/java/nucleus/view/NucleusFragment.java
@@ -0,0 +1,82 @@
+package nucleus.view;
+
+import android.app.Fragment;
+import android.os.Bundle;
+
+import nucleus.factory.PresenterFactory;
+import nucleus.factory.ReflectionPresenterFactory;
+import nucleus.presenter.Presenter;
+
+/**
+ * This view is an example of how a view should control it's presenter.
+ * You can inherit from this class or copy/paste this class's code to
+ * create your own view implementation.
+ *
+ * @param a type of presenter to return with {@link #getPresenter}.
+ */
+public abstract class NucleusFragment extends Fragment implements ViewWithPresenter {
+
+ private static final String PRESENTER_STATE_KEY = "presenter_state";
+ private PresenterLifecycleDelegate presenterDelegate =
+ new PresenterLifecycleDelegate<>(ReflectionPresenterFactory. fromViewClass(getClass()));
+
+ /**
+ * Returns a current presenter factory.
+ */
+ public PresenterFactory getPresenterFactory() {
+ return presenterDelegate.getPresenterFactory();
+ }
+
+ /**
+ * Sets a presenter factory.
+ * Call this method before onCreate/onFinishInflate to override default {@link ReflectionPresenterFactory} presenter factory.
+ * Use this method for presenter dependency injection.
+ */
+ @Override
+ public void setPresenterFactory(PresenterFactory presenterFactory) {
+ presenterDelegate.setPresenterFactory(presenterFactory);
+ }
+
+ /**
+ * Returns a current attached presenter.
+ * This method is guaranteed to return a non-null value between
+ * onResume/onPause and onAttachedToWindow/onDetachedFromWindow calls
+ * if the presenter factory returns a non-null value.
+ *
+ * @return a currently attached presenter or null.
+ */
+ public P getPresenter() {
+ return presenterDelegate.getPresenter();
+ }
+
+ @Override
+ public void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ if (bundle != null)
+ presenterDelegate.onRestoreInstanceState(bundle.getBundle(PRESENTER_STATE_KEY));
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle bundle) {
+ super.onSaveInstanceState(bundle);
+ bundle.putBundle(PRESENTER_STATE_KEY, presenterDelegate.onSaveInstanceState());
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ presenterDelegate.onResume(this);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ presenterDelegate.onDropView();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ presenterDelegate.onDestroy(!getActivity().isChangingConfigurations());
+ }
+}
diff --git a/app/src/main/java/nucleus/view/NucleusLayout.java b/app/src/main/java/nucleus/view/NucleusLayout.java
new file mode 100644
index 000000000..8e9c57f52
--- /dev/null
+++ b/app/src/main/java/nucleus/view/NucleusLayout.java
@@ -0,0 +1,113 @@
+package nucleus.view;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+import nucleus.factory.PresenterFactory;
+import nucleus.factory.ReflectionPresenterFactory;
+import nucleus.presenter.Presenter;
+
+/**
+ * This view is an example of how a view should control it's presenter.
+ * You can inherit from this class or copy/paste this class's code to
+ * create your own view implementation.
+ *
+ * @param a type of presenter to return with {@link #getPresenter}.
+ */
+public class NucleusLayout extends FrameLayout implements ViewWithPresenter {
+
+ private static final String PARENT_STATE_KEY = "parent_state";
+ private static final String PRESENTER_STATE_KEY = "presenter_state";
+
+ private PresenterLifecycleDelegate presenterDelegate =
+ new PresenterLifecycleDelegate<>(ReflectionPresenterFactory. fromViewClass(getClass()));
+
+ public NucleusLayout(Context context) {
+ super(context);
+ }
+
+ public NucleusLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public NucleusLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ /**
+ * Returns a current presenter factory.
+ */
+ public PresenterFactory getPresenterFactory() {
+ return presenterDelegate.getPresenterFactory();
+ }
+
+ /**
+ * Sets a presenter factory.
+ * Call this method before onCreate/onFinishInflate to override default {@link ReflectionPresenterFactory} presenter factory.
+ * Use this method for presenter dependency injection.
+ */
+ @Override
+ public void setPresenterFactory(PresenterFactory presenterFactory) {
+ presenterDelegate.setPresenterFactory(presenterFactory);
+ }
+
+ /**
+ * Returns a current attached presenter.
+ * This method is guaranteed to return a non-null value between
+ * onResume/onPause and onAttachedToWindow/onDetachedFromWindow calls
+ * if the presenter factory returns a non-null value.
+ *
+ * @return a currently attached presenter or null.
+ */
+ public P getPresenter() {
+ return presenterDelegate.getPresenter();
+ }
+
+ /**
+ * Returns the unwrapped activity of the view or throws an exception.
+ *
+ * @return an unwrapped activity
+ */
+ public Activity getActivity() {
+ Context context = getContext();
+ while (!(context instanceof Activity) && context instanceof ContextWrapper)
+ context = ((ContextWrapper) context).getBaseContext();
+ if (!(context instanceof Activity))
+ throw new IllegalStateException("Expected an activity context, got " + context.getClass().getSimpleName());
+ return (Activity) context;
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Bundle bundle = new Bundle();
+ bundle.putBundle(PRESENTER_STATE_KEY, presenterDelegate.onSaveInstanceState());
+ bundle.putParcelable(PARENT_STATE_KEY, super.onSaveInstanceState());
+ return bundle;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ Bundle bundle = (Bundle) state;
+ super.onRestoreInstanceState(bundle.getParcelable(PARENT_STATE_KEY));
+ presenterDelegate.onRestoreInstanceState(bundle.getBundle(PRESENTER_STATE_KEY));
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (!isInEditMode())
+ presenterDelegate.onResume(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ presenterDelegate.onDropView();
+ presenterDelegate.onDestroy(!getActivity().isChangingConfigurations());
+ }
+}
diff --git a/app/src/main/java/nucleus/view/ParcelFn.java b/app/src/main/java/nucleus/view/ParcelFn.java
new file mode 100644
index 000000000..5b4fedca7
--- /dev/null
+++ b/app/src/main/java/nucleus/view/ParcelFn.java
@@ -0,0 +1,25 @@
+package nucleus.view;
+
+import android.os.Parcel;
+
+class ParcelFn {
+
+ private static final ClassLoader CLASS_LOADER = ParcelFn.class.getClassLoader();
+
+ static a type of the presenter.
+ */
+public final class PresenterLifecycleDelegate {
+
+ private static final String PRESENTER_KEY = "presenter";
+ private static final String PRESENTER_ID_KEY = "presenter_id";
+
+ @Nullable private PresenterFactory presenterFactory;
+ @Nullable private P presenter;
+ @Nullable private Bundle bundle;
+
+ private boolean presenterHasView;
+
+ public PresenterLifecycleDelegate(@Nullable PresenterFactory presenterFactory) {
+ this.presenterFactory = presenterFactory;
+ }
+
+ /**
+ * {@link ViewWithPresenter#getPresenterFactory()}
+ */
+ @Nullable
+ public PresenterFactory getPresenterFactory() {
+ return presenterFactory;
+ }
+
+ /**
+ * {@link ViewWithPresenter#setPresenterFactory(PresenterFactory)}
+ */
+ public void setPresenterFactory(@Nullable PresenterFactory presenterFactory) {
+ if (presenter != null)
+ throw new IllegalArgumentException("setPresenterFactory() should be called before onResume()");
+ this.presenterFactory = presenterFactory;
+ }
+
+ /**
+ * {@link ViewWithPresenter#getPresenter()}
+ */
+ public P getPresenter() {
+ if (presenterFactory != null) {
+ if (presenter == null && bundle != null)
+ presenter = PresenterStorage.INSTANCE.getPresenter(bundle.getString(PRESENTER_ID_KEY));
+
+ if (presenter == null) {
+ presenter = presenterFactory.createPresenter();
+ PresenterStorage.INSTANCE.add(presenter);
+ presenter.create(bundle == null ? null : bundle.getBundle(PRESENTER_KEY));
+ }
+ bundle = null;
+ }
+ return presenter;
+ }
+
+ /**
+ * {@link android.app.Activity#onSaveInstanceState(Bundle)}, {@link android.app.Fragment#onSaveInstanceState(Bundle)}, {@link android.view.View#onSaveInstanceState()}.
+ */
+ public Bundle onSaveInstanceState() {
+ Bundle bundle = new Bundle();
+ getPresenter();
+ if (presenter != null) {
+ Bundle presenterBundle = new Bundle();
+ presenter.save(presenterBundle);
+ bundle.putBundle(PRESENTER_KEY, presenterBundle);
+ bundle.putString(PRESENTER_ID_KEY, PresenterStorage.INSTANCE.getId(presenter));
+ }
+ return bundle;
+ }
+
+ /**
+ * {@link android.app.Activity#onCreate(Bundle)}, {@link android.app.Fragment#onCreate(Bundle)}, {@link android.view.View#onRestoreInstanceState(Parcelable)}.
+ */
+ public void onRestoreInstanceState(Bundle presenterState) {
+ if (presenter != null)
+ throw new IllegalArgumentException("onRestoreInstanceState() should be called before onResume()");
+ this.bundle = ParcelFn.unmarshall(ParcelFn.marshall(presenterState));
+ }
+
+ /**
+ * {@link android.app.Activity#onResume()},
+ * {@link android.app.Fragment#onResume()},
+ * {@link android.view.View#onAttachedToWindow()}
+ */
+ public void onResume(Object view) {
+ getPresenter();
+ if (presenter != null && !presenterHasView) {
+ //noinspection unchecked
+ presenter.takeView(view);
+ presenterHasView = true;
+ }
+ }
+
+ /**
+ * {@link android.app.Activity#onDestroy()},
+ * {@link android.app.Fragment#onDestroyView()},
+ * {@link android.view.View#onDetachedFromWindow()}
+ */
+ public void onDropView() {
+ if (presenter != null && presenterHasView) {
+ presenter.dropView();
+ presenterHasView = false;
+ }
+ }
+
+ /**
+ * {@link android.app.Activity#onDestroy()},
+ * {@link android.app.Fragment#onDestroy()},
+ * {@link android.view.View#onDetachedFromWindow()}
+ */
+ public void onDestroy(boolean isFinal) {
+ if (presenter != null && isFinal) {
+ presenter.destroy();
+ presenter = null;
+ }
+ }
+}
diff --git a/app/src/main/java/nucleus/view/ViewWithPresenter.java b/app/src/main/java/nucleus/view/ViewWithPresenter.java
new file mode 100644
index 000000000..979065a5a
--- /dev/null
+++ b/app/src/main/java/nucleus/view/ViewWithPresenter.java
@@ -0,0 +1,30 @@
+package nucleus.view;
+
+import nucleus.factory.PresenterFactory;
+import nucleus.factory.ReflectionPresenterFactory;
+import nucleus.presenter.Presenter;
+
+public interface ViewWithPresenter {
+
+ /**
+ * Returns a current presenter factory.
+ */
+ PresenterFactory getPresenterFactory();
+
+ /**
+ * Sets a presenter factory.
+ * Call this method before onCreate/onFinishInflate to override default {@link ReflectionPresenterFactory} presenter factory.
+ * Use this method for presenter dependency injection.
+ */
+ void setPresenterFactory(PresenterFactory presenterFactory);
+
+ /**
+ * Returns a current attached presenter.
+ * This method is guaranteed to return a non-null value between
+ * onResume/onPause and onAttachedToWindow/onDetachedFromWindow calls
+ * if the presenter factory returns a non-null value.
+ *
+ * @return a currently attached presenter or null.
+ */
+ P getPresenter();
+}