mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-09-10 05:53:17 +02:00
Temporarily include nucleus in the project
This commit is contained in:
parent
638d3a32cf
commit
447dfd1e3c
@ -141,9 +141,6 @@ dependencies {
|
|||||||
compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
|
compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
|
||||||
kapt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION"
|
kapt "com.pushtorefresh.storio:sqlite-annotations-processor:$STORIO_VERSION"
|
||||||
|
|
||||||
// Model View Presenter
|
|
||||||
compile 'info.android15.nucleus:nucleus:2.0.5'
|
|
||||||
|
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||||
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
|
@ -87,8 +87,9 @@ public abstract class BaseRxActivity<P extends Presenter> extends BaseActivity i
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onDestroy() {
|
||||||
super.onPause();
|
super.onDestroy();
|
||||||
presenterDelegate.onPause(isFinishing());
|
presenterDelegate.onDropView();
|
||||||
|
presenterDelegate.onDestroy(!isChangingConfigurations());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.base.fragment;
|
package eu.kanade.tachiyomi.ui.base.fragment;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.App;
|
import eu.kanade.tachiyomi.App;
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
||||||
@ -85,13 +84,14 @@ public abstract class BaseRxFragment<P extends Presenter> extends BaseFragment i
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onDestroyView() {
|
||||||
super.onPause();
|
super.onDestroyView();
|
||||||
presenterDelegate.onPause(getActivity().isFinishing() || isRemoving(this));
|
presenterDelegate.onDropView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isRemoving(Fragment fragment) {
|
@Override
|
||||||
Fragment parent = fragment.getParentFragment();
|
public void onDestroy() {
|
||||||
return fragment.isRemoving() || (parent != null && isRemoving(parent));
|
super.onDestroy();
|
||||||
|
presenterDelegate.onDestroy(!getActivity().isChangingConfigurations());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
app/src/main/java/nucleus/factory/PresenterFactory.java
Normal file
7
app/src/main/java/nucleus/factory/PresenterFactory.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package nucleus.factory;
|
||||||
|
|
||||||
|
import nucleus.presenter.Presenter;
|
||||||
|
|
||||||
|
public interface PresenterFactory<P extends Presenter> {
|
||||||
|
P createPresenter();
|
||||||
|
}
|
64
app/src/main/java/nucleus/factory/PresenterStorage.java
Normal file
64
app/src/main/java/nucleus/factory/PresenterStorage.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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
app/src/main/java/nucleus/factory/RequiresPresenter.java
Normal file
13
app/src/main/java/nucleus/factory/RequiresPresenter.java
Normal 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();
|
||||||
|
}
|
164
app/src/main/java/nucleus/presenter/Presenter.java
Normal file
164
app/src/main/java/nucleus/presenter/Presenter.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
342
app/src/main/java/nucleus/presenter/RxPresenter.java
Normal file
342
app/src/main/java/nucleus/presenter/RxPresenter.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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
app/src/main/java/nucleus/presenter/delivery/Delivery.java
Normal file
56
app/src/main/java/nucleus/presenter/delivery/Delivery.java
Normal 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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
79
app/src/main/java/nucleus/view/NucleusActivity.java
Normal file
79
app/src/main/java/nucleus/view/NucleusActivity.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
82
app/src/main/java/nucleus/view/NucleusFragment.java
Normal file
82
app/src/main/java/nucleus/view/NucleusFragment.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
113
app/src/main/java/nucleus/view/NucleusLayout.java
Normal file
113
app/src/main/java/nucleus/view/NucleusLayout.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
25
app/src/main/java/nucleus/view/ParcelFn.java
Normal file
25
app/src/main/java/nucleus/view/ParcelFn.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
127
app/src/main/java/nucleus/view/PresenterLifecycleDelegate.java
Normal file
127
app/src/main/java/nucleus/view/PresenterLifecycleDelegate.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
app/src/main/java/nucleus/view/ViewWithPresenter.java
Normal file
30
app/src/main/java/nucleus/view/ViewWithPresenter.java
Normal 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();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user