/*
* Copyright 2015 Hannes Dorfmann.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hannesdorfmann.mosby3.mvp.delegate;
import android.app.Activity;
import android.content.Context;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.View;
import com.hannesdorfmann.mosby3.PresenterManager;
import com.hannesdorfmann.mosby3.mvp.MvpPresenter;
import com.hannesdorfmann.mosby3.mvp.MvpView;
import com.hannesdorfmann.mosby3.mvp.viewstate.RestorableParcelableViewState;
import com.hannesdorfmann.mosby3.mvp.viewstate.ViewState;
import java.util.UUID;
/**
* A {@link ViewGroupMvpDelegate} that supports {@link ViewState}
*
* @author Hannes Dorfmann
* @since 1.1.0
*/
public class ViewGroupMvpViewStateDelegateImpl<V extends MvpView, P extends MvpPresenter<V>, VS extends ViewState<V>>
implements ViewGroupMvpDelegate<V, P> {
// TODO allow custom save state hook in
public static boolean DEBUG = false;
private static final String DEBUG_TAG = "ViewGroupMvpDelegateImp";
private ViewGroupMvpViewStateDelegateCallback<V, P, VS> delegateCallback;
private String mosbyViewId;
private final boolean keepPresenter;
private final Activity activity;
private final boolean isInEditMode;
private VS restoreableParcelableViewState = null;
private boolean applyViewState = false;
private boolean viewStateFromMemoryRestored = false;
/**
* Creates a new instance
*
* @param delegateCallback the callback
* @param keepPresenter true, if you want to keep the presenter and view state during screen
* orientation changes etc.
*/
public ViewGroupMvpViewStateDelegateImpl(@NonNull View view,
@NonNull ViewGroupMvpViewStateDelegateCallback<V, P, VS> delegateCallback,
boolean keepPresenter) {
if (view == null) {
throw new NullPointerException("View is null!");
}
if (delegateCallback == null) {
throw new NullPointerException("MvpDelegateCallback is null!");
}
this.delegateCallback = delegateCallback;
this.keepPresenter = keepPresenter;
this.isInEditMode = view.isInEditMode();
if (!isInEditMode) {
this.activity = PresenterManager.getActivity(delegateCallback.getContext());
} else {
this.activity = null;
}
}
/**
* Generates the unique (mosby internal) viewState id and calls {@link
* MvpDelegateCallback#createPresenter()}
* to create a new presenter instance
*
* @return The new created presenter instance
*/
private P createViewIdAndCreatePresenter() {
P presenter = delegateCallback.createPresenter();
if (presenter == null) {
throw new NullPointerException("Presenter returned from createPresenter() is null.");
}
if (keepPresenter) {
Context context = delegateCallback.getContext();
mosbyViewId = UUID.randomUUID().toString();
PresenterManager.putPresenter(PresenterManager.getActivity(context), mosbyViewId, presenter);
}
return presenter;
}
private VS createViewState() {
VS viewState = delegateCallback.createViewState();
if (keepPresenter) {
PresenterManager.putViewState(activity, mosbyViewId, viewState);
}
applyViewState = false;
viewStateFromMemoryRestored = false;
return viewState;
}
@NonNull private Context getContext() {
Context c = delegateCallback.getContext();
if (c == null) {
throw new NullPointerException("Context returned from " + delegateCallback + " is null");
}
return c;
}
@Override public void onAttachedToWindow() {
if (isInEditMode) return;
P presenter = null;
VS viewState = null;
if (mosbyViewId == null) {
// No presenter available,
// Activity is starting for the first time (or keepPresenterInstance == false)
presenter = createViewIdAndCreatePresenter();
if (DEBUG) {
Log.d(DEBUG_TAG, "new Presenter instance created: "
+ presenter
+ " MvpView: "
+ delegateCallback.getMvpView());
}
viewState = createViewState();
if (DEBUG) {
Log.d(DEBUG_TAG, "new ViewState instance created: "
+ viewState
+ " MvpView: "
+ delegateCallback.getMvpView());
}
} else {
presenter = PresenterManager.getPresenter(activity, mosbyViewId);
if (presenter == null) {
// Process death,
// hence no presenter with the given viewState id stored, although we have a viewState id
presenter = createViewIdAndCreatePresenter();
if (DEBUG) {
Log.d(DEBUG_TAG,
"No Presenter instance found in cache, although MosbyView ID present. This was caused by process death, therefore new Presenter instance created: "
+ presenter);
}
} else {
if (DEBUG) {
Log.d(DEBUG_TAG, "Presenter instance reused from internal cache: "
+ presenter
+ " MvpView: "
+ delegateCallback.getMvpView());
}
}
viewState = PresenterManager.getViewState(activity, mosbyViewId);
if (viewState == null) {
if (restoreableParcelableViewState == null) {
// Process death, no viewstate restored from parcel
viewState = createViewState();
if (DEBUG) {
Log.d(DEBUG_TAG,
"No ViewState instance found in cache, although MosbyView ID present. This was caused by process death, therefore new ViewState instance created: "
+ viewState);
}
} else {
// Memory ViewState is null, so use RestoreableViewState
viewState = restoreableParcelableViewState;
applyViewState = true;
viewStateFromMemoryRestored = false;
if (keepPresenter) {
if (mosbyViewId == null) {
throw new IllegalStateException(
"The (internal) Mosby View id is null although restoreable view state (Parcelable) is not null. This should never happen. This seems to be a Mosby internal error. Please report this issue at https://github.com/sockeqwe/mosby/issues");
}
// Put restoreable view state into memory cache for future useage
PresenterManager.putViewState(activity, mosbyViewId, viewState);
}
if (DEBUG) {
Log.d(DEBUG_TAG, "Parcelable ViewState instance reused from last SavedState: "
+ viewState
+ " MvpView: "
+ delegateCallback.getMvpView());
}
}
} else {
// ViewState from memory
applyViewState = true;
viewStateFromMemoryRestored = true;
if (DEBUG) {
Log.d(DEBUG_TAG, "ViewState instance reused from internal cache: "
+ viewState
+ " MvpView: "
+ delegateCallback.getMvpView());
}
}
}
// presenter is ready, so attach viewState
V view = delegateCallback.getMvpView();
if (view == null) {
throw new NullPointerException(
"MvpView returned from getMvpView() is null. Returned by " + delegateCallback);
}
if (presenter == null) {
throw new IllegalStateException(
"Oops, Presenter is null. This seems to be a Mosby internal bug. Please report this issue here: https://github.com/sockeqwe/mosby/issues");
}
if (viewState == null) {
throw new IllegalStateException(
"Oops, ViewState is null. This seems to be a Mosby internal bug. Please report this issue here: https://github.com/sockeqwe/mosby/issues");
}
delegateCallback.setViewState(viewState);
if (applyViewState) {
delegateCallback.setRestoringViewState(true);
}
delegateCallback.setPresenter(presenter);
presenter.attachView(view);
if (DEBUG) {
Log.d(DEBUG_TAG,
"MvpView attached to Presenter. MvpView: " + view + " Presenter: " + presenter);
}
if (applyViewState) {
viewState.apply(view, viewStateFromMemoryRestored);
delegateCallback.setRestoringViewState(false);
delegateCallback.onViewStateInstanceRestored(viewStateFromMemoryRestored);
if (DEBUG) {
Log.d(DEBUG_TAG, "ViewState restored (from memory = "
+ viewStateFromMemoryRestored
+ " ). MvpView: "
+ view
+ " ViewState: "
+ viewState);
}
} else {
delegateCallback.onNewViewStateInstance();
}
}
@Override public void onDetachedFromWindow() {
if (isInEditMode) return;
P presenter = delegateCallback.getPresenter();
if (presenter == null) {
throw new NullPointerException(
"Presenter returned from delegateCallback.getPresenter() is null");
}
if (keepPresenter) {
boolean destroyedPermanently =
!ActivityMvpDelegateImpl.retainPresenterInstance(keepPresenter, activity);
if (destroyedPermanently) {
// Whole activity will be destroyed
if (DEBUG) {
Log.d(DEBUG_TAG, "Detaching View "
+ delegateCallback.getMvpView()
+ " from Presenter "
+ presenter
+ " and removing presenter permanently from internal cache because the hosting Activity will be destroyed permanently");
}
if (mosbyViewId != null) { // mosbyViewId == null if keepPresenter == false
PresenterManager.remove(activity, mosbyViewId);
}
mosbyViewId = null;
presenter.detachView(false);
} else {
boolean detachedBecauseOrientationChange =
ActivityMvpDelegateImpl.retainPresenterInstance(keepPresenter, activity);
if (detachedBecauseOrientationChange) {
// Simple orientation change
if (DEBUG) {
Log.d(DEBUG_TAG, "Detaching View "
+ delegateCallback.getMvpView()
+ " from Presenter "
+ presenter
+ " temporarily because of orientation change");
}
presenter.detachView(true);
} else {
// view detached, i.e. because of back stack / navigation
/*
if (DEBUG) {
Log.d(DEBUG_TAG, "Detaching View "
+ delegateCallback.getMvpView()
+ " from Presenter "
+ presenter
+ " because view has been destroyed. Also Presenter is removed permanently from internal cache.");
}
PresenterManager.remove(activity, mosbyViewId);
mosbyViewId = null;
presenter.detachView(false);
*/
}
}
} else {
// retain instance feature disabled
if (DEBUG) {
Log.d(DEBUG_TAG, "Detaching View "
+ delegateCallback.getMvpView()
+ " from Presenter "
+ presenter
+ " permanently");
}
presenter.detachView(false);
if (mosbyViewId != null) { // mosbyViewId == null if keepPresenter == false
PresenterManager.remove(activity, mosbyViewId);
}
mosbyViewId = null;
}
}
/**
* Must be called from {@link View#onSaveInstanceState()}
*/
@Override public Parcelable onSaveInstanceState() {
if (isInEditMode) return null;
VS viewState = delegateCallback.getViewState();
if (viewState == null) {
throw new NullPointerException("ViewState returned from getViewState() is null for MvpView "
+ delegateCallback.getMvpView());
}
Parcelable superState = delegateCallback.superOnSaveInstanceState();
if (keepPresenter) {
if (viewState instanceof RestorableParcelableViewState) {
return new MosbyViewStateSavedState(superState, mosbyViewId,
(RestorableParcelableViewState) viewState);
}
return new MosbyViewStateSavedState(superState, mosbyViewId, null);
} else {
return superState;
}
}
/**
* Must be called from {@link View#onRestoreInstanceState(Parcelable)}
*/
@Override public void onRestoreInstanceState(Parcelable state) {
if (isInEditMode) return;
if (!(state instanceof MosbyViewStateSavedState)) {
delegateCallback.superOnRestoreInstanceState(state);
return;
}
MosbyViewStateSavedState savedState = (MosbyViewStateSavedState) state;
mosbyViewId = savedState.getMosbyViewId();
restoreableParcelableViewState = (VS) savedState.getRestoreableViewState();
delegateCallback.superOnRestoreInstanceState(savedState.getSuperState());
}
}