/*
* Copyright 2016 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;
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.mvi.MviPresenter;
import com.hannesdorfmann.mosby3.mvp.MvpView;
import java.util.UUID;
/**
* The default implementation of {@link ViewGroupMviDelegate}
*
* @author Hannes Dorfmann
* @see ViewGroupMviDelegate
* @since 3.0.0
*/
public class ViewGroupMviDelegateImpl<V extends MvpView, P extends MviPresenter<V, ?>>
implements ViewGroupMviDelegate<V, P> {
// TODO allow custom save state hook in
public static boolean DEBUG = false;
private static final String DEBUG_TAG = "ViewGroupMviDelegateImp";
private ViewGroupMviDelegateCallback<V, P> delegateCallback;
private String mosbyViewId;
private final boolean keepPresenterDuringScreenOrientationChange;
private final boolean isInEditMode;
private P presenter;
private final Activity activity;
public ViewGroupMviDelegateImpl(@NonNull View view,
@NonNull ViewGroupMviDelegateCallback<V, P> delegateCallback,
boolean keepPresenterDuringScreenOrientationChange) {
if (view == null) {
throw new NullPointerException("View is null!");
}
if (delegateCallback == null) {
throw new NullPointerException("MvpDelegateCallback is null!");
}
this.delegateCallback = delegateCallback;
this.keepPresenterDuringScreenOrientationChange = keepPresenterDuringScreenOrientationChange;
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
* MviDelegateCallback#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 (keepPresenterDuringScreenOrientationChange) {
Context context = delegateCallback.getContext();
mosbyViewId = UUID.randomUUID().toString();
PresenterManager.putPresenter(PresenterManager.getActivity(context), mosbyViewId, presenter);
}
return presenter;
}
@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;
boolean viewStateWillBeRestored = false;
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);
}
} else {
Context context = getContext();
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 {
viewStateWillBeRestored = true;
if (DEBUG) {
Log.d(DEBUG_TAG, "Presenter instance reused from internal cache: " + presenter);
}
}
}
// 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 (viewStateWillBeRestored) {
delegateCallback.setRestoringViewState(true);
}
presenter.attachView(view);
if (viewStateWillBeRestored) {
delegateCallback.setRestoringViewState(false);
}
if (DEBUG) {
Log.d(DEBUG_TAG,
"MvpView attached to Presenter. MvpView: " + view + " Presenter: " + presenter);
}
}
@Override public void onDetachedFromWindow() {
if (isInEditMode) return;
if (keepPresenterDuringScreenOrientationChange) {
boolean destroyedPermanently = !ActivityMviDelegateImpl.retainPresenterInstance(
keepPresenterDuringScreenOrientationChange, activity);
if (destroyedPermanently) {
// Whole activity will be destroyed
// Internally Orientation manager already does the clean up
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 keepPresenterDuringScreenOrientationChange == false
PresenterManager.remove(activity, mosbyViewId);
}
mosbyViewId = null;
presenter.detachView(false);
} else {
boolean detachedBecauseOrientationChange = ActivityMviDelegateImpl.retainPresenterInstance(
keepPresenterDuringScreenOrientationChange, 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
presenter.detachView(false);
if (mosbyViewId
!= null) { // mosbyViewId == null if keepPresenterDuringScreenOrientationChange == false
PresenterManager.remove(activity, mosbyViewId);
}
mosbyViewId = null;
}
}
/**
* Must be called from {@link View#onSaveInstanceState()}
*/
public Parcelable onSaveInstanceState() {
if (isInEditMode) return null;
Parcelable superState = delegateCallback.superOnSaveInstanceState();
if (keepPresenterDuringScreenOrientationChange) {
return new MosbySavedState(superState, mosbyViewId);
} else {
return superState;
}
}
/**
* Restore the data from SavedState
*
* @param state The state to read data from
*/
private void restoreSavedState(MosbySavedState state) {
mosbyViewId = state.getMosbyViewId();
}
/**
* Must be called from {@link View#onRestoreInstanceState(Parcelable)}
*/
public void onRestoreInstanceState(Parcelable state) {
if (isInEditMode) return;
if (!(state instanceof MosbySavedState)) {
delegateCallback.superOnRestoreInstanceState(state);
return;
}
MosbySavedState savedState = (MosbySavedState) state;
restoreSavedState(savedState);
delegateCallback.superOnRestoreInstanceState(savedState.getSuperState());
}
}