1

I have an application where the MainActiviy works with a bottom navigation which switches between full screen fragments.

I've learnt to control the back button navigation by ensuring I add each fragment to the backstack when created.

fragmentManager.beginTransaction().add(R.id.contentContainer, fragment, fragment_tag).addToBackStack(fragment_tag).commit();

There is one type of fragment, the loading screen fragment, that I do not want added to the backstack so I exclude the addToBackStack() method when creating the fragment.

As shown in the gif below Somehow the loading fragment still appears when pressing the back button even though it is not on the backstack (I've confirmed this with the debugger).

Demo of loading fragment appearing even though not on backstack

If anyone could give me a hand in figuring out why it is showing up I'd be really grateful, it has plagued me for about a week and I'm out of ideas!

Here is the code:

package *package name*;

import *all import statements*

public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<ArrayList> {
    BottomNavigation mBottomBar;
    private FloatingActionButton fab;
    private FirebaseDatabase database;
    private DatabaseReference DB_Storage_Ref, DB_Master_Ref;
    FragmentManager fragmentManager;
    CustomBottomBarSelectionListener bbListener;
    CustomBackStackChangeListener cBSCL;

    ArrayList<IngredientCard> master = new ArrayList<>();
    ArrayList<IngredientCard> all = new ArrayList<>();
    ArrayList<IngredientCard> fridge = new ArrayList<>();
    ArrayList<IngredientCard> freezer = new ArrayList<>();
    ArrayList<IngredientCard> pantry = new ArrayList<>();
    ArrayList<IngredientCard> ingredient_imports = new ArrayList<>();
    int arraysLoaded = 0;
    boolean loadingComplete = false;

    ArrayList<String> storageLocationList = new ArrayList<>();
    Map<String, ArrayList<IngredientCard>> storageLocationMapLists = new HashMap<>();

    final String[] tag = {null};
    boolean backButtonPressed = false;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

//      Establish FirebaseDatabase Instance and required DB References
        database = FirebaseDatabase.getInstance();
        DB_Storage_Ref = database.getReference("Storage");
        DB_Master_Ref = database.getReference("Master");

//      These Storage location must match branch titles in Firebase JSON database
//      Create a list of all Storage Room Titles (matching realtime database branch names)
        storageLocationList.add("All");
        storageLocationList.add("Fridge");
        storageLocationList.add("Freezer");
        storageLocationList.add("Pantry");

//      Create a hashmap mapping all storage room arrays to the associated storage room titles.
        storageLocationMapLists.put("All", all);
        storageLocationMapLists.put("Fridge", fridge);
        storageLocationMapLists.put("Freezer", freezer);
        storageLocationMapLists.put("Pantry", pantry);

//      Associate UI to Variables
        Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
        fab = (FloatingActionButton) findViewById(R.id.fab);
        mBottomBar = (BottomNavigation) findViewById(R.id.BottomNavigation);

        fragmentManager = getSupportFragmentManager();

        bbListener = new CustomBottomBarSelectionListener(this);
        mBottomBar.setOnMenuItemClickListener(bbListener);

        cBSCL = new CustomBackStackChangeListener(this);
        fragmentManager.addOnBackStackChangedListener(cBSCL);

//      Load arrays with data from Firebase Database.
        populateArrays();

//      Customise UI config where necessary
        setSupportActionBar(myToolbar);

        mBottomBar.setDefaultSelectedIndex(2);
        tag[0] = PLAN_FRAGMENT_TAG;
        fragmentManager.beginTransaction().add(R.id.contentContainer, new PlanFragment(), tag[0]).commit();

        //      Set onClick Listener for FAB button. The FAB should change/animate as user switches between BottomBar options
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Fragment fragment;

//              Find the IngredientsFragment in the Fragment Manager
                fragment = fragmentManager.findFragmentByTag(INGREDIENT_FRAGMENT_TAG);

//              If the Fragment exists and is visible then carryout action
                if (fragment != null && fragment.isVisible()) {
                    Intent SelectIngredient = new Intent(getBaseContext(), Ingred_MasterList.class);
                    Bundle args = new Bundle();
                    args.putParcelableArrayList(ARG_INGREDIENTS_LIST, master);
                    args.putStringArrayList(ARG_STORAGE_LOCATIONS, storageLocationList);
                    SelectIngredient.putExtras(args);
                    startActivity(SelectIngredient,args);
                }
            }
        });

    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);

        if(getIntent().getExtras() != null) {
            Bundle args = getIntent().getExtras();

            if (args.containsKey(INGREDIENT_IMPORTS)) {
                ingredient_imports = (args.getParcelableArrayList(INGREDIENT_IMPORTS));
                bbListener.switchFragment(LOADING_FRAGMENT_TAG, new LoadingFragment());
                fragmentManager.popBackStackImmediate();
                distributeItems(ingredient_imports);
            }
        }
    }

    private void distributeItems(ArrayList<IngredientCard> array) {
        for(IngredientCard ingredient : array){
            DB_Storage_Ref.child("All").child(ingredient.getItemName()).setValue(ingredient);
            DB_Storage_Ref.child(ingredient.getStorageLocation()).child(ingredient.getItemName()).setValue(ingredient);
        }
        ingredient_imports.clear();
    }

    private void populateArrays() {

//      Cycle through storageLocationList array and add the storage location title (which must match a branch name on the Firebase Database.
        for (int i = 0; i < storageLocationList.size(); i++) {
            Bundle args = new Bundle();
            args.putString(TEMP_BUNDLE_STORAGE_TITLE, storageLocationList.get(i));

//          For each storage location create a loader to retrieve its data from the Firebase Database
            getSupportLoaderManager().initLoader(i, args, this);
        }
//      Create a loader that retrieves the master list of food icons
        getSupportLoaderManager().initLoader(MASTER_LIST_ARRAY_ID, null, this);
    }

    @Override
    public Loader<ArrayList> onCreateLoader(int id, Bundle args) {
        String DBbranch;

        if (args == null) {
            //If bundle args don't exist assume we want data from 'Master' branch of DB
            DBbranch = "Food_Items";
            return new IngredientsListLoader(this, DB_Master_Ref, DBbranch, this);
        } else {
            //If bundle args exist, extract them and add them as IngredientListLoader variable
            DBbranch = args.getString(TEMP_BUNDLE_STORAGE_TITLE);
            return new IngredientsListLoader(this, DB_Storage_Ref, DBbranch, this);
        }
    }

    @Override
