How to properly implement the top back button in navigation drawer?
Asked Answered
S

3

2

I'm currently trying to add a navigation drawer to my weather app, so I watched a youtube tutorial on it and was able to implement it the way I wanted to until I realized that the tutorial I watched didn't cover how I can implement the up/top back button for the nav drawer so as a matter of fact, I currently cannot get back to my default fragment after opening any of the nav tabs. I searched several sites and youtube videos looking for tutorials on how to implement the top back button but haven't seen/been able to find it. I also searched this site and still haven't found anyone with a similar issue here. Please, can anyone be of help?

Here's a screenshot of how my app currently is: https://i.sstatic.net/SeSjV.png but if I open any of the navbar options i.e settings and click back, I can't return back to the default fragment where the weather is displayed. It also doesn't have an up-back button as well.

Currently, clicking back only exits the app.

This is the only code I've tried and it didn't work:

@Override
public boolean onOptionsItemSelected(MenuItem item) {

    if (item.getItemId() == android.R.id.home) {
        int backStackCount = fragmentManager.getBackStackEntryCount();//check currently how many frags loaded
        if (backStackCount > 0) {
            fragmentManager.popBackStack(); //go back to previously loaded fragment
        }   
    }

    return super.onOptionsItemSelected(item);
}

It gave the following error:

error: cannot find symbol int backStackCount = fragmentManager.getBackStackEntryCount();//check currently how many frags loaded

Here's my activity code:

public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
    private DrawerLayout drawer;
    // Last update time, click sound, search button, search panel.
    TextView timeField;
    MediaPlayer player;
    ImageView Search;
    EditText textfield;
    // For scheduling background image change(using constraint layout, start counting from dubai, down to statue of liberty.
    ConstraintLayout constraintLayout;
    public static int count = 0;
    int[] drawable = new int[]{R.drawable.dubai, R.drawable.norway, R.drawable.eiffel_tower, R.drawable.hong_kong, R.drawable.statue_of_liberty,
            R.drawable.beijing, R.drawable.chicago, R.drawable.colombia, R.drawable.vienna,R.drawable.tokyo};
    Timer _t;

    private WeatherDataViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        // use home activity layout.

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        // Allow activity to make use of the toolbar

        drawer = findViewById(R.id.drawer_layout);
        NavigationView navigationView = findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        viewModel = new ViewModelProvider(this).get(WeatherDataViewModel.class);

        // Trigger action to open & close nevigation drawer
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar
                , R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        timeField = findViewById(R.id.textView9);
        Search = findViewById(R.id.imageView4);
        textfield = findViewById(R.id.textfield);
        //  find the id's of specific variables.

        BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
        // host 3 fragments along with bottom navigation.
        final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
        assert navHostFragment != null;
        final NavController navController = navHostFragment.getNavController();
        NavigationUI.setupWithNavController(bottomNavigationView, navController);

        // Make hourly & daily tab unusable
        bottomNavigationView.setOnNavigationItemSelectedListener(item -> {

            if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
                getSupportFragmentManager().popBackStack();
            }
            return false;
        });

        navController.addOnDestinationChangedListener((controller, destination, arguments) -> navController.popBackStack(destination.getId(), false));

        // For scheduling background image change
        constraintLayout = findViewById(R.id.layout);
        constraintLayout.setBackgroundResource(R.drawable.dubai);
        _t = new Timer();
        _t.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                // run on ui thread
                runOnUiThread(() -> {
                    if (count < drawable.length) {

                        constraintLayout.setBackgroundResource(drawable[count]);
                        count = (count + 1) % drawable.length;
                    }
                });
            }
        }, 5000, 5000);

        Search.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // make click sound when search button is clicked.
                player = MediaPlayer.create(HomeActivity.this, R.raw.click);
                player.start();

                getWeatherData(textfield.getText().toString().trim());
                // make use of some fragment's data

                Fragment currentFragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
                if (currentFragment instanceof FirstFragment) {
                    FirstFragment firstFragment = (FirstFragment) currentFragment;
                    firstFragment.getWeatherData(textfield.getText().toString().trim());
                } else if (currentFragment instanceof SecondFragment) {
                    SecondFragment secondFragment = (SecondFragment) currentFragment;
                    secondFragment.getWeatherData(textfield.getText().toString().trim());
                } else if (currentFragment instanceof ThirdFragment) {
                    ThirdFragment thirdFragment = (ThirdFragment) currentFragment;
                    thirdFragment.getWeatherData(textfield.getText().toString().trim());
                }
            }

            private void getWeatherData(String name) {

                ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);

                Call<Example> call = apiInterface.getWeatherData(name);

                call.enqueue(new Callback<Example>() {
                    @Override
                    public void onResponse(@NonNull Call<Example> call, @NonNull Response<Example> response) {

                        try {
                            assert response.body() != null;
                            timeField.setVisibility(View.VISIBLE);
                            timeField.setText("First Updated:" + " " + response.body().getDt());
                        } catch (Exception e) {
                            timeField.setVisibility(View.GONE);
                            timeField.setText("First Updated: Unknown");
                            Log.e("TAG", "No City found");
                            Toast.makeText(HomeActivity.this, "No City found", Toast.LENGTH_SHORT).show();
                        }
                    }

                    @Override
                    public void onFailure(@NotNull Call<Example> call, @NotNull Throwable t) {
                        t.printStackTrace();
                    }

                });
            }

        });
    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.settings_id:
                getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
                        new Settings()).commit();
                break;
            case R.id.ads_upgrade_id:
                getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
                        new Upgrade()).commit();
                break;
            case R.id.privacy_policy_id:
                getSupportFragmentManager().beginTransaction().replace(R.id.fragment,
                        new Privacy_Policy()).commit();
                break;
        }
        drawer.closeDrawer(GravityCompat.START);

        return true;
    }

    @Override
    public void onBackPressed() {
        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else {
            super.onBackPressed();
            // Open/close drawer animation
        }
    }
