Temporarily include nucleus in the project

This commit is contained in:
len 2016-04-19 12:58:33 +02:00
parent 638d3a32cf
commit 447dfd1e3c
19 changed files with 1288 additions and 13 deletions

View File

@ -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"

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

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

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
});
}
}

View File

@ -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();
}
});
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}