/*
* 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.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.BackstackAccessor;
import android.support.v4.app.Fragment;
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 java.util.UUID;
/**
* * The default implementation of {@link FragmentMvpDelegate}
*
* @param <V> The type of {@link MvpView}
* @param <P> The type of {@link MvpPresenter}
* @author Hannes Dorfmann
* @see FragmentMvpDelegate
* @since 1.1.0
*/
public class FragmentMvpDelegateImpl<V extends MvpView, P extends MvpPresenter<V>>
implements FragmentMvpDelegate<V, P> {
protected static final String KEY_MOSBY_VIEW_ID = "com.hannesdorfmann.mosby3.fragment.mvp.id";
public static boolean DEBUG = false;
private static final String DEBUG_TAG = "FragmentMvpVSDelegate";
private MvpDelegateCallback<V, P> delegateCallback;
protected Fragment fragment;
protected final boolean keepPresenterInstanceDuringScreenOrientationChanges;
protected final boolean keepPresenterOnBackstack;
private boolean onViewCreatedCalled = false;
protected String mosbyViewId;
/**
* @param fragment The Fragment
* @param delegateCallback the DelegateCallback
* @param keepPresenterDuringScreenOrientationChange true, if the presenter should be kept during
* screen orientation
* changes. Otherwise, false
* @param keepPresenterOnBackstack true, if the presenter should be kept when the fragment is
* destroyed because it is put on the backstack, Otherwise false
*/
public FragmentMvpDelegateImpl(@NonNull Fragment fragment,
@NonNull MvpDelegateCallback<V, P> delegateCallback,
boolean keepPresenterDuringScreenOrientationChange, boolean keepPresenterOnBackstack) {
if (delegateCallback == null) {
throw new NullPointerException("MvpDelegateCallback is null!");
}
if (fragment == null) {
throw new NullPointerException("Fragment is null!");
}
if (!keepPresenterDuringScreenOrientationChange && keepPresenterOnBackstack) {
throw new IllegalArgumentException("It is not possible to keep the presenter on backstack, "
+ "but NOT keep presenter through screen orientation changes. Keep presenter on backstack also "
+ "requires keep presenter through screen orientation changes to be enabled");
}
this.fragment = fragment;
this.delegateCallback = delegateCallback;
this.keepPresenterInstanceDuringScreenOrientationChanges =
keepPresenterDuringScreenOrientationChange;
this.keepPresenterOnBackstack = keepPresenterOnBackstack;
}
/**
* Generates the unique (mosby internal) view 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. Activity is " + getActivity());
}
if (keepPresenterInstanceDuringScreenOrientationChanges) {
mosbyViewId = UUID.randomUUID().toString();
PresenterManager.putPresenter(getActivity(), mosbyViewId, presenter);
}
return presenter;
}
@Override public void onViewCreated(View view, @Nullable Bundle bundle) {
P presenter = null;
if (bundle != null && keepPresenterInstanceDuringScreenOrientationChanges) {
mosbyViewId = bundle.getString(KEY_MOSBY_VIEW_ID);
if (DEBUG) {
Log.d(DEBUG_TAG,
"MosbyView ID = " + mosbyViewId + " for MvpView: " + delegateCallback.getMvpView());
}
if (mosbyViewId != null
&& (presenter = PresenterManager.getPresenter(getActivity(), mosbyViewId)) != null) {
//
// Presenter restored from cache
//
if (DEBUG) {
Log.d(DEBUG_TAG,
"Reused presenter " + presenter + " for view " + delegateCallback.getMvpView());
}
} else {
//
// No presenter found in cache, most likely caused by process death
//
presenter = createViewIdAndCreatePresenter();
if (DEBUG) {
Log.d(DEBUG_TAG, "No presenter found although view Id was here: "
+ mosbyViewId
+ ". Most likely this was caused by a process death. New Presenter created"
+ presenter
+ " for view "
+ getMvpView());
}
}
} else {
//
// Activity starting first time, so create a new presenter
//
presenter = createViewIdAndCreatePresenter();
if (DEBUG) {
Log.d(DEBUG_TAG, "New presenter " + presenter + " for view " + getMvpView());
}
}
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");
}
delegateCallback.setPresenter(presenter);
getPresenter().attachView(getMvpView());
if (DEBUG) {
Log.d(DEBUG_TAG, "View" + getMvpView() + " attached to Presenter " + presenter);
}
onViewCreatedCalled = true;
}
@NonNull private Activity getActivity() {
Activity activity = fragment.getActivity();
if (activity == null) {
throw new NullPointerException(
"Activity returned by Fragment.getActivity() is null. Fragment is " + fragment);
}
return activity;
}
private P getPresenter() {
P presenter = delegateCallback.getPresenter();
if (presenter == null) {
throw new NullPointerException("Presenter returned from getPresenter() is null");
}
return presenter;
}
private V getMvpView() {
V view = delegateCallback.getMvpView();
if (view == null) {
throw new NullPointerException("View returned from getMvpView() is null");
}
return view;
}
protected boolean retainPresenterInstance() {
Activity activity = getActivity();
if (activity.isChangingConfigurations()) {
return keepPresenterInstanceDuringScreenOrientationChanges;
}
if (activity.isFinishing()) {
return false;
}
if (keepPresenterOnBackstack && BackstackAccessor.isFragmentOnBackStack(fragment)) {
return true;
}
return !fragment.isRemoving();
}
@Override public void onDestroyView() {
onViewCreatedCalled = false;
Activity activity = getActivity();
boolean retainPresenterInstance = retainPresenterInstance();
P presenter = getPresenter();
presenter.detachView(retainPresenterInstance);
if (!retainPresenterInstance
&& mosbyViewId
!= null) { // mosbyViewId is null if keepPresenterInstanceDuringScreenOrientationChanges == false
PresenterManager.remove(activity, mosbyViewId);
}
if (DEBUG) {
Log.d(DEBUG_TAG, "detached MvpView from Presenter. MvpView "
+ delegateCallback.getMvpView()
+ " Presenter: "
+ presenter);
Log.d(DEBUG_TAG, "Retaining presenter instance: "
+ Boolean.toString(retainPresenterInstance)
+ " "
+ presenter);
}
}
@Override public void onPause() {
}
@Override public void onResume() {
}
@Override public void onStart() {
if (!onViewCreatedCalled) {
throw new IllegalStateException("It seems that you are using "
+ delegateCallback.getClass().getCanonicalName()
+ " as headless (UI less) fragment (because onViewCreated() has not been called or maybe delegation misses that part). Having a Presenter without a View (UI) doesn't make sense. Simply use an usual fragment instead of an MvpFragment if you want to use a UI less Fragment");
}
}
@Override public void onStop() {
}
@Override public void onActivityCreated(Bundle savedInstanceState) {
}
@Override public void onAttach(Activity activity) {
}
@Override public void onDetach() {
}
@Override public void onSaveInstanceState(Bundle outState) {
if ((keepPresenterInstanceDuringScreenOrientationChanges || keepPresenterOnBackstack)
&& outState != null) {
outState.putString(KEY_MOSBY_VIEW_ID, mosbyViewId);
if (DEBUG) {
Log.d(DEBUG_TAG, "Saving MosbyViewId into Bundle. ViewId: " + mosbyViewId);
}
}
}
@Override public void onCreate(Bundle saved) {
}
@Override public void onDestroy() {
}
}