//  Should be called after loadInBackground has completed but seems to return earlier. The method returnResults has been created in IngredientsListLoader to deal with this.
    public void onLoadFinished(Loader<ArrayList> loader, ArrayList data) {

        if (loader.getId() == MASTER_LIST_ARRAY_ID) {
//          if MASTER_LIST Loader set master ArrayList to data
            master = data;
        } else {
//          cycle through each item in storageLocationList Array (the Array position -eq loader id) and replace Array in storageLocationList position with data Array
            for (int i = 0; i < storageLocationList.size(); i++) {
                if (loader.getId() == i) {
                    storageLocationMapLists.put(storageLocationList.get(i), data);
                }
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<ArrayList> loader) {
    }

    @Override
    public void onBackPressed() {
        backButtonPressed = true;
        if (fragmentManager.getBackStackEntryCount() > 0) {
            Log.i("MainActivity", "popping fragment backstack");
            fragmentManager.popBackStack();
        } else {
            Log.i("MainActivity", "nothing on backstack, calling super");
            super.onBackPressed();
        }

    }

    void bottomBarUpdate(){
        Fragment currentBackStackFragment = getBackstackFragment();

        if(currentBackStackFragment instanceof Ingredients_BottomBarFrag || currentBackStackFragment instanceof LoadingFragment){
            mBottomBar.setSelectedIndex(0,true);
            return;
        }
        if(currentBackStackFragment instanceof MealsFragment){
            mBottomBar.setSelectedIndex(1,true);
            return;
        }
        if(currentBackStackFragment instanceof PlanFragment){
            mBottomBar.setSelectedIndex(2,true);
            return;
        }
        if(currentBackStackFragment instanceof ShoppingFragment){
            mBottomBar.setSelectedIndex(3,true);
            return;
        }
        if(currentBackStackFragment instanceof SettingsFragment){
            mBottomBar.setSelectedIndex(4,true);
            return;
        }
    }

    private Fragment getBackstackFragment(){
        String fragmentTag;

        if(fragmentManager.getBackStackEntryCount() > 0) {
            fragmentTag = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1).getName();

        }else{
            fragmentTag = PLAN_FRAGMENT_TAG;
            fragmentManager.beginTransaction().add(R.id.contentContainer, new PlanFragment(), tag[0]).commit();
        }
        return fragmentManager.findFragmentByTag(fragmentTag);

    }
}

class IngredientsListLoader extends AsyncTaskLoader {
    private DatabaseReference DBRef;
    private String DBBranch;
    private ArrayList<IngredientCard> food_Items_List = new ArrayList<>();
    private MainActivity ma;

    IngredientsListLoader(Context context, DatabaseReference instance, String DBBranch, MainActivity main) {
        super(context);
        DBRef = instance;
        this.DBBranch = DBBranch;
        ma = main;
        forceLoad();
    }

    @Override
    public ArrayList<IngredientCard> loadInBackground() {
        food_Items_List.clear();
        DBRef = DBRef.child(DBBranch);

        CustomListener cl = new CustomListener(ma);
        DBRef.addValueEventListener(cl);

        Log.v("TAG", "Returning LIST of size " + food_Items_List.size());

        return cl.returnResults();
    }
}

class CustomListener implements ValueEventListener {
    private ArrayList<IngredientCard> food_Items_List = new ArrayList<>();
    private MainActivity ma;


    CustomListener(MainActivity main){
        ma = main;
    }

    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        Iterable<DataSnapshot> children = dataSnapshot.getChildren();
        food_Items_List.clear();

