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).
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();
}
}