com.sefford.fraggle.FraggleManager.java Source code

Java tutorial

Introduction

Here is the source code for com.sefford.fraggle.FraggleManager.java

Source

/*
 * Copyright (C) 2014 Sal Daz
 *
 * 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.sefford.fraggle;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

import com.sefford.fraggle.interfaces.FraggleFragment;
import com.sefford.fraggle.interfaces.Logger;

/**
 * FraggleManager wraps some common operations over Android's FragmentManager concerning the
 * addition, query and removal of Fragments.
 *
 * @author Sal Daz Gonzlez <sefford@gmail.com>
 */
public class FraggleManager {
    /**
     * Flag for normal backstack operation.
     * <p/>
     * This means the backstack will not be cleared and the new fragment will be added atop of the
     * current one.
     */
    public static final int ADD_TO_BACKSTACK = 0;
    /**
     * Flags addFragment method it has to add the fragment to the backstack.
     */
    public static final int DO_NOT_ADD_TO_BACKSTACK = 1;
    /**
     * Flags addFragment method it has to clear the backstack.
     * <p/>
     * All the previous fragments will be discarded.
     */
    public static final int CLEAR_BACKSTACK = (1 << 1);
    /**
     * Flag indicating to replace or not the fragment.
     * <p/>
     * If the Fragment needs to be replaced, will FraggleManager will substitue the current Fragment
     * with the new one. Otherwise the new will appear on top of the current one and call
     * {@link com.sefford.fraggle.interfaces.FraggleFragment#onFragmentNotVisible() onFragmentNotVisible}
     * instead
     */
    public static final int DO_NOT_REPLACE_FRAGMENT = (1 << 2);
    /**
     * Logger tag
     */
    protected static final String TAG = "FraggleManager";
    /**
     * Logger facilities
     */
    private final Logger log;
    /**
     * Injected Fragment Manager
     */
    private FragmentManager fm;

    /**
     * Creates a new instance of Fraggle Manager
     *
     * @param log Logger Facilities
     */
    public FraggleManager(Logger log) {
        this.log = log;
    }

    /**
     * Initializes the Fraggle Manager by using a FragmentManager.
     *
     * @param fm FragmentManager to wrap around the FraggleManager.
     */
    public void initialize(FragmentManager fm) {
        this.fm = fm;
    }

    /**
     * Calculates the correct mode of adding a Fragment.
     * <p/>
     * If there are no available Fragments the FraggleManager will not try to call a previous
     * Fragment lifecycle.
     *
     * @return No flags if there are no fragments available or DO_NOT_REPLACE otherwise.
     */
    protected int calculateCorrectMode() {
        return fm.getBackStackEntryCount() == 0 ? 0 : DO_NOT_REPLACE_FRAGMENT;
    }

    /**
     * Adds a fragment to the activity content viewgroup. This will typically pass by a several
     * stages, in this order:
     * <ul>
     * <li>Considering if the necessity of {@link com.sefford.fraggle.interfaces.FraggleFragment#isSingleInstance() adding another instance of such fragment class}</li>
     * <li>{@link #processClearBackstack(int) Processing clearing backstack flags conditions}</li>
     * <li>{@link #processAddToBackstackFlag(String, int, android.app.FragmentTransaction) Process adding to backstack flags conditions}</li>
     * <li>{@link #processAnimations(FragmentAnimation, android.app.FragmentTransaction) Process the state of the deserved animations if any}</li>
     * <li>{@link #performTransaction(android.app.Fragment, int, android.app.FragmentTransaction, int) Perform the actual transaction}</li>
     * </ul>
     * <p/>
     * If the fragment is not required to be readded (as in a up navigation) the fragment manager
     * will pop all the backstack until the desired fragment and the {@link com.sefford.fraggle.interfaces.FraggleFragment#onFragmentVisible() onFragmentVisible()}
     * method will be called instead to bring up the dormant fragment.
     *
     * @param frag        Fragment to add
     * @param tag         Fragment tag
     * @param flags       Adds flags to manipulate the state of the backstack
     * @param containerId Container ID where to insert the fragment
     */
    public void addFragment(Fragment frag, String tag, FragmentAnimation animation, int flags, int containerId) {
        if (frag != null) {
            if ((!((FraggleFragment) frag).isSingleInstance()) || peek(tag) == null) {
                FragmentTransaction ft = fm.beginTransaction();
                processClearBackstack(flags);
                processAddToBackstackFlag(tag, flags, ft);
                processAnimations(animation, ft);
                performTransaction(frag, flags, ft, containerId);
            } else {
                fm.popBackStack(tag, 0);
                peek(tag).onFragmentVisible();
            }
        }
    }

    /**
     * Returns the first fragment in the stack with the tag "tag".
     *
     * @param tag Tag to look for in the Fragment stack
     * @return First fragment in the stack with the name Tag
     */
    protected FraggleFragment peek(String tag) {
        return (FraggleFragment) fm.findFragmentByTag(tag);
    }

    /**
     * Process Clear backstack flag.
     * <p/>
     * FraggleManager will clear the back stack before trying to add the next Fragment if
     * {@link #CLEAR_BACKSTACK CLEAR_BACKSTACK} flag is found
     *
     * @param flags Added flags to the Fragment configuration
     */
    protected void processClearBackstack(int flags) {
        if ((flags & CLEAR_BACKSTACK) == CLEAR_BACKSTACK) {
            try {
                fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
            } catch (IllegalStateException exception) {
                log.e(TAG, exception.getMessage(), exception);
            }
        }
    }