Shabuoth answered 22/12, 2021 at 21:33 Comment(5)
Please if there's anything I should correct/add, let me knowShabuoth
Hey Richard.. I think this could help you outCampinas
@zain The preview you shared is quite different from how I would love my app to be done. When you clicked the navbar on your app, it displayed home as the first bar on that side meaning that the entire app is being controlled by the navbar, it has no other default fragment normally controlled before adding additional items to the app using the navbar. Also, your codes were written in Kotlin.Shabuoth
@zain I don't want my app to be controlled using the navbar, I designed the navbar just for additional things like settings, upgrade to remove ads, and privacy policy(like how AccuWeather nav bar is). I've posted a screenshot of it for easy Identification. Any suggestion on how I can go about it or do you know of any way to do it? If not, I'll have to bounty the code, thanks for your previous suggestion.Shabuoth
I've adjusted that on java; pls check the answerCampinas
C
1
if (item.getItemId() == android.R.id.home) {
    int backStackCount = fragmentManager.getBackStackEntryCount();//check currently how many frags loaded
    if (backStackCount > 0) {
        fragmentManager.popBackStack(); //go back to previously loaded fragment
    }   
}

Currently, clicking back only exits the app.

Using popBackStack() will pop up the backstack making your app exists, but you just need to return to the default fragment.

To fix this, you need to change the behavior of the drawer burger button, so that it can be used sometimes to open the drawer navView layout, and other times to back to the default fragment; the later is when you want to add the top back button instead.

how I can implement the up/top back button for the nav drawer

This requires to access the setToolbarNavigationClickListener method which enables you add a listener to burger clicks.

In this case you need to return back to the home fragment as you need, in the onCreate() method add:

toggle.setToolbarNavigationClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Enable the functionality of opening the side drawer, when the burger icon is clicked
        toggle.setDrawerIndicatorEnabled(true); // Show the burger icon & enable the drawer funcionality
        navController.navigate(R.id.home); // Back to default fragment (replace home with your default fragment id in the navGraph)
    }
});

The remaining part is to show the back button whenever you want to go to certain fragment

And use toggle.setDrawerIndicatorEnabled() to enable/disable the functionality of opening/closing the drawer when the home/burger icon is clicked

navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
    @Override
    public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {

        // Repeat this condition for all the Fragments that you want to show the back button
        if (destination.getId() == R.id.settings_id) { // replace `settings_id` with your fragment id in the navGraph that you want to show the back button
            // Disable the functionality of opening the side drawer, when the burger icon is clicked & show the UP button instead
            toggle.setDrawerIndicatorEnabled(false);

        } 

    }
});
Campinas answered 4/1, 2022 at 21:51 Comment(1)
Good day zain, please can you look into this question? https://mcmap.net/q/1634130/-how-to-navigate-back-to-activity-from-navigation-drawer-tabs/16020235 I've gotten an answer for it that works but it interferes with my sound, I believe you'll be able to handle it since you have the sound informationShabuoth
J
1

You can try add app:startDestination="@+id/your default fragment" attribute in your mobile_navigation.xml's root layout.

mobile_navigation.xml is a file where you manage your fragments like in this example

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/nav_home"> 

<fragment
    android:id="@+id/nav_home"
    android:name="com.example.navd.ui.home.HomeFragment"
    android:label="@string/menu_home"
    tools:layout="@layout/fragment_home" />

<fragment
    android:id="@+id/nav_gallery"
    android:name="com.example.navd.ui.gallery.GalleryFragment"
    android:label="@string/menu_gallery"
    tools:layout="@layout/fragment_gallery" />

<fragment
    android:id="@+id/nav_slideshow"
    android:name="com.example.navd.ui.slideshow.SlideshowFragment"
    android:label="@string/menu_slideshow"
    tools:layout="@layout/fragment_slideshow" />
