Эх сурвалжийг харах

Temporarily include nucleus in the project

len 9 жил өмнө
parent
commit
447dfd1e3c

+ 0 - 3
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"

+ 4 - 3
app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.java

@@ -87,8 +87,9 @@ public abstract class BaseRxActivity<P extends Presenter> extends BaseActivity i
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
-        presenterDelegate.onPause(isFinishing());
+    protected void onDestroy() {
+        super.onDestroy();
+        presenterDelegate.onDropView();
+        presenterDelegate.onDestroy(!isChangingConfigurations());
     }
 }

+ 7 - 7
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<P extends Presenter> 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());
     }
 }

+ 7 - 0
app/src/main/java/nucleus/factory/PresenterFactory.java

@@ -0,0 +1,7 @@
+package nucleus.factory;
+
+import nucleus.presenter.Presenter;
+
+public interface PresenterFactory<P extends Presenter> {
+    P createPresenter();
+}

+ 64 - 0
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<String, Presenter> idToPresenter = new HashMap<>();
+    private HashMap<Presenter, String> presenterToId = new HashMap<>();
+
+    /**
+     * Adds a presenter to the storage
+     *
+     * @param presenter a presenter to add
+     */
+    public void add(final Presenter presenter) {
+        String id = presenter.getClass().getSimpleName() + "/" + System.nanoTime() + "/" + (int)(Math.random() * Integer.MAX_VALUE);
+        idToPresenter.put(id, presenter);
+        presenterToId.put(presenter, id);
+        presenter.addOnDestroyListener(new Presenter.OnDestroyListener() {
+            @Override
+            public void onDestroy() {
+                idToPresenter.remove(presenterToId.remove(presenter));
+            }
+        });
+    }
+
+    /**
+     * Returns a presenter by id or null if such presenter does not exist.
+     *
+     * @param id  id of a presenter that has been received by calling {@link #getId(Presenter)}
+     * @param <P> a type of presenter
+     * @return a presenter or null
+     */
+    public <P> 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();
+    }
+}