    /**
     * Processes Add to Backstack flag.
     * <p/>
     * Will not add the Fragment to the backstack if the
     * {@link #DO_NOT_ADD_TO_BACKSTACK DO_NOT_ADD_TO_BACKSTACK} flag is found.
     *
     * @param title Title of the fragment
     * @param flags Added flags to the Fragment configuration
     * @param ft    Transaction to add to backstack from
     */
    protected void processAddToBackstackFlag(String title, int flags, FragmentTransaction ft) {
        if ((flags & DO_NOT_ADD_TO_BACKSTACK) != DO_NOT_ADD_TO_BACKSTACK) {
            ft.addToBackStack(title);
        }
    }

    /**
     * Processes the custom animations element, adding them as required
     *
     * @param animation Animation object to process
     * @param ft        Fragment transaction to add to the transition
     */
    protected void processAnimations(FragmentAnimation animation, FragmentTransaction ft) {
        if (animation != null) {
            if (animation.isCompletedAnimation()) {
                ft.setCustomAnimations(animation.getEnterAnim(), animation.getExitAnim(), animation.getPushInAnim(),
                        animation.getPopOutAnim());
            } else {
                ft.setCustomAnimations(animation.getEnterAnim(), animation.getExitAnim());
            }
        }
    }

    /**
     * Configures the way to add the Fragment into the transaction. It can vary from adding a new fragment,
     * to using a previous instance and refresh it, or replacing the last one.
     *
     * @param frag        Fragment to add
     * @param flags       Added flags to the Fragment configuration
     * @param ft          Transaction to add the fragment
     * @param containerId Target container ID
     */
    protected void configureAdditionMode(Fragment frag, int flags, FragmentTransaction ft, int containerId) {
        if ((flags & DO_NOT_REPLACE_FRAGMENT) != DO_NOT_REPLACE_FRAGMENT) {
            ft.replace(containerId, frag, ((FraggleFragment) frag).getFragmentTag());
        } else {
            ft.add(containerId, frag, ((FraggleFragment) frag).getFragmentTag());
            peek().onFragmentNotVisible();
        }
    }

    /**
     * Commits the transaction to the Fragment Manager.
     *
     * @param frag        Fragment to add
     * @param flags       Added flags to the Fragment configuration
     * @param ft          Transaction to add the fragment
     * @param containerId Target containerID
     */
    protected void performTransaction(Fragment frag, int flags, FragmentTransaction ft, int containerId) {
        configureAdditionMode(frag, flags, ft, containerId);
        ft.commitAllowingStateLoss();
    }

    /**
     * Peeks the last fragment in the Fragment stack.
     *
     * @return Last Fragment in the fragment stack
     * @throws java.lang.NullPointerException if there is no Fragment Added
     */
    protected FraggleFragment peek() {
        return ((FraggleFragment) fm
                .findFragmentByTag(fm.getBackStackEntryAt(fm.getBackStackEntryCount() - 1).getName()));
    }

    /**
     * Decides what to do with the backstack.
     * <p/>
     * The default behavior is as follows:
     * <p/>
     * FraggleManager will determine if the Fragment has a
     * {@link com.sefford.fraggle.interfaces.FraggleFragment#customizedOnBackPressed() customized action(s) for backpressing}
     * If so, the Fraggle Manager will execute its {@link com.sefford.fraggle.interfaces.FraggleFragment#onBackPressed() onBackPressed()} method.
     * <p/>
     * If the Fragment does not have any kind of custom action, then the FraggleManager will try
     * to determine if there is a {@link com.sefford.fraggle.interfaces.FraggleFragment#onBackPressedTarget()}.
     * <p/>
     * If positive, the FraggleManager will pop until it finds the Fragment.
     * <p/>
     * Otherwise will pop the inmediate Fragment and execute its {@link com.sefford.fraggle.interfaces.FraggleFragment#onFragmentVisible()}
     *
     * @param containerId Target container ID
     */
    public void popBackStack(int containerId) {
        FraggleFragment currentFragment = (FraggleFragment) fm.findFragmentById(containerId);
        if (!currentFragment.customizedOnBackPressed()) {
            if (currentFragment.onBackPressedTarget().isEmpty()) {
                fm.popBackStackImmediate();
                if (fm.getBackStackEntryCount() >= 1) {
                    peek().onFragmentVisible();
                }
            } else {
                //Clean all until containerId
                popBackStack(currentFragment.onBackPressedTarget(), 0);
            }
        } else {
            currentFragment.onBackPressed();
        }
    }

    /**
     * Pops the Fragment with the tag, applying the necessary flags
     *
     * @param tag   Tag to look for in the Fragment stack
     * @param flags Flags to apply for the
     */
    public void popBackStack(String tag, int flags) {
        fm.popBackStack(tag, flags);
    }

    /**
     * Checks Backstack Entry Count.
     *
     * @return Backstack Entry Count.
     */
    public int getBackStackEntryCount() {
        return fm.getBackStackEntryCount();
    }
}