Java tutorial
/* * ================================================================================================= * Copyright (C) 2013 - 2014 Martin Albedinsky [Wolf-ITechnologies] * ================================================================================================= * Licensed under the Apache License, Version 2.0 or later (further "License" only). * ------------------------------------------------------------------------------------------------- * You may use this file only in compliance with the License. More details and copy of this License * you may obtain at * * http://www.apache.org/licenses/LICENSE-2.0 * * You can redistribute, modify or publish any part of the code written within this file but as it * is described in the License, the software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF ANY KIND. * * See the License for the specific language governing permissions and limitations under the License. * ================================================================================================= */ package com.wit.android.support.fragment.manage; import android.annotation.SuppressLint; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; 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 com.wit.android.support.fragment.FragmentsConfig; import java.util.ArrayList; import java.util.List; /** * <h3>Class Overview</h3> * todo: description * * @author Martin Albedinsky * @see com.wit.android.support.fragment.manage.FragmentController.FragmentFactory * @see com.wit.android.support.fragment.manage.FragmentController.TransactionOptions */ public class FragmentController { /** * Interface =================================================================================== */ /** * <h3>Interface Overview</h3> * Required interface for fragment factory. * * @author Martin Albedinsky * @see com.wit.android.support.fragment.manage.FragmentController */ public static interface FragmentFactory { /** * Returns an instance of the fragment associated with the specified <var>fragmentId</var> * within this fragment factory. * <p> * This is in most cases invoked from an instance of {@link com.wit.android.support.fragment.manage.FragmentController}, * when that instance of FragmentController was requested to show fragment with the specified * <var>fragmentId</var>. * * @param fragmentId An id of the requested fragment to create. * @param params Bundle with parameters for fragment. * @return An instance of the fragment associated with the specified <var>fragmentId</var> or * {@code null} if this fragment factory doesn't provides requested fragment. * @see #isFragmentProvided(int) */ @Nullable public Fragment createFragmentInstance(int fragmentId, @Nullable Bundle params); /** * Returns an options for the fragment associated with the specified <var>fragmentId</var> * within this fragment factory to manage such a fragment showing by FragmentController. * <p> * This is in most cases invoked form an instance of {@link com.wit.android.support.fragment.manage.FragmentController}, * when that instance of FragmentController was requested to show fragment with the specified * <var>fragmentId</var>. In such a case, if this fragment factory will not provide valid * TransactionOptions, FragmentController will use default ones. * * @param fragmentId An id of the fragment for which are options requested. * @param params Same params as for {@link #createFragmentInstance(int, android.os.Bundle)}. * @return TransactionOptions object for fragment associated with the specified <var>fragmentId</var> * or {@code null} if this fragment factory doesn't provides TransactionOptions for fragment * with the specified <var>fragmentId</var>. */ @Nullable public TransactionOptions getFragmentTransactionOptions(int fragmentId, @Nullable Bundle params); /** * Returns a tag for the fragment associated with the specified <var>fragmentId</var> within * this fragment factory. * * @param fragmentId An id of the fragment for which is its TAG requested. * @return Tag for fragment associated with the specified <var>fragmentId</var> or {@code null} * if this fragment factory doesn't provides TAG for fragment with the specified <var>fragmentId</var>. */ @Nullable public String getFragmentTag(int fragmentId); /** * Returns flag indicating whether there is provided a fragment for the specified <var>fragmentId</var> * by this factory or not. * * @param fragmentId An id of the desired fragment to check. * @return {@code True} if fragment is provided, so {@link #createFragmentInstance(int, android.os.Bundle)} * will return an instance of such a fragment, {@code false} otherwise. */ public boolean isFragmentProvided(int fragmentId); } /** * <h3>Class Overview</h3> * todo: description * * @author Martin Albedinsky */ public static interface OnChangeListener { /** * Invoked whenever an old fragment is replaced by a new one or simply a new fragment is first * time showed by an instance of FragmentController. * * @param id An id of the currently changed (showed) fragment. * @param tag A tag of the currently changed (showed) fragment. * @param fromFactory {@code True} if the changed fragment was obtained from a factory, * {@code false} otherwise. */ public void onFragmentChanged(int id, String tag, boolean fromFactory); } /** * <h3>Class Overview</h3> * todo: description * * @author Martin Albedinsky */ public static interface OnBackStackChangeListener { /** * Invoked whenever fragments back stack change occur. * * @param added {@code True} if there was added new back stack entry, {@code false} * if old one was removed. * @param id An id of the back stack entry of which status was changed. This is actually * a position of the added/removed entry in the fragments back stack. This is * default behaviour of the fragments back stack managed by {@link android.support.v4.app.FragmentManager}. * @param tag A tag of the back stack entry of which status was changed. */ public void onFragmentsBackStackChanged(boolean added, int id, String tag); } /** * Constants =================================================================================== */ /** * Log TAG. */ private static final String TAG = "FragmentController"; /** * Flag indicating whether the debug output trough log-cat is enabled or not. */ private static final boolean DEBUG_ENABLED = FragmentsConfig.LIBRARY_DEBUG_LOG_ENABLED; /** * Flag indicating whether the output trough log-cat is enabled or not. */ private static final boolean LOG_ENABLED = FragmentsConfig.LIBRARY_LOG_ENABLED; /** * Default tag used when showing fragments. */ public static final String FRAGMENT_TAG = "com.wit.android.support.fragment.manage.FragmentController.TAG.Fragment"; /** * Static members ============================================================================== */ /** * Members ===================================================================================== */ /** * Fragment manager to handle showing, obtaining and hiding fragments. */ final FragmentManager mFragmentManager; /** * Fragment factory which provides fragment instances to show (manage) by this controller. */ private FragmentFactory mFactory; /** * Id of a layout container within the current window view hierarchy, into which will be view of * all managed fragments placed. */ private int mFragmentContainerId = -1; /** * The entry at the top of the fragments back stack. */ private FragmentManager.BackStackEntry mTopBackStackEntry; /** * Listener callback for back stack changes. */ private OnBackStackChangeListener mBackStackListener; /** * Listener callback for fragment changes. */ private OnChangeListener mFragmentListener; /** * Tag of the currently showing fragment showed by this controller */ private String mCurrentFragmentTag; /** * Constructors ================================================================================ */ /** * Creates a new instance of FragmentController within the given <var>parentFragment</var>'s context. * <p> * Passed fragment will be used to obtain an instance of {@link android.support.v4.app.FragmentManager} * by {@link android.support.v4.app.Fragment#getFragmentManager()} that will be used to mange * fragments by this controller. * <p> * If the given <var>parentFragment</var> implements {@link FragmentController.OnBackStackChangeListener} * or {@link com.wit.android.support.fragment.manage.FragmentController.OnChangeListener} it will * be automatically set as these callbacks to this controller. * * @param parentFragment A fragment in which will be this controller used. * @see #FragmentController(android.support.v4.app.FragmentActivity) */ public FragmentController(@NonNull Fragment parentFragment) { this(parentFragment.getFragmentManager()); if (parentFragment instanceof OnBackStackChangeListener) { setOnBackStackChangeListener((OnBackStackChangeListener) parentFragment); } if (parentFragment instanceof OnChangeListener) { setOnChangeListener((OnChangeListener) parentFragment); } } /** * Creates a new instance of FragmentController within the given <var>parentActivity</var>'s context. * <p> * Passed activity will be used to obtain an instance of {@link android.support.v4.app.FragmentManager} * by {@link android.support.v4.app.FragmentActivity#getFragmentManager()} that will be used to * mange fragments by this controller. * <p> * If the given <var>parentActivity</var> implements {@link FragmentController.OnBackStackChangeListener} * or {@link com.wit.android.support.fragment.manage.FragmentController.OnChangeListener} it will * be automatically set as these callbacks to this controller. * * @param parentActivity An activity in which will be this controller used. * @see #FragmentController(android.support.v4.app.Fragment) */ public FragmentController(@NonNull FragmentActivity parentActivity) { this(parentActivity.getSupportFragmentManager()); if (parentActivity instanceof OnBackStackChangeListener) { setOnBackStackChangeListener((OnBackStackChangeListener) parentActivity); } if (parentActivity instanceof OnChangeListener) { setOnChangeListener((OnChangeListener) parentActivity); } } /** * Creates a new instance of FragmentController with the given <var>fragmentManager</var>. * * @param fragmentManager Fragment manager to manage fragments. * @see #FragmentController(android.support.v4.app.FragmentActivity) * @see #FragmentController(android.support.v4.app.Fragment) */ public FragmentController(@NonNull FragmentManager fragmentManager) { this.mFragmentManager = fragmentManager; mFragmentManager.addOnBackStackChangedListener(new BackStackListener()); // Check for back stacked fragments. final int n = mFragmentManager.getBackStackEntryCount(); if (n > 0) { this.mTopBackStackEntry = mFragmentManager.getBackStackEntryAt(n - 1); } } /** * Methods ===================================================================================== */ /** * Public -------------------------------------------------------------------------------------- */ /** * Same as {@link #showFragment(int, android.os.Bundle)} without params. */ public boolean showFragment(int fragmentId) { return showFragment(fragmentId, null); } /** * Shows a fragment provided by the current factory associated with the specified <var>fragmentId</var>. * * @param fragmentId Id of the desired factory fragment to show. * @param params Parameters for fragment. These will be passed to the factory when calling * {@link FragmentFactory#createFragmentInstance(int, android.os.Bundle)} upon it. * @return {@code True} if the requested fragment has been successfully showed, {@code false} * if the current factory does not provide fragment with the requested id or there is already * fragment with the same TAG already showing and should not be replaced. * @throws java.lang.IllegalStateException If this controller does not have factory attached. */ public boolean showFragment(int fragmentId, @Nullable Bundle params) { // Check if we have fragment factory and fragment is provided. if (!checkFragmentFactory(fragmentId)) { Log.e(TAG, "Current factory(" + mFactory.getClass().getSimpleName() + ") does not provide fragment for the requested id(" + fragmentId + ")."); return false; } return this.performShowFragment(fragmentId, params); } /** * Same as {@link #showFragment(android.support.v4.app.Fragment, FragmentController.TransactionOptions)} * with default {@link FragmentController.TransactionOptions}. */ public boolean showFragment(@NonNull Fragment fragment) { return showFragment(fragment, new TransactionOptions()); } /** * Same as {@link #showFragment(android.support.v4.app.Fragment, FragmentController.TransactionOptions)} * with default {@link FragmentController.TransactionOptions} with the given <var>fragmentTag</var>. */ public boolean showFragment(@Nullable Fragment fragment, @Nullable String fragmentTag) { return showFragment(fragment, new TransactionOptions().tag(fragmentTag)); } /** * Shows the given <var>fragment</var> using the given options. * * @param fragment The fragment instance to show. * @param options Options used when showing fragment. * @return {@code True} if the fragment has been successfully showed, {@code false} * if there is already fragment with the same TAG already showing and should not be replaced. */ public boolean showFragment(@Nullable Fragment fragment, @Nullable TransactionOptions options) { return this.performShowFragment(fragment, options); } /** * Returns an instance of Fragment obtained form the FragmentManager attached to this controller * by the specified <var>fragmentTag</var>. * * @param fragmentTag TAG of the desired fragment to find. * @return Fragment instance or {@code null} if there is no such a fragment within the * FragmentManager with the specified tag. * @see #findFactoryFragmentById(int) */ @Nullable public Fragment findFragmentByTag(@Nullable String fragmentTag) { return mFragmentManager.findFragmentByTag(fragmentTag); } /** * Same as {@link #findFragmentByTag(String)}, but with id. */ @Nullable public Fragment findFragmentById(int fragmentId) { return mFragmentManager.findFragmentById(fragmentId); } /** * Same as {@link #findFragmentByTag(String)}, where fragment tag will be requested from the * current factory. * * @param fragmentId Id of the desired factory fragment to find. * @throws java.lang.IllegalStateException If this controller does not have factory attached. */ @Nullable public Fragment findFactoryFragmentById(int fragmentId) { return this.checkFragmentFactory(fragmentId) ? findFragmentByTag(mFactory.getFragmentTag(fragmentId)) : null; } /** * Returns an instance of the fragment which is currently being shown on the screen. To determine * visible fragment, these fragment's flags are checked: * <ul> * <li>{@link android.support.v4.app.Fragment#isVisible()}</li> * <li>or {@link android.support.v4.app.Fragment#isAdded()}</li> * </ul> * This will be actually an instance of the fragment which was showed as last. * * @return Instance of the currently visible or added fragment, or {@code null} if there * are no fragments ({@code n = 0}) within the current fragment manager or all checked fragments * are in the undesirable state. * @see #getVisibleSecondFragment() */ @Nullable public Fragment getVisibleFragment() { final List<Fragment> fragments = mFragmentManager.getFragments(); final List<Fragment> visibleFragments = new ArrayList<>(); if (fragments != null) { // Get only visible fragments. for (Fragment fragment : fragments) { // TODO: perform here more fragment flag checks ? if (fragment != null && (fragment.isVisible() || fragment.isAdded())) { visibleFragments.add(fragment); if (DEBUG_ENABLED) { Log.d(TAG, "visible/added fragment(" + fragment.getTag() + ")"); } } } } int size; Fragment fragment = null; switch (size = visibleFragments.size()) { case 0: // No fragments available. break; case 1: // Only one fragment available, get it. fragment = visibleFragments.get(0); break; default: // More than one fragment available. fragment = visibleFragments.get(size - 1); } if (DEBUG_ENABLED) { Log.d(TAG, "Resolved visible fragment(" + fragment + ")"); } return fragment; } /** * Returns an instance of the second fragment which is currently being shown on the screen, where * two fragments are visible at the same time. To determine visible second fragment, these fragment's * flags are checked: * <ul> * <li>{@link android.support.v4.app.Fragment#isVisible()}</li> * <li>or {@link android.support.v4.app.Fragment#isAdded()}</li> * </ul> * This will be actually an instance of the fragment which was showed before the last one. * * @return Instance of the currently visible or added second fragment, or {@code null} if there * are no fragments ({@code n > 1}) within the current fragment manager or all checked fragments * are in the undesirable state. * @see #getVisibleSecondFragment() */ @Nullable public Fragment getVisibleSecondFragment() { final List<Fragment> fragments = mFragmentManager.getFragments(); final List<Fragment> visibleFragments = new ArrayList<>(); if (fragments != null) { // Get only visible fragments. for (Fragment fragment : fragments) { // TODO: perform here more fragment flag checks ? if (fragment != null && (fragment.isVisible() || fragment.isAdded())) { visibleFragments.add(fragment); if (DEBUG_ENABLED) { Log.d(TAG, "visible/added fragment(" + fragment.getTag() + ")"); } } } } int size; Fragment secondFragment = null; switch (size = visibleFragments.size()) { case 0: case 1: // No or one fragments available. break; case 2: // Two fragments available, get the second one. secondFragment = visibleFragments.get(0); break; default: // More than two fragments available. secondFragment = visibleFragments.get(size - 2); } if (DEBUG_ENABLED) { Log.d(TAG, "Resolved second visible fragment(" + secondFragment + ")"); } return secondFragment; } /** * Wrapped {@link android.support.v4.app.FragmentManager#popBackStack()}. */ public void hideVisibleFragment() { mFragmentManager.popBackStack(); } /** * Wrapped {@link android.support.v4.app.FragmentManager#popBackStackImmediate()}. */ public boolean hideVisibleFragmentImmediate() { return mFragmentManager.popBackStackImmediate(); } /** * Same as {@link #setFragmentOptionsMenuVisible(String, boolean)}, where fragment tag will be * requested form the current factory by the specified <var>fragmentId</var>. * * @param fragmentId Id of the desired fragment from the current factory, of which options menu * to show/hide. */ public boolean setFactoryFragmentOptionsMenuVisible(int fragmentId, boolean visible) { return this.checkFragmentFactory(fragmentId) && setFragmentOptionsMenuVisible(mFactory.getFragmentTag(fragmentId), visible); } /** * Same as {@link #setFragmentOptionsMenuVisible(String, boolean)} but with fragment id. */ public boolean setFragmentOptionsMenuVisible(int fragmentId, boolean visible) { final Fragment fragment = mFragmentManager.findFragmentById(fragmentId); if (fragment != null) { fragment.setHasOptionsMenu(visible); return true; } return false; } /** * Shows/hides options menu of the requested fragment by calling {@link android.support.v4.app.Fragment#setHasOptionsMenu(boolean)}. * * @param fragmentTag A tag of the desired fragment of which options to show/hide. * @param visible {@code True} to show options menu, {@code false} to hide options menu. * @return {@code True} if fragment was found and request to show/hide its options menu was * performed, {@code false} otherwise. * @see #setFragmentOptionsMenuVisible(int, boolean) */ public boolean setFragmentOptionsMenuVisible(@Nullable String fragmentTag, boolean visible) { final Fragment fragment = mFragmentManager.findFragmentByTag(fragmentTag); if (fragment != null) { fragment.setHasOptionsMenu(visible); return true; } return false; } /** * Wrapped {@link android.support.v4.app.FragmentManager#beginTransaction()}. * <p> * <b>Do not forget to commit here created transaction.</b> */ @NonNull @SuppressLint("CommitTransaction") public FragmentTransaction beginTransaction() { return mFragmentManager.beginTransaction(); } /** * Returns a flag indicating whether this controller has factory attached or not. * * @return {@code True} if factory is attached, {@code false} otherwise. */ public boolean hasFragmentFactory() { return mFactory != null; } /** * Returns flag indicating whether there are some fragments within the fragment manager's back * stack or not. * * @return {@code True} if fragment manager's back stack holds some entries, {@code false} * otherwise. * @see android.support.v4.app.FragmentManager#getBackStackEntryCount() */ public boolean hasBackStackEntries() { return mFragmentManager.getBackStackEntryCount() > 0; } /** * Clears fragments back stack by calling {@link android.support.v4.app.FragmentManager#popBackStack()} * in loop of size obtained by {@link android.support.v4.app.FragmentManager#getBackStackEntryCount()}. * <p> * <b>Note</b>, that {@link android.support.v4.app.FragmentManager#popBackStack()} is an asynchronous * call, so the fragments back stack can be cleared in the feature not immediately. * * @see #clearBackStackImmediate() */ public void clearBackStack() { final int n = mFragmentManager.getBackStackEntryCount(); if (n > 0) { for (int i = 0; i < n; i++) { mFragmentManager.popBackStack(); } } } /** * Like {@link #clearBackStack()}, but this will call {@link android.support.v4.app.FragmentManager#popBackStackImmediate()}. * <p> * <b>Note</b>, that {@link android.support.v4.app.FragmentManager#popBackStackImmediate()} is a * synchronous call, so the fragments back stack will be popped immediately within this call. If * there is too many fragments, this can take some time. * * @return {@code True} if there was at least one fragment popped, {@code false} otherwise. */ public boolean clearBackStackImmediate() { final int n = mFragmentManager.getBackStackEntryCount(); if (n > 0) { boolean popped = false; for (int i = 0; i < n; i++) { if (mFragmentManager.popBackStackImmediate() && !popped) { popped = true; } } return popped; } return false; } /** * Getters + Setters --------------------------------------------------------------------------- */ /** * Registers a callback to be invoked when some change occur in the fragments back stack. * * @param listener Listener callback. */ public void setOnBackStackChangeListener(@NonNull OnBackStackChangeListener listener) { this.mBackStackListener = listener; } /** * Removes the current OnBackStackChangeListener callback. */ public void removeOnBackStackChangeListener() { this.mFragmentListener = null; } /** * Registers a callback to be invoked when fragments are being changed. * * @param listener Listener callback. */ public void setOnChangeListener(@NonNull OnChangeListener listener) { this.mFragmentListener = listener; } /** * Removes the current OnChangeListener callback. */ public void removeOnChangeListener() { this.mBackStackListener = null; } /** * Returns a tag of the currently showing fragment. <b>Note</b>, that this is only accurate, as * it depends on how are fragments changing, and if all fragments are managed by this controller. * * @return Tag of the currently showing fragment or {@code null} if no fragment was shown * by this controller yet. */ @Nullable public String getCurrentFragmentTag() { if (mFragmentContainerId > 0) { final Fragment fragment = mFragmentManager.findFragmentById(mFragmentContainerId); return fragment != null ? fragment.getTag() : null; } return null; } /** * Returns a tag of the currently visible fragment. * * @return Fragment tag or {@code null} if there is currently no fragments visible. * @see #getVisibleFragment() * @see #getVisibleSecondFragmentTag() */ @Nullable public String getVisibleFragmentTag() { final Fragment visibleFragment = getVisibleFragment(); return (visibleFragment != null) ? visibleFragment.getTag() : null; } /** * Returns a tag of the second currently visible fragment. * * @return Fragment tag or {@code null} if there is only one or none currently visible fragments. * @see #getVisibleSecondFragment() * @see #getVisibleFragmentTag() */ @Nullable public String getVisibleSecondFragmentTag() { final Fragment visibleFragment = getVisibleSecondFragment(); return (visibleFragment != null) ? visibleFragment.getTag() : null; } /** * Returns the fragment manager passed to this controller during initialization. * * @return Instance of FragmentManager. */ @NonNull public FragmentManager getFragmentManager() { return mFragmentManager; } /** * Sets an id of the layout container used to host root views of all fragments manager by this * controller. * * @param layoutId The desired id of layout container within the current window view hierarchy, * into which should be views of all managed fragments placed. * @see #getFragmentContainerId() */ public void setFragmentContainerId(int layoutId) { this.mFragmentContainerId = layoutId; } /** * Returns an id of the layout container for fragment views. * * @return The desired id of layout container within the current window view hierarchy, into which * should be views of all managed fragments placed or {@code -1} as default. * @see #setFragmentContainerId(int) */ public int getFragmentContainerId() { return mFragmentContainerId; } /** * Sets the factory for this dialog controller. * * @param factory The desired factory. * @see #getFragmentFactory() * @see #hasFragmentFactory() */ public void setFragmentFactory(@Nullable FragmentFactory factory) { this.mFactory = factory; } /** * Returns the current fragment factory of this controller instance. * * @return Instance of fragment factory or {@code null} if there was no factory set. * @see #setFragmentFactory(com.wit.android.support.fragment.manage.FragmentController.FragmentFactory) * @see #hasFragmentFactory() */ @Nullable public FragmentFactory getFragmentFactory() { return mFactory; } /** * Returns the top entry of the fragments back stack. * * @return The top back stack entry or {@code null} if there are no back stack entries. */ @Nullable public FragmentManager.BackStackEntry getTopBackStackEntry() { return mTopBackStackEntry; } /** * Protected ----------------------------------------------------------------------------------- */ /** * Invoked to show the given fragment instance using the given options. * * @param fragment Fragment to show. * @param options Transaction options for the given fragment. If the given <var>options</var> * are invalid, the default options will be used. * @return {@code True} if showing was successful, {@code false} if there is already * showing fragment with the same tag as specified within the options. * @throws java.lang.IllegalStateException If the current id for layout container is invalid. */ protected boolean onShowFragment(@NonNull Fragment fragment, @Nullable TransactionOptions options) { if (options == null) { options = new TransactionOptions(); } if (!options.replaceSame) { // Do not replace same fragment. Fragment currentFragment = mFragmentManager.findFragmentByTag(options.tag); if (currentFragment != null) { if (LOG_ENABLED) { Log.v(TAG, "Fragment with tag(" + options.tag + ") is already showing or within the back-stack."); } return false; } } // Check if we have place where the fragment should be placed. if (options.containerId <= 0) { if (mFragmentContainerId <= 0) { // No id provided for the layout where should be fragment // placed. throw new IllegalStateException( "There is no id provided for the layout container into which should be requested fragment's view placed."); } else { options.containerId = mFragmentContainerId; } } final FragmentTransaction transaction = beginTransaction(); // Apply animations to the transaction from the FragmentTransition parameter. if (options.transition != null && options.transition != FragmentTransition.NONE) { final FragmentTransition trans = options.transition; /** * <pre> * There are provided 4 animations: * First two for currently incoming and outgoing fragment. * Second two for incoming fragment from back stack and * currently outgoing fragment. * </pre> */ transaction.setCustomAnimations(trans.getInAnimResId(), trans.getOutAnimResId(), trans.getInAnimBackResId(), trans.getOutAnimBackResId()); } if (DEBUG_ENABLED) { Log.d(TAG, "onShowFragment() options = " + options.toString()); } if (options.add) { transaction.add(options.containerId, fragment, options.tag); } else { transaction.replace(options.containerId, fragment, options.tag); } // Add fragment to back stack if requested. if (options.addToBackStack) { transaction.addToBackStack(fragment.getTag()); if (DEBUG_ENABLED) { Log.d(TAG, "Fragment(" + fragment + ") added to back stack under the tag(" + fragment.getTag() + ")."); } } return onCommitTransaction(transaction, options); } /** * Invoked to finally commit created fragment transaction. Here passed transaction is already set * upped according to the TransactionOptions. * <p> * This implementation commits the passed <var>transaction</var> and in case that the <var>options</var> * has set flag {@link com.wit.android.support.fragment.manage.FragmentController.TransactionOptions#showImmediate} * to {@code true}, {@link android.support.v4.app.FragmentManager#executePendingTransactions()} * will be invoked too on the attached FragmentManager. * * @param transaction Final fragment transaction to commit. * @param options Already processed transaction. * @return Always returns {@code true}. */ protected boolean onCommitTransaction(@NonNull FragmentTransaction transaction, @NonNull TransactionOptions options) { // Commit transaction. transaction.commit(); if (options.showImmediate) { mFragmentManager.executePendingTransactions(); } return true; } /** * Called to dispatch the back stack change. * * @param entriesCount The count of the fragment back stack entries. * @param action The back stack change action identifier. */ void dispatchBackStackChanged(int entriesCount, int action) { final boolean added = action == BackStackListener.ADDED; if (entriesCount > 0) { final FragmentManager.BackStackEntry entry = mFragmentManager.getBackStackEntryAt(entriesCount - 1); if (entry != null) { notifyBackStackEntryChange(mTopBackStackEntry = entry, added); } } else if (mTopBackStackEntry != null) { notifyBackStackEntryChange(mTopBackStackEntry, false); this.mTopBackStackEntry = null; } } /** * Private ------------------------------------------------------------------------------------- */ /** * Performs showing of a fragment obtained from the current fragment factory. * * @param fragmentId The id of fragment from the current fragment factory to show. * @param params Parameters to be passed to the current factory by * {@link com.wit.android.support.fragment.manage.FragmentController.FragmentFactory#createFragmentInstance(int, android.os.Bundle)}. * @return {@code True} if showing was successful, {@code false} otherwise. */ private boolean performShowFragment(int fragmentId, Bundle params) { // First obtain fragment instance then fragment tag. Fragment fragment = mFactory.createFragmentInstance(fragmentId, params); if (fragment == null) { // Invalid fragment instance. Log.e(TAG, "No fragment instance provided by factory(" + mFactory.getClass().getSimpleName() + ") for the requested fragment id(" + fragmentId + ")."); return false; } final boolean success = onShowFragment(fragment, mFactory.getFragmentTransactionOptions(fragmentId, params)); return success && notifyFragmentChanged(fragmentId, fragment.getTag(), true); } /** * Performs showing of the given fragment instance using the given options. * * @param fragment Fragment to show. * @param options Show options for the given fragment. * @return {@code True} if showing was successful, {@code false} otherwise. */ private boolean performShowFragment(Fragment fragment, TransactionOptions options) { final boolean success = onShowFragment(fragment, options); return success && notifyFragmentChanged(fragment.getId(), fragment.getTag(), false); } /** * Checks whether there is factory available and if so, if a fragment for the specified <var>fragmentId</var> * is provided by that factory. * * @param fragmentId The id of requested fragment. * @return {@code True} if the current fragment factory provides such a fragment, {@code false} * otherwise. * @throws java.lang.IllegalStateException If the current factory isn't available. */ private boolean checkFragmentFactory(int fragmentId) { if (mFactory == null) { throw new IllegalStateException("No fragment factory found."); } return mFactory.isFragmentProvided(fragmentId); } /** * Called to notify, that the given <var>changedEntry</var> was added or removed from the back stack. * * @param changedEntry The back stack entry which was changed. * @param added {@code True} if the specified entry was added to the back stack, * {@code false} if was removed. */ private void notifyBackStackEntryChange(FragmentManager.BackStackEntry changedEntry, boolean added) { this.mCurrentFragmentTag = changedEntry.getName(); if (mBackStackListener != null) { // Dispatch to listener. mBackStackListener.onFragmentsBackStackChanged(added, changedEntry.getId(), mCurrentFragmentTag); } } /** * Called to notify, that there was fragment with the given id and tag currently changed, so replaces * the old one. * * @param id The id of the currently changed (showed) fragment. * @param tag The tag of the currently changed (showed) fragment. * @param factory {@code True} if the changed fragment was obtained from a factory, * {@code false} otherwise. */ private boolean notifyFragmentChanged(int id, String tag, boolean factory) { this.mCurrentFragmentTag = tag; if (mFragmentListener != null) { mFragmentListener.onFragmentChanged(id, mCurrentFragmentTag, factory); } return true; } /** * Inner classes =============================================================================== */ /** * <h3>Class Overview</h3> * todo: description * <h3>Default SetUp:</h3> * <ul> * <li>tag: {@link com.wit.android.support.fragment.manage.FragmentController#FRAGMENT_TAG}</li> * <li>transition: {@link FragmentTransition#NONE}</li> * <li>container id: <b>-1</b></li> * <li>back-stacking: <b>false</b></li> * <li>replacing same: <b>true</b></li> * <li>showing immediately: <b>false</b></li> * </ul> * * @author Martin Albedinsky * @see com.wit.android.support.fragment.manage.FragmentController * @see com.wit.android.support.fragment.manage.FragmentController.FragmentFactory */ public static class TransactionOptions implements Parcelable { /** * Members ================================================================================= */ /** * Creator used to create an instance or array of instances of TransactionOptions from {@link android.os.Parcel}. */ public static final Creator<TransactionOptions> CREATOR = new Creator<TransactionOptions>() { /** */ @Override public TransactionOptions createFromParcel(Parcel source) { return new TransactionOptions(source); } /** */ @Override public TransactionOptions[] newArray(int size) { return new TransactionOptions[size]; } }; /** * Tag for fragment. * <p> * Default value: <b>{@link #FRAGMENT_TAG}</b> */ protected String tag = FRAGMENT_TAG; /** * Show direction. */ protected FragmentTransition transition = FragmentTransition.NONE; /** * Fragment container layout id. */ protected int containerId = -1; /** * Flag indicating, whether fragment should be added to back stack or not. */ protected boolean addToBackStack = false; /** * Flag indicating, whether a same fragment (currently showing) can be replaced by a new one * with this options containing same tag or not. */ protected boolean replaceSame = true; /** * Flag indicating, whether a new fragment should be showed immediately or not. */ protected boolean showImmediate = false; /** * Flag indicating, whether to add a new fragment or replace old one. */ protected boolean add; /** * Constructors ============================================================================ */ /** * Creates a new instance of default TransactionOptions. */ public TransactionOptions() { } /** * Called form {@link #CREATOR} to create an instance of TransactionOptions form the given * parcel <var>source</var>. * * @param source Parcel with data for a new instance. */ protected TransactionOptions(Parcel source) { this.containerId = source.readInt(); this.tag = source.readString(); this.addToBackStack = source.readInt() == 1; this.replaceSame = source.readInt() == 1; this.showImmediate = source.readInt() == 1; this.add = source.readInt() == 1; this.transition = source.readParcelable(FragmentTransition.class.getClassLoader()); } /** * Methods ================================================================================= */ /** */ @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(containerId); dest.writeString(tag); dest.writeInt(addToBackStack ? 1 : 0); dest.writeInt(replaceSame ? 1 : 0); dest.writeInt(showImmediate ? 1 : 0); dest.writeInt(add ? 1 : 0); transition.writeToParcel(dest, flags); } /** */ @Override public int describeContents() { return 0; } /** */ @Override public String toString() { final StringBuilder builder = new StringBuilder(""); builder.append("[tag("); builder.append(tag); builder.append("), "); builder.append(" transition("); builder.append(transition != null ? transition.name() : "null"); builder.append("), "); builder.append(" backStacked("); builder.append(addToBackStack); builder.append("), "); builder.append(" replace("); builder.append(replaceSame); builder.append("), "); builder.append(" container("); builder.append(containerId); builder.append("), "); builder.append(" add("); builder.append(add); builder.append(")]"); return builder.toString(); } /** * Sets a flag indicating, whether to add or replace fragment to {@code true} so a new * fragment will be showed using {@link android.support.v4.app.FragmentTransaction#add(int, android.support.v4.app.Fragment, String)}. * * @return This options instance. */ public TransactionOptions add() { this.add = true; return this; } /** * Sets a flag indicating, whether to add or replace fragment to {@code false} so an old * fragment will be replaced by a new one using {@link android.support.v4.app.FragmentTransaction#replace(int, android.support.v4.app.Fragment, String)}. * * @return This options instance. */ public TransactionOptions replace() { this.add = false; return this; } /** * Sets a tag for the fragment to be showed. * * @param fragmentTag The desired fragment tag. * @return This options instance. */ public TransactionOptions tag(String fragmentTag) { this.tag = fragmentTag; return this; } /** * Sets a flag indicating, whether fragment should be added to the fragments back stack or not. * * @param add {@code True} to add fragment to the back stack, {@code false} otherwise. * @return This options instance. */ public TransactionOptions addToBackStack(boolean add) { this.addToBackStack = add; return this; } /** * Sets a transition used to animate fragment views change. * * @param transition Transition with animations. * @return This options instance. * @see com.wit.android.support.fragment.manage.FragmentTransition */ public TransactionOptions transition(FragmentTransition transition) { this.transition = transition; return this; } /** * Sets an id of the layout container into which should be a new fragment's view placed. * <p> * <b>Note</b>, that this id will be used only for this options. * * @param layoutId An id of the desired layout container to be used as container for fragment's * view. * @return This options instance. * @see com.wit.android.support.fragment.manage.FragmentController#setFragmentContainerId(int) */ public TransactionOptions containerId(int layoutId) { this.containerId = layoutId; return this; } /** * Sets a flag indicating, whether the currently showing fragment with the same TAG can be * replaced by a new one (using this options) or not. * * @param replace {@code True} to replace a fragment with the same TAG as specified here, * with a new one, {@code false} otherwise. * @return This options instance. */ public TransactionOptions replaceSame(boolean replace) { this.replaceSame = replace; return this; } /** * Sets a flag indicating, whether a new fragment should be showed immediately or not. * * @param immediate {@code True} to show immediately, {@code false} otherwise. * @return This options instance. */ public TransactionOptions showImmediate(boolean immediate) { this.showImmediate = immediate; return this; } } /** * Fragments back stack inner implementation. */ private final class BackStackListener implements FragmentManager.OnBackStackChangedListener { /** * Constants =============================================================================== */ /** * Flag to indicate, that fragment was added to the back stack. */ static final int ADDED = 0x00; /** * Flag to indicate, that fragment was removed from the back stack. */ static final int REMOVED = 0x01; /** * Members ================================================================================= */ /** * Current size of the fragments back stack. */ int currentCount = 0; /** * Methods ================================================================================= */ /** */ @Override public void onBackStackChanged() { final int n = mFragmentManager.getBackStackEntryCount(); if (n >= 0 && n != currentCount) { dispatchBackStackChanged(n, n > currentCount ? ADDED : REMOVED); this.currentCount = n; } } } }