        for (DataSnapshot child : children) {
            IngredientCard ingredientCard = child.getValue(IngredientCard.class);
            food_Items_List.add(ingredientCard);
            Log.v("ValueEventLisenter", "Accessing Firebase!");
        }
        returnResults();
        removeLoadingScreen();
    }


    @Override
    public void onCancelled(DatabaseError databaseError) {

    }

    ArrayList<IngredientCard> returnResults() {
        return food_Items_List;
    }

    void removeLoadingScreen(){
        //If all arrays have been loaded and the ingredient_import array has been cleared...
        if(ma.arraysLoaded == ma.storageLocationList.size() && ma.ingredient_imports.size() == 0) {
            ma.loadingComplete = true;

            // tag[0] represents the tag of the currently displayed fragment. It changes to the first parameter of the switchFragment method each time it is called.
            //If the displayed fragment is the LOADING_FRAGMENT switch it out for the INGREDIENT_FRAGMENT
            if (ma.tag[0] == LOADING_FRAGMENT_TAG) {
                ma.bbListener.switchFragment(INGREDIENT_FRAGMENT_TAG, new Ingredients_BottomBarFrag());
            }
        }else{
            //For each loader that completes and calls this method, the values of arraysLoaded increases until it matches the number of loaders expected to return.
            ma.arraysLoaded++;
        }
    }


}

class CustomBottomBarSelectionListener implements OnMenuItemSelectionListener {
    private MainActivity ma;

    CustomBottomBarSelectionListener(MainActivity main){
        ma = main;
    }

    @Override
    public void onMenuItemSelect(@IdRes int tabId, int position, boolean fromUser) {

        //if this is triggered via pressing the back button, then simply return as fragmentManager.popBackStack() will handle switching fragments.
        if(ma.backButtonPressed){
            ma.backButtonPressed = false;
            return;
        }

        switch (tabId) {
            case R.id.menu_ingredients:
                //if items have not completed loading show loading screen
                if(!ma.loadingComplete && ma.ingredient_imports.size() == 0){
                    switchFragment(LOADING_FRAGMENT_TAG, new LoadingFragment());
                }else{
                    switchFragment(INGREDIENT_FRAGMENT_TAG, new Ingredients_BottomBarFrag());
                }
                break;
            //TODO: Have RecyclerView scroll position restored when fragment comes back into view
            case R.id.menu_meals:
                switchFragment(MEAL_FRAGMENT_TAG, new MealsFragment());
                break;

            case R.id.menu_plan:
                switchFragment(PLAN_FRAGMENT_TAG, new PlanFragment());
                break;

            case R.id.menu_groceries:
                switchFragment(SHOPPING_FRAGMENT_TAG, new ShoppingFragment());
                break;

            case R.id.menu_settings:
                switchFragment(SETTINGS_FRAGMENT_TAG, new SettingsFragment());
                break;
        }
    }

    @Override
    public void onMenuItemReselect(@IdRes int i, int i1, boolean b) {
        //TODO Add reselect code
    }

    protected void switchFragment(String fragTag, Fragment frag) {

//      Sets a reference of current fragments Tag
        ma.tag[0] = fragTag;

        if(ma.tag[0]== LOADING_FRAGMENT_TAG){
            //load LOADING_FRAGMENT but DONT add to backstack
            ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).commit();
        }else {
            //Add every other fragment to backstack
            ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).addToBackStack(ma.tag[0]).commit();
        }
    }
};

class CustomBackStackChangeListener implements FragmentManager.OnBackStackChangedListener{
    private MainActivity ma;


    CustomBackStackChangeListener(MainActivity main){
        ma = main;
    }

    @Override
    public void onBackStackChanged() {

        //If BackStackChanged is triggered due to anything other than pressing the back button, return.
        if(!ma.backButtonPressed){
            return;
        }

        ma.bottomBarUpdate();
    }
}

IMPROVED CODE DEMONSTRATION (sorry, adding code in the comments is horrible so I'll do it here)

    protected void switchFragment(String fragTag, Fragment frag) {

//      Sets a reference of current fragments Tag
    ma.tag[0] = fragTag;

    if(ma.tag[0]== LOADING_FRAGMENT_TAG){
        //load LOADING_FRAGMENT but DONT add to backstack
        ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).commit();
    }else {
        Fragment fragment = ma.getSupportFragmentManager().findFragmentByTag(LOADING_FRAGMENT_TAG);
        if(fragment != null && fragment.isVisible()){
            ma.fragmentManager.beginTransaction().remove(fragment);
        }
        //Add every other fragment to backstack
        ma.fragmentManager.beginTransaction().add(R.id.contentContainer, frag, ma.tag[0]).addToBackStack(ma.tag[0]).commit();
    }
}

1 Answers1

0

whenever you are switching to another fragment from that fragment which you do not want too include in the backstack you can finish that fragment first before switching. That can be done by declaring the stating object of the fragment and giving that object its instance. then where you are switching the fragment check with the help of the fragment name if its static object is null or not. if its not null finish the fragment

Anmol317
  • 1,356
  • 1
  • 14
  • 31