Jamesy answered 4/1, 2022 at 5:12 Comment(1)
I already have that addedShabuoth
C
1
if (item.getItemId() == android.R.id.home) {
    int backStackCount = fragmentManager.getBackStackEntryCount();//check currently how many frags loaded
    if (backStackCount > 0) {
        fragmentManager.popBackStack(); //go back to previously loaded fragment
    }   
}

Currently, clicking back only exits the app.

Using popBackStack() will pop up the backstack making your app exists, but you just need to return to the default fragment.

To fix this, you need to change the behavior of the drawer burger button, so that it can be used sometimes to open the drawer navView layout, and other times to back to the default fragment; the later is when you want to add the top back button instead.

how I can implement the up/top back button for the nav drawer

This requires to access the setToolbarNavigationClickListener method which enables you add a listener to burger clicks.

In this case you need to return back to the home fragment as you need, in the onCreate() method add:

toggle.setToolbarNavigationClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Enable the functionality of opening the side drawer, when the burger icon is clicked
        toggle.setDrawerIndicatorEnabled(true); // Show the burger icon & enable the drawer funcionality
        navController.navigate(R.id.home); // Back to default fragment (replace home with your default fragment id in the navGraph)
    }
});

The remaining part is to show the back button whenever you want to go to certain fragment

And use toggle.setDrawerIndicatorEnabled() to enable/disable the functionality of opening/closing the drawer when the home/burger icon is clicked

navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
    @Override
    public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {

        // Repeat this condition for all the Fragments that you want to show the back button
        if (destination.getId() == R.id.settings_id) { // replace `settings_id` with your fragment id in the navGraph that you want to show the back button
            // Disable the functionality of opening the side drawer, when the burger icon is clicked & show the UP button instead
            toggle.setDrawerIndicatorEnabled(false);

        } 

    }
});
Campinas answered 4/1, 2022 at 21:51 Comment(1)
Good day zain, please can you look into this question? https://mcmap.net/q/1634130/-how-to-navigate-back-to-activity-from-navigation-drawer-tabs/16020235 I've gotten an answer for it that works but it interferes with my sound, I believe you'll be able to handle it since you have the sound informationShabuoth
G
0

I think you should override the onKeyDown at the MainActivity, which controlls the behavior of the virtual back button of the entire app. Right now any click on this button exits the app because you have only one "page" that holds the fragments and switches between them, so if you go back from this single page, you exit the app...

I have a public String at the MainActivty that holds the current_fragment and I update it each time I switch fragment:

MainActivity (before onCreate())

public String current_page = "";

any Fragment

  public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {

    mainActivity.current_fragment = "anyFragment";

home Fragment

public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {

    mainActivity.current_fragment = "home_page";

MainActivity

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {

    if (keyCode == KeyEvent.KEYCODE_BACK) { // you only want to treat the back click, not any click...

        if (drawerLayout.isDrawerOpen(GravityCompat.START)) { // if drawer is open, close it

            drawerLayout.closeDrawer(GravityCompat.START);
            return true;

        } else if (!current_page.equals ("home_page") {

            setHomePage(); // a method that switch the fragment to homePageFragment
            return true;

        } 
    }

    return super.onKeyDown(keyCode, event);
}

About setHomePage () - that is a method for switching between Fragments, like you do at your app already...

Grugru answered 29/12, 2021 at 9:25 Comment(6)
Okay, thanks for your suggestion, I'm currently working on it. I'm using this code after my onBackPressed(). I hope that's the right place to use it? Secondly, the drawerLayout part showed an error so I replaced it with drawer(the official name, not the id) and it stopped. Finally, please what do the "current_page" and "setHomePage" parts represent? because I'm getting errors on both parts.Shabuoth
@Richard Wilson First, don't use onBackPresed() at all. Use this method below, onKeyDown, at MainActivity. Secondly, yes, of course you need to use your DrawerLayout variable name. Finally, I edited and extend my explanation about current_page and setHomePage. See belowGrugru
Now I'm really confused about where to write the "any Fragment and "home Fragment" code. I wrote the "any Fragment" codes on both my privacy policy and upgrade to remove ads fragments and they both bring the error: cannot resolve conflict "current_fragment" while I wrote the "home Fragment" code on the Settings fragment(which is the first tab on the navbar) and it brings the error cannot resolve conflict "home Fragment". Then HomeActivity gives the error: cannot resolve method "setHomePage".Shabuoth
I read and practiced java for 6 months using head first java. Then 3 months on practical android before I started this as my first project. So you can't expect me to be an expert as just my firstShabuoth
but regardless, I've later figured out that the problem with this is a different thing which I wrote in my new question https://mcmap.net/q/1634131/-how-to-display-my-navigation-bar-contents-without-interruption/16020235Shabuoth
I could have deleted this one but StackOverflow warned against it.Shabuoth

© 2022 - 2024 — McMap. All rights reserved.