4

I spent my whole day now looking for an answer and didn't found any solution that suited this question.

I am looking for a way to create a BottomNavigation usage similar to the one of the Instagram or PlayKiosk app. Fragments should be added to the back stack only once. When pressing the back button I'd expect the app to jump back to the last Fragment that was visited and the button of the BottomNavigation to fit that Fragment.

Currently I use the following code:

//BottomNavigationListener
private BottomNavigationView.OnNavigationItemSelectedListener buttonNavigationItemSelectedListener
        = new BottomNavigationView.OnNavigationItemSelectedListener() {

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        switch (item.getItemId()) {
            case R.id.navigation_game:
                currentFragment = GameFragment.newInstance();
                fragmentManager.beginTransaction()
                        .addToBackStack(TAG_FRAGMENT_GAME)
                        .replace(R.id.content_frame, currentFragment, TAG_FRAGMENT_GAME)
                        .commit();
                break;
            case R.id.navigation_tournament:
                currentFragment = TournamentFragment.newInstance();
                fragmentManager.beginTransaction()
                        .addToBackStack(TAG_FRAGMENT_TOURNAMENT)
                        .replace(R.id.content_frame, currentFragment, TAG_FRAGMENT_TOURNAMENT)
                        .commit();
                break;
            case R.id.navigation_history:
                break;
        }
        return true;
    }

};

But this leads to the problem that I could press the button of my BottomNavigation a couple of times and for each of this clicks a new Fragment would be instantiated. Also the BottomNavigation buttons are not set according to the Fragments.

I found this answer but it didn't work out Prevent The Same Fragment From Stacking More Than Once ( addToBackStack)

Luke P
  • 113
  • 2
  • 5
  • You can handle onBackPressed method ? Keep last fragment when it opened and when you press back button, just replace the last fragment you visited. – Cagri Yalcin Oct 03 '17 at 17:03

1 Answers1

0

You can use following structure to have the same working logic of Instagram.

First create a custom limited unique queue class. It only holds last N items, where N is the limit number (or maximum item count) passed to its constructor. Moreover, this type of queue keeps only one instance of a class. If an instance of class A is already in the queue and another instance of class A is to be added to the queue, the former instance is removed first and then the new object is inserted.

public class LimitedUniqueQueue<E> extends LinkedList<E> {

    private int limit;

    public LimitedUniqueQueue(int limit) {
        this.limit = limit;
    }

    @Override
    public boolean add(E o) {
        // For uniqueness
        for (int i = 0; i < super.size(); i++) {
            E item = super.get(i);
            if (item.getClass() == o.getClass()) {
                super.remove(i);
                break;
            }
        }
        boolean added = super.add(o);
        // For size limit
        while (added && size() > limit) {
            super.remove();
        }
        return added;
    }
}

Then update your Activity as follows:

public class MainActivity extends AppCompatActivity {

    private BottomNavigationView navigation;
    private LimitedUniqueQueue<Fragment> queue;
    ...
    private BottomNavigationView.OnNavigationItemSelectedListener onNavigationItemSelectedListener
                = new BottomNavigationView.OnNavigationItemSelectedListener() {

        Fragment gameFragment;
        Fragment tournamentFragment;
        Fragment profileFragment;

        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            switch (item.getItemId()) {
                case R.id.navigation_game:
                    if (gameFragment == null) {
                        gameFragment = new GameFragment();
                    }
                    transaction.replace(R.id.content, gameFragment).commit();
                    queue.add(gameFragment);
                    return true;
                case R.id.navigation_tournament:
                    if (tournamentFragment == null) {
                        tournamentFragment = new TournamentFragment();
                    }
                    transaction.replace(R.id.content, tournamentFragment).commit();
                    queue.add(tournamentFragment);
                    return true;
                case R.id.navigation_profile:
                    if (profileFragment == null) {
                        profileFragment = new ProfileFragment();
                    }
                    transaction.replace(R.id.content, profileFragment).commit();
                    queue.add(profileFragment);
                    return true;
            }
            return false;
        }

    };

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

        navigation = (BottomNavigationView) findViewById(R.id.navigation);
        queue = new LimitedUniqueQueue<>(navigation.getMenu().size());

        navigation.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener);
        navigation.setSelectedItemId(R.id.navigation_game);
        ...
    }

    @Override
    public void onBackPressed() {
        if (queue.size() > 1) {
            queue.removeLast();
            Fragment previousFragment = queue.getLast();
            if (previousFragment instanceof GameFragment) {
                navigation.setSelectedItemId(R.id.navigation_game);
            } else if (previousFragment instanceof TournamentFragment) {
                navigation.setSelectedItemId(R.id.navigation_tournament);
            } else if (previousFragment instanceof ProfileFragment) {
                navigation.setSelectedItemId(R.id.navigation_profile);
            }
        } else {
            super.onBackPressed();
        }
    }

    ...
}
Mehmed
  • 2,880
  • 4
  • 41
  • 62
  • Just tested it now and it works fine for me. Thank you very much. The only addition I made was to add already a new GameFragment to the queue in the onCreate to have it already in the queue at start and as my default fallback on back button pressing. – Luke P Oct 05 '17 at 20:54
  • You are welcome. I am calling `navigation.setSelectedItemId(R.id.navigation_game)` in `onCreate()` method in order to show GameFragment at launch and implicitly add it to the `queue` in `onNavigationItemSelected()`. In any case, I am glad it worked. – Mehmed Oct 05 '17 at 23:37