+ 45 - 0
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 <P> the type of the presenter.
+ */
+public class ReflectionPresenterFactory<P extends Presenter> implements PresenterFactory<P> {
+
+    private Class<P> 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 <P>       a type of the presenter
+     * @return a {@link ReflectionPresenterFactory} instance that is supposed to create a presenter from {@link RequiresPresenter} annotation.
+     */
+    @Nullable
+    public static <P extends Presenter> ReflectionPresenterFactory<P> fromViewClass(Class<?> viewClass) {
+        RequiresPresenter annotation = viewClass.getAnnotation(RequiresPresenter.class);
+        //noinspection unchecked
+        Class<P> presenterClass = annotation == null ? null : (Class<P>)annotation.value();
+        return presenterClass == null ? null : new ReflectionPresenterFactory<>(presenterClass);
+    }
+
+    public ReflectionPresenterFactory(Class<P> presenterClass) {
+        this.presenterClass = presenterClass;
+    }
+
+    @Override
+    public P createPresenter() {
+        try {
+            return presenterClass.newInstance();
+        }
+        catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 13 - 0
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();
+}

+ 164 - 0
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}.
+ * <p/>
+ * {@link Presenter.OnDestroyListener} can also be used by external classes
+ * to be notified about the need of freeing resources.
+ *
+ * @param <View> a type of view to return with {@link #getView()}.
+ */
+public class Presenter<View> {
+
+    @Nullable private View view;
+    private CopyOnWriteArrayList<OnDestroyListener> onDestroyListeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * This method is called after presenter construction.
+     *
+     * This method is intended for overriding.
+     *
+     * @param savedState If the presenter is being re-instantiated after a process restart then this Bundle
+     *                   contains the data it supplied in {@link #onSave}.
+     */
+    protected void onCreate(@Nullable Bundle savedState) {
+    }
+
+    /**
+     * This method is being called when a user leaves view.
+     *
+     * This method is intended for overriding.
+     */
+    protected void onDestroy() {
+    }
+
+    /**
+     * A returned state is the state that will be passed to {@link #onCreate} for a new presenter instance after a process restart.
+     *
+     * This method is intended for overriding.
+     *
+     * @param state a non-null bundle which should be used to put presenter's state into.
+     */
+    protected void onSave(Bundle state) {
+    }
+
+    /**
+     * This method is being called when a view gets attached to it.
+     * Normally this happens during {@link Activity#onResume()}, {@link android.app.Fragment#onResume()}
+     * and {@link android.view.View#onAttachedToWindow()}.
+     *
+     * This method is intended for overriding.
+     *
+     * @param view a view that should be taken
+     */
+    protected void onTakeView(View view) {
+    }
+
+    /**
+     * This method is being called when a view gets detached from the presenter.
+     * Normally this happens during {@link Activity#onPause()} ()}, {@link Fragment#onPause()} ()}
+     * and {@link android.view.View#onDetachedFromWindow()}.
+     *
+     * This method is intended for overriding.
+     */
+    protected void onDropView() {
+    }
+
+    /**
+     * A callback to be invoked when a presenter is about to be destroyed.
+     */
+    public interface OnDestroyListener {
+        /**
+         * Called before {@link Presenter#onDestroy()}.
+         */
+        void onDestroy();
+    }
+
+    /**
+     * Adds a listener observing {@link #onDestroy}.
+     *
+     * @param listener a listener to add.
+     */
+    public void addOnDestroyListener(OnDestroyListener listener) {
+        onDestroyListeners.add(listener);
+    }
+
+    /**
+     * Removed a listener observing {@link #onDestroy}.
+     *
+     * @param listener a listener to remove.
+     */
+    public void removeOnDestroyListener(OnDestroyListener listener) {
+        onDestroyListeners.remove(listener);
+    }
+
+    /**
+     * Returns a current view attached to the presenter or null.
+     *
+     * View is normally available between
+     * {@link Activity#onResume()} and {@link Activity#onPause()},
+     * {@link Fragment#onResume()} and {@link Fragment#onPause()},
+     * {@link android.view.View#onAttachedToWindow()} and {@link android.view.View#onDetachedFromWindow()}.
+     *
+     * Calls outside of these ranges will return null.
+     * Notice here that {@link Activity#onActivityResult(int, int, Intent)} is called *before* {@link Activity#onResume()}
+     * so you can't use this method as a callback.
+     *
+     * @return a current attached view.
+     */
+    @Nullable
+    public View getView() {
+        return view;
+    }
+
+    /**
+     * Initializes the presenter.
+     */
+    public void create(Bundle bundle) {
+        onCreate(bundle);
+    }
+
+    /**
+     * Destroys the presenter, calling all {@link Presenter.OnDestroyListener} callbacks.
+     */
+    public void destroy() {
+        for (OnDestroyListener listener : onDestroyListeners)
+            listener.onDestroy();
+        onDestroy();
+    }
+
+    /**
+     * Saves the presenter.
+     */
+    public void save(Bundle state) {
+        onSave(state);
+    }
+
+    /**
+     * Attaches a view to the presenter.
+     *
+     * @param view a view to attach.
+     */
+    public void takeView(View view) {
+        this.view = view;
+        onTakeView(view);
+    }
+
+    /**
+     * Detaches the presenter from a view.
+     */
+    public void dropView() {
+        onDropView();
+        this.view = null;
+    }
+}

+ 342 - 0
app/src/main/java/nucleus/presenter/RxPresenter.java

@@ -0,0 +1,342 @@
+package nucleus.presenter;
+
+import android.os.Bundle;
+import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import nucleus.presenter.delivery.DeliverFirst;
+import nucleus.presenter.delivery.DeliverLatestCache;
+import nucleus.presenter.delivery.DeliverReplay;
+import nucleus.presenter.delivery.Delivery;
+import rx.Observable;
+import rx.Subscription;
+import rx.functions.Action1;
+import rx.functions.Action2;
+import rx.functions.Func0;
+import rx.internal.util.SubscriptionList;
+import rx.subjects.BehaviorSubject;
+
+/**
+ * This is an extension of {@link Presenter} which provides RxJava functionality.
+ *
+ * @param <View> a type of view.
+ */
+public class RxPresenter<View> extends Presenter<View> {
+
+    private static final String REQUESTED_KEY = RxPresenter.class.getName() + "#requested";
+
+    private final BehaviorSubject<View> views = BehaviorSubject.create();
+    private final SubscriptionList subscriptions = new SubscriptionList();
+
+    private final HashMap<Integer, Func0<Subscription>> restartables = new HashMap<>();
+    private final HashMap<Integer, Subscription> restartableSubscriptions = new HashMap<>();
+    private final ArrayList<Integer> requested = new ArrayList<>();
+
+    /**
+     * Returns an {@link rx.Observable} that emits the current attached view or null.
+     * See {@link BehaviorSubject} for more information.
+     *
+     * @return an observable that emits the current attached view or null.
+     */
+    public Observable<View> view() {
+        return views;
+    }
+
+    /**
+     * Registers a subscription to automatically unsubscribe it during onDestroy.
+     * See {@link SubscriptionList#add(Subscription) for details.}
+     *
+     * @param subscription a subscription to add.
+     */
+    public void add(Subscription subscription) {
+        subscriptions.add(subscription);
+    }
+
+    /**
+     * Removes and unsubscribes a subscription that has been registered with {@link #add} previously.
+     * See {@link SubscriptionList#remove(Subscription)} for details.
+     *
+     * @param subscription a subscription to remove.
+     */
+    public void remove(Subscription subscription) {
+        subscriptions.remove(subscription);
+    }
+
+    /**
+     * A restartable is any RxJava observable that can be started (subscribed) and
+     * should be automatically restarted (re-subscribed) after a process restart if
+     * it was still subscribed at the moment of saving presenter's state.
+     *
+     * Registers a factory. Re-subscribes the restartable after the process restart.
+     *
+     * @param restartableId id of the restartable
+     * @param factory       factory of the restartable
+     */
+    public void restartable(int restartableId, Func0<Subscription> factory) {
+        restartables.put(restartableId, factory);
+        if (requested.contains(restartableId))
+            start(restartableId);
+    }
+
+    /**
+     * Starts the given restartable.
+     *
+     * @param restartableId id of the restartable
+     */
+    public void start(int restartableId) {
+        stop(restartableId);
+        requested.add(restartableId);
+        restartableSubscriptions.put(restartableId, restartables.get(restartableId).call());
+    }
+
+    /**
+     * Unsubscribes a restartable
+     *
+     * @param restartableId id of a restartable.
+     */
+    public void stop(int restartableId) {
+        requested.remove((Integer) restartableId);
+        Subscription subscription = restartableSubscriptions.get(restartableId);
+        if (subscription != null)
+            subscription.unsubscribe();
+    }
+
+    /**
+     * Checks if a restartable is unsubscribed.
+     *
+     * @param restartableId id of the restartable.
+     * @return true if the subscription is null or unsubscribed, false otherwise.
+     */
+    public boolean isUnsubscribed(int restartableId) {
+        Subscription subscription = restartableSubscriptions.get(restartableId);
+        return subscription == null || subscription.isUnsubscribed();
+    }
+
+    /**
+     * This is a shortcut that can be used instead of combining together
+     * {@link #restartable(int, Func0)},
+     * {@link #deliverFirst()},
+     * {@link #split(Action2, Action2)}.
+     *
+     * @param restartableId     an id of the restartable.
+     * @param observableFactory a factory that should return an Observable when the restartable should run.
+     * @param onNext            a callback that will be called when received data should be delivered to view.
+     * @param onError           a callback that will be called if the source observable emits onError.
+     * @param <T>               the type of the observable.
+     */
+    public <T> void restartableFirst(int restartableId, final Func0<Observable<T>> observableFactory,
+        final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
+
+        restartable(restartableId, new Func0<Subscription>() {
+            @Override
+            public Subscription call() {
+                return observableFactory.call()
+                    .compose(RxPresenter.this.<T>deliverFirst())
+                    .subscribe(split(onNext, onError));
+            }
+        });
+    }
+
+    /**
+     * This is a shortcut for calling {@link #restartableFirst(int, Func0, Action2, Action2)} with the last parameter = null.
+     */
+    public <T> void restartableFirst(int restartableId, final Func0<Observable<T>> observableFactory, final Action2<View, T> onNext) {
+        restartableFirst(restartableId, observableFactory, onNext, null);
+    }
+
+    /**
+     * This is a shortcut that can be used instead of combining together
+     * {@link #restartable(int, Func0)},
+     * {@link #deliverLatestCache()},
+     * {@link #split(Action2, Action2)}.
+     *
+     * @param restartableId     an id of the restartable.
+     * @param observableFactory a factory that should return an Observable when the restartable should run.
+     * @param onNext            a callback that will be called when received data should be delivered to view.
+     * @param onError           a callback that will be called if the source observable emits onError.
+     * @param <T>               the type of the observable.
+     */
+    public <T> void restartableLatestCache(int restartableId, final Func0<Observable<T>> observableFactory,
+        final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
+
+        restartable(restartableId, new Func0<Subscription>() {
+            @Override
+            public Subscription call() {
+                return observableFactory.call()
+                    .compose(RxPresenter.this.<T>deliverLatestCache())
+                    .subscribe(split(onNext, onError));
+            }
+        });
+    }
+
+    /**
+     * This is a shortcut for calling {@link #restartableLatestCache(int, Func0, Action2, Action2)} with the last parameter = null.
+     */
+    public <T> void restartableLatestCache(int restartableId, final Func0<Observable<T>> observableFactory, final Action2<View, T> onNext) {
+        restartableLatestCache(restartableId, observableFactory, onNext, null);
+    }
+
+    /**
+     * This is a shortcut that can be used instead of combining together
+     * {@link #restartable(int, Func0)},
+     * {@link #deliverReplay()},
+     * {@link #split(Action2, Action2)}.
+     *
+     * @param restartableId     an id of the restartable.
+     * @param observableFactory a factory that should return an Observable when the restartable should run.
+     * @param onNext            a callback that will be called when received data should be delivered to view.
+     * @param onError           a callback that will be called if the source observable emits onError.
+     * @param <T>               the type of the observable.
+     */
+    public <T> void restartableReplay(int restartableId, final Func0<Observable<T>> observableFactory,
+        final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
+
+        restartable(restartableId, new Func0<Subscription>() {
+            @Override
+            public Subscription call() {
+                return observableFactory.call()
+                    .compose(RxPresenter.this.<T>deliverReplay())
+                    .subscribe(split(onNext, onError));
+            }
+        });
+    }
+
+    /**
+     * This is a shortcut for calling {@link #restartableReplay(int, Func0, Action2, Action2)} with the last parameter = null.
+     */
+    public <T> void restartableReplay(int restartableId, final Func0<Observable<T>> observableFactory, final Action2<View, T> onNext) {
+        restartableReplay(restartableId, observableFactory, onNext, null);
+    }
+
+    /**
+     * Returns an {@link rx.Observable.Transformer} that couples views with data that has been emitted by
+     * the source {@link rx.Observable}.
+     *
+     * {@link #deliverLatestCache} keeps the latest onNext value and emits it each time a new view gets attached.
+     * If a new onNext value appears while a view is attached, it will be delivered immediately.
+     *
+     * @param <T> the type of source observable emissions
+     */
+    public <T> DeliverLatestCache<View, T> deliverLatestCache() {
+        return new DeliverLatestCache<>(views);
+    }
+
+    /**
+     * Returns an {@link rx.Observable.Transformer} that couples views with data that has been emitted by
+     * the source {@link rx.Observable}.
+     *
+     * {@link #deliverFirst} delivers only the first onNext value that has been emitted by the source observable.
+     *
+     * @param <T> the type of source observable emissions
+     */
+    public <T> DeliverFirst<View, T> deliverFirst() {
+        return new DeliverFirst<>(views);
+    }
+
+    /**
+     * Returns an {@link rx.Observable.Transformer} that couples views with data that has been emitted by
+     * the source {@link rx.Observable}.
+     *
+     * {@link #deliverReplay} keeps all onNext values and emits them each time a new view gets attached.
+     * If a new onNext value appears while a view is attached, it will be delivered immediately.
+     *
+     * @param <T> the type of source observable emissions
+     */
+    public <T> DeliverReplay<View, T> deliverReplay() {
+        return new DeliverReplay<>(views);
+    }
+
+    /**
+     * Returns a method that can be used for manual restartable chain build. It returns an Action1 that splits
+     * a received {@link Delivery} into two {@link Action2} onNext and onError calls.
+     *
+     * @param onNext  a method that will be called if the delivery contains an emitted onNext value.
+     * @param onError a method that will be called if the delivery contains an onError throwable.
+     * @param <T>     a type on onNext value.
+     * @return an Action1 that splits a received {@link Delivery} into two {@link Action2} onNext and onError calls.
+     */
+    public <T> Action1<Delivery<View, T>> split(final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
+        return new Action1<Delivery<View, T>>() {
+            @Override
+            public void call(Delivery<View, T> delivery) {
+                delivery.split(onNext, onError);
+            }
+        };
+    }
+
+    /**
+     * This is a shortcut for calling {@link #split(Action2, Action2)} when the second parameter is null.
+     */
+    public <T> Action1<Delivery<View, T>> split(Action2<View, T> onNext) {
+        return split(onNext, null);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @CallSuper
+    @Override
+    protected void onCreate(Bundle savedState) {
+        if (savedState != null)
+            requested.addAll(savedState.getIntegerArrayList(REQUESTED_KEY));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @CallSuper
+    @Override
+    protected void onDestroy() {
+        views.onCompleted();
+        subscriptions.unsubscribe();
+        for (Map.Entry<Integer, Subscription> entry : restartableSubscriptions.entrySet())
+            entry.getValue().unsubscribe();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @CallSuper
+    @Override
+    protected void onSave(Bundle state) {
+        for (int i = requested.size() - 1; i >= 0; i--) {
+            int restartableId = requested.get(i);
+            Subscription subscription = restartableSubscriptions.get(restartableId);
+            if (subscription != null && subscription.isUnsubscribed())
+                requested.remove(i);
+        }
+        state.putIntegerArrayList(REQUESTED_KEY, requested);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @CallSuper
+    @Override
+    protected void onTakeView(View view) {
+        views.onNext(view);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @CallSuper
+    @Override
+    protected void onDropView() {
+        views.onNext(null);
+    }
+
+    /**
+     * Please, use restartableXX and deliverXX methods for pushing data from RxPresenter into View.
+     */
+    @Deprecated
+    @Nullable
+    @Override
+    public View getView() {
+        return super.getView();
+    }
+}

+ 38 - 0
app/src/main/java/nucleus/presenter/delivery/DeliverFirst.java

@@ -0,0 +1,38 @@
+package nucleus.presenter.delivery;
+
+import rx.Notification;
+import rx.Observable;
+import rx.functions.Func1;
+
+public class DeliverFirst<View, T> implements Observable.Transformer<T, Delivery<View, T>> {
+
+    private final Observable<View> view;
+
+    public DeliverFirst(Observable<View> view) {
+        this.view = view;
+    }
+
+    @Override
+    public Observable<Delivery<View, T>> call(Observable<T> observable) {
+        return observable.materialize()
+            .take(1)
+            .switchMap(new Func1<Notification<T>, Observable<? extends Delivery<View, T>>>() {
+                @Override
+                public Observable<? extends Delivery<View, T>> call(final Notification<T> notification) {
+                    return view.map(new Func1<View, Delivery<View, T>>() {
+                        @Override
+                        public Delivery<View, T> call(View view) {
+                            return view == null ? null : new Delivery<>(view, notification);
+                        }
+                    });
+                }
+            })
+            .filter(new Func1<Delivery<View, T>, Boolean>() {
+                @Override
+                public Boolean call(Delivery<View, T> delivery) {
+                    return delivery != null;
+                }
+            })
+            .take(1);
+    }
+}

+ 42 - 0
app/src/main/java/nucleus/presenter/delivery/DeliverLatestCache.java

@@ -0,0 +1,42 @@
+package nucleus.presenter.delivery;
+
+import rx.Notification;
+import rx.Observable;
+import rx.functions.Func1;
+import rx.functions.Func2;
+
+public class DeliverLatestCache<View, T> implements Observable.Transformer<T, Delivery<View, T>> {
+
+    private final Observable<View> view;
+
+    public DeliverLatestCache(Observable<View> view) {
+        this.view = view;
+    }
+
+    @Override
+    public Observable<Delivery<View, T>> call(Observable<T> observable) {
+        return Observable
+            .combineLatest(
+                view,
+                observable
+                    .materialize()
+                    .filter(new Func1<Notification<T>, Boolean>() {
+                        @Override
+                        public Boolean call(Notification<T> notification) {
+                            return !notification.isOnCompleted();
+                        }
+                    }),
+                new Func2<View, Notification<T>, Delivery<View, T>>() {
+                    @Override
+                    public Delivery<View, T> call(View view, Notification<T> notification) {
+                        return view == null ? null : new Delivery<>(view, notification);
+                    }
+                })
+            .filter(new Func1<Delivery<View, T>, Boolean>() {
+                @Override
+                public Boolean call(Delivery<View, T> delivery) {
+                    return delivery != null;
+                }
+            });
+    }
+}

+ 50 - 0
app/src/main/java/nucleus/presenter/delivery/DeliverReplay.java

@@ -0,0 +1,50 @@
+package nucleus.presenter.delivery;
+
+import rx.Notification;
+import rx.Observable;
+import rx.Subscription;
+import rx.functions.Action0;
+import rx.functions.Func1;
+import rx.subjects.ReplaySubject;
+
+public class DeliverReplay<View, T> implements Observable.Transformer<T, Delivery<View, T>> {
+
+    private final Observable<View> view;
+
+    public DeliverReplay(Observable<View> view) {
+        this.view = view;
+    }
+
+    @Override
+    public Observable<Delivery<View, T>> call(Observable<T> observable) {
+        final ReplaySubject<Notification<T>> subject = ReplaySubject.create();
+        final Subscription subscription = observable
+            .materialize()
+            .filter(new Func1<Notification<T>, Boolean>() {
+                @Override
+                public Boolean call(Notification<T> notification) {
+                    return !notification.isOnCompleted();
+                }
+            })
+            .subscribe(subject);
+        return view
+            .switchMap(new Func1<View, Observable<Delivery<View, T>>>() {
+                @Override
+                public Observable<Delivery<View, T>> call(final View view) {
+                    return view == null ? Observable.<Delivery<View, T>>never() : subject
+                        .map(new Func1<Notification<T>, Delivery<View, T>>() {
+                            @Override
+                            public Delivery<View, T> call(Notification<T> notification) {
+                                return new Delivery<>(view, notification);
+                            }
+                        });
+                }
+            })
+            .doOnUnsubscribe(new Action0() {
+                @Override
+                public void call() {
+                    subscription.unsubscribe();
+                }
+            });
+    }
+}

+ 56 - 0
app/src/main/java/nucleus/presenter/delivery/Delivery.java

@@ -0,0 +1,56 @@
+package nucleus.presenter.delivery;
+
+import android.support.annotation.Nullable;
+
+import rx.Notification;
+import rx.functions.Action2;
+
+/**
+ * A class that represents a couple of View and Data.
+ *
+ * @param <View>
+ * @param <T>
+ */
+public final class Delivery<View, T> {
+
+    private final View view;
+    private final Notification<T> notification;
+
+    public Delivery(View view, Notification<T> notification) {
+        this.view = view;
+        this.notification = notification;
+    }
+
+    public void split(Action2<View, T> onNext, @Nullable Action2<View, Throwable> onError) {
+        if (notification.getKind() == Notification.Kind.OnNext)
+            onNext.call(view, notification.getValue());
+        else if (onError != null && notification.getKind() == Notification.Kind.OnError)
+            onError.call(view, notification.getThrowable());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Delivery<?, ?> delivery = (Delivery<?, ?>)o;
+
+        if (view != null ? !view.equals(delivery.view) : delivery.view != null) return false;
+        return !(notification != null ? !notification.equals(delivery.notification) : delivery.notification != null);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = view != null ? view.hashCode() : 0;
+        result = 31 * result + (notification != null ? notification.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "Delivery{" +
+            "view=" + view +
+            ", notification=" + notification +
+            '}';
+    }
+}

+ 79 - 0
app/src/main/java/nucleus/view/NucleusActivity.java

@@ -0,0 +1,79 @@
+package nucleus.view;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+import nucleus.factory.PresenterFactory;
+import nucleus.factory.ReflectionPresenterFactory;
+import nucleus.presenter.Presenter;
+
+/**
+ * This class is an example of how an activity could controls it's presenter.
+ * You can inherit from this class or copy/paste this class's code to
+ * create your own view implementation.
+ *
+ * @param <P> a type of presenter to return with {@link #getPresenter}.
+ */
+public abstract class NucleusActivity<P extends Presenter> extends Activity implements ViewWithPresenter<P> {
+
+    private static final String PRESENTER_STATE_KEY = "presenter_state";
+
+    private PresenterLifecycleDelegate<P> presenterDelegate =
+        new PresenterLifecycleDelegate<>(ReflectionPresenterFactory.<P>fromViewClass(getClass()));
+
+    /**
+     * Returns a current presenter factory.
+     */
+    public PresenterFactory<P> 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<P> 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());
+    }
+}

+ 82 - 0
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 <P> a type of presenter to return with {@link #getPresenter}.
+ */
+public abstract class NucleusFragment<P extends Presenter> extends Fragment implements ViewWithPresenter<P> {
+
+    private static final String PRESENTER_STATE_KEY = "presenter_state";
+    private PresenterLifecycleDelegate<P> presenterDelegate =
+        new PresenterLifecycleDelegate<>(ReflectionPresenterFactory.<P>fromViewClass(getClass()));
+
+    /**
+     * Returns a current presenter factory.
+     */
+    public PresenterFactory<P> 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<P> 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());
+    }
+}

+ 113 - 0
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 <P> a type of presenter to return with {@link #getPresenter}.
+ */
+public class NucleusLayout<P extends Presenter> extends FrameLayout implements ViewWithPresenter<P> {
+
+    private static final String PARENT_STATE_KEY = "parent_state";
+    private static final String PRESENTER_STATE_KEY = "presenter_state";
+
+    private PresenterLifecycleDelegate<P> presenterDelegate =
+        new PresenterLifecycleDelegate<>(ReflectionPresenterFactory.<P>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<P> 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<P> 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());
+    }
+}

+ 25 - 0
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 <T> T unmarshall(byte[] array) {
+        Parcel parcel = Parcel.obtain();
+        parcel.unmarshall(array, 0, array.length);
+        parcel.setDataPosition(0);
+        Object value = parcel.readValue(CLASS_LOADER);
+        parcel.recycle();
+        return (T)value;
+    }
+
+    static byte[] marshall(Object o) {
+        Parcel parcel = Parcel.obtain();
+        parcel.writeValue(o);
+        byte[] result = parcel.marshall();
+        parcel.recycle();
+        return result;
+    }
+}

+ 127 - 0
app/src/main/java/nucleus/view/PresenterLifecycleDelegate.java

@@ -0,0 +1,127 @@
+package nucleus.view;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+
+import nucleus.factory.PresenterFactory;
+import nucleus.factory.PresenterStorage;
+import nucleus.presenter.Presenter;
+
+/**
+ * This class adopts a View lifecycle to the Presenter`s lifecycle.
+ *
+ * @param <P> a type of the presenter.
+ */
+public final class PresenterLifecycleDelegate<P extends Presenter> {
+
+    private static final String PRESENTER_KEY = "presenter";
+    private static final String PRESENTER_ID_KEY = "presenter_id";
+
+    @Nullable private PresenterFactory<P> presenterFactory;
+    @Nullable private P presenter;
+    @Nullable private Bundle bundle;
+
+    private boolean presenterHasView;
+
+    public PresenterLifecycleDelegate(@Nullable PresenterFactory<P> presenterFactory) {
+        this.presenterFactory = presenterFactory;
+    }
+
+    /**
+     * {@link ViewWithPresenter#getPresenterFactory()}
+     */
+    @Nullable
+    public PresenterFactory<P> getPresenterFactory() {
+        return presenterFactory;
+    }
+
+    /**
+     * {@link ViewWithPresenter#setPresenterFactory(PresenterFactory)}
+     */
+    public void setPresenterFactory(@Nullable PresenterFactory<P> 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;
+        }
+    }
+}

+ 30 - 0
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<P extends Presenter> {
+
+    /**
+     * Returns a current presenter factory.
+     */
+    PresenterFactory<P> 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<P> 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();
+}