All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.solovyev.android.fragments.MultiPaneFragmentManager Maven / Gradle / Ivy

There is a newer version: 1.1.18
Show newest version
package org.solovyev.android.fragments;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import org.solovyev.android.Activities;
import org.solovyev.common.Builder;
import org.solovyev.common.JPredicate;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class MultiPaneFragmentManager {


    /*
	**********************************************************************
    *
    *                           CONSTANTS
    *
    **********************************************************************
    */

	protected static final int NO_ANIMATION = -1;

	@Nonnull
	private static final String TAG = "MultiPaneFragmentManager";

    /*
    **********************************************************************
    *
    *                           FIELDS
    *
    **********************************************************************
    */

	@Nonnull
	private final FragmentActivity activity;

	private final int mainPaneViewId;

	@Nonnull
	private final Class emptyFragmentClass;

	@Nonnull
	private final String emptyFragmentTag;

	private int startingAnimationResId = NO_ANIMATION;

	private int endingAnimationResId = NO_ANIMATION;

	public MultiPaneFragmentManager(@Nonnull FragmentActivity activity,
									int mainPaneViewId,
									@Nonnull Class emptyFragmentClass,
									@Nonnull String emptyFragmentTag) {
		this.activity = activity;
		this.mainPaneViewId = mainPaneViewId;
		this.emptyFragmentClass = emptyFragmentClass;
		this.emptyFragmentTag = emptyFragmentTag;
	}

	public MultiPaneFragmentManager(@Nonnull FragmentActivity activity,
									int mainPaneViewId,
									@Nonnull Class emptyFragmentClass,
									@Nonnull String emptyFragmentTag,
									int startingAnimationResId,
									int endingAnimationResId) {
		this.activity = activity;
		this.mainPaneViewId = mainPaneViewId;
		this.emptyFragmentClass = emptyFragmentClass;
		this.emptyFragmentTag = emptyFragmentTag;
		this.startingAnimationResId = startingAnimationResId;
		this.endingAnimationResId = endingAnimationResId;
	}

    /*
    **********************************************************************
    *
    *                           GETTER
    *
    **********************************************************************
    */

	@Nonnull
	public FragmentActivity getActivity() {
		return activity;
	}

    /*
    **********************************************************************
    *
    *                           METHODS
    *
    **********************************************************************
    */


	@Nonnull
	private MultiPaneFragmentDef createEmptyMultiPaneFragmentDef(int paneViewId) {
		return MultiPaneFragmentDef.forClass(getEmptyFragmentTag(paneViewId), false, emptyFragmentClass, activity, null);
	}

	@Nonnull
	private String getEmptyFragmentTag(int paneViewId) {
		return emptyFragmentTag + "-" + paneViewId;
	}

	protected void setFragment(int fragmentViewId, @Nonnull MultiPaneFragmentDef mpfd) {
		final FragmentManager fm = activity.getSupportFragmentManager();

		final FragmentTransaction ft = fm.beginTransaction();

		setFragment(fragmentViewId, mpfd, fm, ft);

		ft.commitAllowingStateLoss();

		// we cannot wait until android will execute pending transactions as some logic rely on added/attached transactions
		executePendingTransactions(fm);
	}

	private void setFragment(final int fragmentViewId,
							 @Nonnull final MultiPaneFragmentDef mpfd,
							 @Nonnull final FragmentManager fm,
							 @Nonnull final FragmentTransaction ft) {
		hideKeyboard();

		// in some cases we cannot execute pending transactions after commit (e.g. transactions from action bar => we need to try to execute them now)
		boolean canContinue = executePendingTransactions(fm);

		if (canContinue) {
			if (startingAnimationResId != NO_ANIMATION && endingAnimationResId != NO_ANIMATION) {
				ft.setCustomAnimations(startingAnimationResId, endingAnimationResId, startingAnimationResId, endingAnimationResId);
			}

			if (mpfd.isAddToBackStack()) {
				ft.addToBackStack(mpfd.getTag());
			}
			/**
			 * Fragments identified by tag defines set of fragments of the same TYPE
			 */
			final Fragment fragmentByTag = fm.findFragmentByTag(mpfd.getTag());

			/**
			 * Fragments identified by tag defines set of fragments of the same LOCATION in view
			 */
			final Fragment fragmentById = fm.findFragmentById(fragmentViewId);

			if (fragmentByTag != null) {
				// found fragment of known type - reuse?
				if (mpfd.canReuse(fragmentByTag)) {
					// fragment can be reused
					if (fragmentByTag.isDetached()) {
						if (fragmentById != null) {
							tryRemoveFragment(ft, fragmentById);
						}
						// fragment is detached and can be simple reused
						ft.attach(fragmentByTag);
					} else {
						// fragment is not free - it is already shown on the view
						// let's check that it is shown in the right place
						if (fragmentByTag.equals(fragmentById)) {
							// yes, fragment is shown under the view with same ID
						} else {
							// no, fragment is shown somewhere else, let's copy state
							if (fragmentById != null) {
								ft.remove(fragmentById);
							}

							final Fragment newFragment = mpfd.build();
							copyState(fragmentByTag, newFragment, fm);
							ft.remove(fragmentByTag);
							ft.add(fragmentViewId, newFragment, mpfd.getTag());
						}
					}
				} else {
					// fragment cannot be reused => remove if first
					ft.remove(fragmentByTag);

					if (fragmentById != null && fragmentById != fragmentByTag) {
						tryRemoveFragment(ft, fragmentById);
					}
					// add new fragment
					ft.add(fragmentViewId, mpfd.build(), mpfd.getTag());
				}

			} else {
				if (fragmentById != null) {
					tryRemoveFragment(ft, fragmentById);
				}
				ft.add(fragmentViewId, mpfd.build(), mpfd.getTag());
			}
		}
	}

	public void copyState(@Nonnull Fragment source, @Nonnull Fragment destination, @Nonnull FragmentManager fm) {
		final Fragment.SavedState savedState = fm.saveFragmentInstanceState(source);
		destination.setInitialSavedState(savedState);
	}

	public void hideKeyboard() {
		final View focusedView = activity.getCurrentFocus();

		if (focusedView != null) {
			final InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
			imm.hideSoftInputFromWindow(focusedView.getWindowToken(), 0);
		}
	}

	/**
	 * Method executes all pending transactions in {@link android.support.v4.app.FragmentManager}.
	 *
	 * @param fm fragment manager
	 * @return true if pending transactions were successfully executed, false otherwise
	 */
	private boolean executePendingTransactions(@Nonnull FragmentManager fm) {
		boolean success;
		// we must run all pending transactions to be sure that no fragments for same tags are in pending list
		try {
			fm.executePendingTransactions();
			success = true;
		} catch (RuntimeException e) {
			success = false;
			/**
			 * May be thrown by {@link android.support.v4.app.FragmentManager#executePendingTransactions()}.
			 */
			Log.e(TAG, e.getMessage(), e);

			// we cannot work with the same UI anymore => restart activity (as persistence data is OK, only UI is broken)
			Activities.restartActivity(activity);
		}
		return success;
	}

	private void tryRemoveFragment(@Nonnull FragmentTransaction ft, @Nonnull Fragment fragment) {
		// let's see if we can preserve old fragment for further use
		if (fragment instanceof DetachableFragment) {
			// yes, we can => detach if not detached yet
			if (!fragment.isDetached()) {
				ft.detach(fragment);
			}
		} else {
			// no, we cannot => remove
			ft.remove(fragment);
		}
	}

	public void removeFragment(int fragmentViewId) {
		final FragmentManager fm = activity.getSupportFragmentManager();

		final FragmentTransaction ft = fm.beginTransaction();

		final Fragment fragmentById = fm.findFragmentById(fragmentViewId);
		if (fragmentById != null) {
			tryRemoveFragment(ft, fragmentById);
		}

		ft.commitAllowingStateLoss();

		// we cannot wait until android will execute pending transactions as some logic rely on added/attached transactions
		executePendingTransactions(fm);
	}

	public void goBack() {
		hideKeyboard();
		activity.getSupportFragmentManager().popBackStack();
	}

	public boolean goBackImmediately() {
		hideKeyboard();
		return activity.getSupportFragmentManager().popBackStackImmediate();
	}

	public void goBack(@Nonnull String tag) {
		hideKeyboard();
		activity.getSupportFragmentManager().popBackStack(tag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
	}

	public boolean isFragmentShown(@Nonnull String fragmentTag) {
		final FragmentManager fm = activity.getSupportFragmentManager();
		final Fragment fragment = fm.findFragmentByTag(fragmentTag);
		if (fragment != null && fragment.isAdded() && !fragment.isDetached()) {
			return true;
		} else {
			return false;
		}
	}

	@Nullable
	public  F getFragment(@Nonnull String fragmentTag) {
		final FragmentManager fm = activity.getSupportFragmentManager();
		return (F) fm.findFragmentByTag(fragmentTag);
	}

	protected void emptifyFragmentPane(int paneViewId) {
		setFragment(paneViewId, createEmptyMultiPaneFragmentDef(paneViewId));
	}

    /*
    **********************************************************************
    *
    *                           MAIN PANE
    *
    **********************************************************************
    */

	@Deprecated
	public void setMainFragment(@Nonnull Class fragmentClass,
								@Nullable Bundle fragmentArgs,
								@Nullable JPredicate reuseCondition,
								@Nonnull String fragmentTag,
								boolean addToBackStack) {
		setMainFragment(MultiPaneFragmentDef.newInstance(fragmentTag, addToBackStack, ReflectionFragmentBuilder.forClass(activity, fragmentClass, fragmentArgs), reuseCondition));
	}

	@Deprecated
	public void setMainFragment(@Nonnull Builder fragmentBuilder,
								@Nullable JPredicate reuseCondition,
								@Nonnull String fragmentTag) {
		setMainFragment(fragmentBuilder, reuseCondition, fragmentTag, false);
	}

	@Deprecated
	public void setMainFragment(@Nonnull Builder fragmentBuilder,
								@Nullable JPredicate reuseCondition,
								@Nonnull String fragmentTag,
								boolean addToBackStack) {
		setMainFragment(MultiPaneFragmentDef.newInstance(fragmentTag, addToBackStack, fragmentBuilder, reuseCondition));
	}

	public void setMainFragment(@Nonnull MultiPaneFragmentDef mpfd) {
		setFragment(mainPaneViewId, mpfd);
	}

	protected void emptifyMainFragment() {
		setMainFragment(createEmptyMultiPaneFragmentDef(mainPaneViewId));
	}

	public void setMainFragment(@Nonnull FragmentDef fragmentDef,
								@Nonnull FragmentManager fm,
								@Nonnull FragmentTransaction ft) {
		setFragment(mainPaneViewId, MultiPaneFragmentDef.fromFragmentDef(fragmentDef, null, activity), fm, ft);
	}

	public void setMainFragment(@Nonnull FragmentDef fragmentDef, @Nullable Bundle fragmentArgs) {
		setFragment(mainPaneViewId, MultiPaneFragmentDef.fromFragmentDef(fragmentDef, fragmentArgs, activity));
	}

	public void setMainFragment(@Nonnull FragmentDef fragmentDef) {
		setMainFragment(fragmentDef, null);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy