I built a customizable navigation drawer from scratch(didn't make use of the default drawer provided by Android Studio). In my weather app's navigation bar menu https://i.stack.imgur.com/SIjdx.jpg, whenever I select an option on the menu(say settings), it displays the contents of the option along with the bottom navigation view and my Activity's Toolbar contents which comprises of the nav hamburger icon, the edittext and the search button(the activity hosting my 3 fragments) which spoils the app and makes it look very ugly i.e. https://i.stack.imgur.com/gxj5n.jpg (From that screenshot, the entire content should be empty if implemented well). The case is the same for the other bar menu options. All I want is an empty space to work on, I want the app to only display the navigation bar contents without the rest. Example; https://i.stack.imgur.com/3Jtga.png Please how should I do this?
The view of the Navigation Menu is controlled by this code(on line 185):
#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;
}
"fragment" there represents that I'm currently using my fragment's container view on my activity to display the Nav menu contents which I know is wrong for sure, so what should I use in replace? I lack strong experience as it's my first time building an app and I've tirelessly spent 3 hours on my own trying to figure out the issue which proved abortive.
Here is 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 navigation 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
}
}
}
In case you require any other code to look into the issue, please let me know. I'm just trying to avoid posting too much
EDIT:
My old bottomtabs nav graph:
<?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/my_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.viz.lightweatherforecast.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" />
<fragment
android:id="#+id/secondFragment"
android:name="com.viz.lightpreciseweatherforecast.SecondFragment"
android:label="fragment_second"
tools:layout="#layout/fragment_second" />
<fragment
android:id="#+id/thirdFragment"
android:name="com.viz.lightpreciseweatherforecast.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third" />
</navigation>
My new nav bar graph:
<?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/bar_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.viz.lightweatherforecast.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" />
<fragment
android:id="#+id/settings_id"
android:name="com.viz.lightweatherforecast.Settings"
android:label="#string/settings"
tools:layout="#layout/settings" />
<fragment
android:id="#+id/ads_upgrade_id"
android:name="com.viz.lightweatherforecast.Upgrade"
android:label="#string/upgrade_to_remove_ads"
tools:layout="#layout/upgrade" />
<fragment
android:id="#+id/privacy_policy_id"
android:name="com.viz.lightweatherforecast.Privacy_Policy"
android:label="#string/privacy_policy"
tools:layout="#layout/privacy_policy"/>
</navigation>
You are using navigation architecture components, so the navController is the one that should control fragment transactions, you are doing that right with BottomNavigationView.
But within the navDrawer you are doing the transaction through the supportFragmentManager which should be done through the navController instead as both handle the navigation differently.
whenever I select an option on the menu(say settings), it displays the contents of the option along with the bottom navigation view
That is because the BottomNavView is a part of the activity, and you need to move it to a fragment; this requires to change the navigation design of your app; to do that change your app navigation like the below:
Main navigation:
<navigation
..... >
<fragment
android:name="......HomeFragment"/>
<fragment
android:name="......SettingFragment"/>
<fragment
android:name="......AdsUpgradeFragment"/>
<fragment
android:name="......PrivacyPolicyFragment"/>
</navigation>
The HomeFragment is the fragment that should hold the BottomNaviagtionView instead of the activity; and when you navigate to the SettingFragment, the navConroller will replace the entire fragment in the navHostFragment, and therefore the BottomNaviagtionView won't be shown.
my Activity's Toolbar contents which comprises of the nav hamburger
icon, the edittext and the search button(the activity hosting my 3
fragments) which spoils the app and makes it look very ugly
Unlike the BottomNaviagtionView, you can't do that with your toolBar that is used as the supportActionBar, because setting supportActionBar more than once in order to change its look; will duplicates it; so you have to accept a single toolbar; but instead you can hide/show the layout that holds the search button & the EditText whenever the destination changes:
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
LinearLayout searchBar = findViewById(R.id.searchbar); // change searchbar according to the layout id that holds the search button and the EditText
if (destination.getId() == R.id.nav_home) {
searchBar.setVisibility(View.VISIBLE);
} else {
searchBar.setVisibility(View.GONE);
}
});
and yes they currently exit the app when clicking back
To exit the app whenever, the bottom back button is pressed in any fragment use OnBackPressedDispatcher() within onCreateView() of those fragment (in your case SettingFragment, PrivacyPolicyFragment, & AdsUpgradeFragment):
And make sure that appBarConfiguration doesn't reference those fragments so, that the UP button can be shown instead of the burger.
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
// Exit the app when back is pressed
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
requireActivity().finishAndRemoveTask();
else requireActivity().finish();
}
});
Also, make sure that your setup the DrawerLayout & its navView in the navController with:
NavigationView navView = findViewById(....);
appBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home) // remove up button from all these fragments >> Keep the up/back button in R.id.settings_id, R.id.settings_id, ads_upgrade_id, privacy_policy_id
.setOpenableLayout(drawer)
.build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
And to make the Home fragment hidden by default in the navDrawer:
navView.getMenu().findItem(R.id.nav_home).setVisible(false); // adjust R.id.nav_home to yours
UPDATE
I currently have a single nav graph. The firstFragment represents the
today, 2nd - hourly and 3rd
I'm using is for my other weather tabs but the one you're suggesting
is for the navbar menu, should I replace yours with mine or just
create a new one for your suggestion?
You should use two navGraphs, the first is for the main navigation which I'd suggested; and the second is for the BottomNavigationView navigation which you already use; that is because we transferred the BottomNavigationView from the activity layout to the main/home fragment layout; and it' recommended the BottomNavigationView should have a separate navGraph;
So, you now need two FragmentContainerView; the first is in the activity layout which reference the navGraph provided in this answer, and the second is in the home fragment layout that references your original navGraph of the BottomNavigationView.
Sample:
I think I understood your problem very well (if I am not wrong). Thing is that what behavior you are getting right now is the normal scenario. You are using the same host activity to host both Navigation Drawer and Bottom Navigation fragments thus when you tried to navigate to another fragment from the same host the presence of the host's direct child view Bottom NavBar is showing. I think you can solve this problem in a few different but pretty simple logical ways.
(Not recommended) Using a different Activity for the Settings page. Just like startActivity(this, <some intent>). But in this way, you will end up creating lots of individual activities.
(Recommended) Using a common Navigation Host activity for navigating to independent pages/fragments. Like: Settings, Contact Us, etc. You can add different fragments into the common_nav_graph and set some actions with or without arguments for navigation. Just add the common_nav_graph as a nested graph inside your current one and set a simple argumented action. The arguments will help you to navigate desired pages/fragments without showing the bottom navigation bar.
Simply hide Bottom navigation when navigating to some fragments from your drawer.
Related
This is more of a Java question (having some trouble understanding how should the inheritance be set). I'm trying to add a side menu to my application (which works ok). The class signature of my menu activity is:
public class MenuActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener
Once the user logged in, the first window he see is the main dashboard. The dashboard activity extends the menu activity in order to have the side menu:
public class DashboardActivity extends MenuActivity
For navigating to other activities, I implemented the onNavigationItemSelected method to select the intent to load (The method is located in the MenuActivity):
#Override
public boolean onOptionsItemSelected(#NonNull MenuItem item) {
if (toggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Intent intent = null;
switch (item.getItemId()) {
case R.id.lay_dashboard:
intent = new Intent(this, DashboardActivity.class);
//intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
break;
case R.id.lay_settings:
intent = new Intent(this, SettingsActivity.class);
//intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
break;
case R.id.lay_contacts:
startActivity(new Intent(this, ContactsActivity.class));
break;
case R.id.lay_about:
startActivity(new Intent(this, AboutActivity.class));
break;
case R.id.lay_logout:
mAuth.signOut();
intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
break;
}
if (intent != null) {
startActivity(intent);
}
drawerLayout.closeDrawer(GravityCompat.START);
return true;
}
As you can see I commented the intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); as part of debugging the issue. I want the navigation process not to start a new screen, rather move to it.
Also the OnCreate method in the MenuActivity looks like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
}
drawerLayout = findViewById(R.id.drawer_layout);
navigationView = findViewById(R.id.lay_nav_view);
toggle = new ActionBarDrawerToggle(this, drawerLayout,
R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawerLayout.addDrawerListener(toggle);
toggle.syncState();
navigationView.setNavigationItemSelectedListener(this);
mAuth = FirebaseAuth.getInstance();
logged_user = mAuth.getCurrentUser();
if (logged_user != null) {
// Get data from firebase in order to set the avatar
// and user name in the menu
}
}
protected void setLayoutView(int layout) {
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (inflater != null) {
View contentView = inflater.inflate(layout, null, false);
drawerLayout.addView(contentView, 0);
}
}
Each one of the activitise extends the MenuActivity and does the following in their onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setLayoutView(R.layout.activity_about); // My method which loads UI
// other code
}
So, as you can see, each one of the activities is extending the MenuActivity. As I understand, this means that everytime I navigate from one activity to another, the OnCreate of MenuActivity is being executed again. I fetch data from the Firebase in that method in order to set the username and the avatar in the top of the menu. So everytime I navigate from one activity to another, it will fetch again and again. How can I make the menu to load only once in a logged mode ? Also, thanks to all of you that have read this topic (I know it's long).
EDIT: To make it more clear, I'm adding the hierarchy of my code:
Maybe the solution for this issue is to make the MenuActivity a singleton class?
All I want to have a menu on each one of the activities and fetching the data only once.
Hello you could use NavigationDrawer with fragments. That will be the most optimal solution and easy, You could also use Navigation Component from Jetpack to change the destinations.
you could use something like this,the following layout uses a DrawerLayout with two child views: a NavHostFragment to contain the main content and a NavigationView for the contents of the navigation drawer.
<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- Layout to contain contents of main body of screen (drawer will slide over this) -->
<fragment
android:name="androidx.navigation.fragment.NavHostFragment"
android:id="#+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />
Next, connect the DrawerLayout to your navigation graph by passing it to AppBarConfiguration, as shown in the following example:
val appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
Next, in your main activity class, call setupWithNavController() from your main activity's onCreate() method, as shown below:
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)
...
val navController = findNavController(R.id.nav_host_fragment)
findViewById<NavigationView>(R.id.nav_view)
.setupWithNavController(navController)
}
Thats it , then you can use the navContoller to navigate to any fragment,
and rest of what you need is a navigation graph and actions set on destinations to navigate between fragments this way :
navController.navigate(R.id.actionSettingFragment_to_ContactsFrament)
Hope this is kinda helpful. I have a blog written on this so please check it if it is useful to you.
There's more than one way to resolve your issue. I'm going to provide different approaches towards a solution.
Repository
To reuse the data you fetch from Firebase, it needs to be persisted or cached within your application. In general you'd implement an repository abstraction around the Firebase. This repository could be a singleton and just cache the data in memory directly. You only have to make sure to cleanup the cache when necessary. The MenuActivity mustn't be a singlton.
Intent extras
Instead of storing the data in memory, you could provide it as Intent extras. Whenever the data is present, just skip the loading and reuse the data provided.
Fragments
Instead of a stack of activities, you could use fragments. So you'll have a single activity and just replace the fragments within it. With this approach you'll just define a single drawer layout you'll automatically use with every fragment. This approach enables you to use Navigation components as well.
I have an Activity that contains a BottomNavigationView, and this bottomnav helps the activity to display three fragments. These fragments load well, and I use an AsyncTask to do every heavy operation, while in the UI thread, I show a ProgressBar until everything loads.
There is a weird behaviour with my fragment: The first time I load the fragment it takes some time to actually display it, instead of displaying it instantly with a progressbar.
This thing only happens the first time, and only in this fragment.
The fragment code only contains this:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
new LoadData(getView(), getContext()).execute();
}
private class LoadData extends AsyncTask<Void, Void, Void> {
private View v;
private Context context;
public LoadData(View v, Context context) {
items = new ArrayList<>();
this.v = v;
this.context = context;
}
#Override
protected Void doInBackground(Void... voids) {
setItems(context); //Heavy operation
adapter = new DashAdapter(items, context);
return null;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
//shows progressbar
progress = v.findViewById(R.id.DFProgress);
progress.setVisibility(View.VISIBLE);
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
setPager();
//sets viewPager and hides progressbar
progress.setVisibility(View.GONE);
}
}
In the gif below, if you look at the bottomnavigationview at the bottom, you can see that it takes time to display the fragment. But after trying to load the fragment a second time, it loads as expected.
How could I make the fragment to load the right way?
I had the same problem. I have two options.
Use postdelay when you call LoadData or
First add all fragments with manually. You manage navigationItemSelected yourself.
Like this:
val firstFragment: Fragment = FirstFragment()
val secondFragment: Fragment = SecondFragment()
val thirdFragment: Fragment = ThirdFragment()
val navView: BottomNavigationView = findViewById(R.id.nav_view)
var active = firstFragment
fm.beginTransaction().add(R.id.nav_host_fragment, thirdFragment, "3").hide(thirdFragment).commit()
fm.beginTransaction().add(R.id.nav_host_fragment, secondFragment, "2").hide(secondFragment).commit()
fm.beginTransaction().add(R.id.nav_host_fragment, firstFragment, "1").commit()
navView.setOnNavigationItemReselectedListener { }
navView.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_first -> {
fm.beginTransaction().hide(active).show(firstFragment).commit()
active = firstFragment
}
R.id.navigation_second -> {
fm.beginTransaction().hide(active).show(secondFragment).commit()
active = secondFragment
}
R.id.navigation_third -> {
fm.beginTransaction().hide(active).show(thirdFragment).commit()
active = thirdFragment
}
}
true
}
And remove these lines in your nav_host_fragment:
app:defaultNavHost="true"
app:navGraph="#navigation/mobile_navigation"
you can use jetpack navigation for simple bottombar navigation
Simple Bottom Navigation with Jetpack Navigation:
Let’s start by including the Jetpack Navigation library in your apps by adding these lines in app’s build.gradle file:
def nav_version = "2.1.0"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
We start by creating a simple bottom navigation flow first. For that, you need to do first add NavHostFragment in your single activity layout file. Add this in the activity_main.xml file inside the FrameLayout tag.
<fragment
android:id="#+id/fragNavHost"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/bottom_nav_graph" />
You will see an error saying “Cannot resolve symbol #navigation/bottom_nav_graph .”
<?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/bottom_nav_graph.xml"
app:startDestination="#id/homeFragment2">
<fragment
android:id="#+id/homeFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.HomeFragment"
android:label="fragment_home"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/searchFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.SearchFragment"
android:label="fragment_search"
tools:layout="#layout/fragment_search" />
<fragment
android:id="#+id/notificationsFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.NotificationsFragment"
android:label="fragment_notifications"
tools:layout="#layout/fragment_notifications" />
<fragment
android:id="#+id/profileFragment2"
android:name="com.wajahatkarim3.bottomnavigationdemo.ProfileFragment"
android:label="fragment_profile"
tools:layout="#layout/fragment_profile" />
</navigation>
Its time for add some code in our activity class. Open MainActivity.kt file, and create a method setupViews() in it. Call this in onCreate() of the activity. Add these lines in the setupVeiws() method.
fun setupViews()
{
// Finding the Navigation Controller
var navController = findNavController(R.id.fragNavHost)
// Setting Navigation Controller with the BottomNavigationView
bottomNavView.setupWithNavController(navController)
// Setting Up ActionBar with Navigation Controller
// Pass the IDs of top-level destinations in AppBarConfiguration
var appBarConfiguration = AppBarConfiguration(
topLevelDestinationIds = setOf (
R.id.homeFragment,
R.id.searchFragment,
R.id.notificationsFragment,
R.id.profileFragment
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
}
When I tried #Kasım Özdemir 's answer, Each time the activity is launched, There was an initial ripple effect on the first item in bottom navigation view.(because I am using material bottom navigation view, which has a default ripple effect. And also UI was not visible when I tap for the first time, but I think that was because I was using different method than #Kasım Özdemir to change my active fragment when Item is clicked.
I didn't want to start my activity with first item, but with middle item in navigation view, which is "FragmentTwo" in below case. So the ripple effect was quite irrelevant.
So, I just attached the fragment instead of adding it and then hiding it, and now there is no ripple, Here is how code looks in Kotlin...
val firstFragment: Fragment = FragmentOne()
val middleFragment: Fragment = FragmentTwo()
val thirdFragment: Fragment = FragmentThree()
var fragment: Fragment? = null
var bnv: BottomNavigationView? = null
bnv = findViewById(R.id.bottom_navigation)
bnv!!.selectedItemId = R.id.middle_page
//fragments attached other than the fragment linked with middle item
supportFragmentManager.beginTransaction().attach(firstFragment).commit()
supportFragmentManager.beginTransaction().attach(thirdFragment).commit()
supportFragmentManager.beginTransaction().add(R.id.activity_main_container, middleFragment())
.commit()
bnv!!.setOnItemSelectedListener { item ->
when (item.itemId) {
R.id.first_page -> fragment = firstFragment
R.id.middle_page -> fragment = middleFragment
R.id.third_page -> fragment = thirdFragment
}
if (item.isChecked){
false
}
else {
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.fade_in, R.anim.fade_out).replace(
R.id.activity_main_container,
fragment!!
).commit()
true
}
}
So, using attach can also work fine instead of adding and then hiding it... for more information you can check this answer
(If you haven't already, Try using Navigation Component for bottom navigation, like this. It is more handy in some case, as it handles default backStack management by itself.)
I have a app with BottomNavigation (3 Items). The Item 1 load Fragment 1, Item 2 load fragment 2 and Item 3 load fragment 3. When one item is selected in the BottomNavigation, the view of this item keep blue and the text is more big. I have implemented onBackPressed, for it back the fragment history (BackStack). But, when i'm backing, the views in BottomNavigation are stoppe. So, if i'm in fragment 3, and i press back button and it back to fragment 2, the BottomNavigationView show that i'm in fragment 3, and if i press back button again, i go to fragment1, but the BottomNavigationView don't update, it keep in Fragment 3. The screenshorts will show what i'm talking about.
Sorry my bad english and my bad explanation.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TextView mTextMessage;
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment selectedFragment = null;
switch (item.getItemId()) {
case R.id.navigation_home:
selectedFragment = Fragment1.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.content, selectedFragment).addToBackStack(null).commit();
return true;
case R.id.navigation_dashboard:
selectedFragment = Frament2.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.content, selectedFragment).addToBackStack(null).commit();
return true;
case R.id.navigation_notifications:
selectedFragment = Fragment3.newInstance();
getSupportFragmentManager().beginTransaction().replace(R.id.content, selectedFragment).addToBackStack(null).commit();
return true;
}
return false;
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
}
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
From the Material Design guidelines for bottom navigation:
On Android, the Back button does not navigate between bottom navigation bar views.
And from the Developer Training for Back Navigation:
Note: You should not add transactions to the back stack when the transaction is for horizontal navigation (such as when switching tabs)
So you should not use the back stack when replacing fragments based on user interaction with your BottomNavigationView.
If you choose to ignore these guidelines, probably the answer is to look into FragmentManager.OnBackStackChangedListener and activate the appropriate navigation item when you pop the back stack.
The Problem
I have a navigation drawer with different fragments. There is a default toolbar every Fragment should use, except of one Fragment which needs a collapsing Toolbar.
My Question
How can I switch between the toolbars for the fragments ?
It seems you want to achieve something like this.
I have made an activity with common toolbar. when switching to the collapsing toolbar fragment I've made the toolbar transparent and fragment's toolbar takes over. The toolbar's color remains the same on switching to other fragments.
This allows you to manage complete collapsing toolbar's layout structure in xml and logic remains in Fragment.
Hope this will help. Refer the gif linked.
Gist for gif
The best solution that I found to easily collapse, lock it(keep it in collapsed mode) and unlock the collapsingToolbar.
private void collapseAppBar() {
// Collapse the AppBarLayout with animation
mAppBarLayout.setExpanded(false, true);
}
private void lockAppBar() {
/* Disable the nestedScrolling to disable expanding the
appBar with dragging the nestedScrollView below it */
ViewCompat.setNestedScrollingEnabled(nestedScrollView, false);
/* But still appBar is expandable with dragging the appBar itself
and below code disables that too
*/
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return false;
}
});
}
private void unLockAppBar() {
ViewCompat.setNestedScrollingEnabled(nestedScrollView, true);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
if (behavior != null) {
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(AppBarLayout appBarLayout) {
return true;
}
});
}
}
And I use these functions in this way:
Fragment fragment = null;
Class fragmentClass;
switch (menuItem.getItemId()) {
case R.id.fragment1:
unLockAppBar();
fragmentClass = first_Fragment.class;
break;
case R.id.fragment2:
collapseAppBar();
lockAppBar();
fragmentClass = second_Fragment.class;
break;
case R.id.fragment3:
collapseAppBar();
lockAppBar();
fragmentClass = third_Fragment.class;
break;
You can easily get the Toolbar from your Fragment and then modify or change some property of that Toolbar inside the Fragment.
To get the Toolbar from your Activity you might consider using this.
Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
Now you need to make the changes on the Toolbar in the onResume function and then undo the changes each time you return from the Fragment inside onStop function. Otherwise the changes made in the Fragment will be carried on to other fragments as well when switched to other Fragment from the navigation drawer.
But in your case, I would recommend each Fragment should have their Toolbar so that it doesn't conflict with each other and can be modified as you need. And yes, remove the Toolbar from your Activity.
So add the Toolbar in the layout of your Fragment like this.
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimaryDark"/>
Then find it in the Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment, container, false);
Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
// Modify your Toolbar here.
// ...
// For example.
// toolbar.setBackground(R.color.red);
// Create home button
AppCompatActivity activity = (AppCompatActivity) getActivity();
activity.setSupportActionBar(toolbar);
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
And Override the onOptionsItemSelected function.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case android.R.id.home:
getActivity().onBackPressed();
}
return super.onOptionsItemSelected(item);
}
I'm using Jetpack's Navigation components with single Activity and different Fragments in my app.
Some Fragments are accessible from bottom navigation (and have Toolbar from Activity). Some others are "special" Fragments and have their own Collapsible Toolbar.
To achieve this, I'm hiding Toolbar from Activity in "special" Fragments with this code in Activity:
// Handle toolbar changes in different Fragments
val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.my_special_fragment_with_collapsible_toolbar -> {
binding.toolbarMain.visibility = View.GONE
}
else -> {
binding.toolbarMain.visibility = View.VISIBLE
}
}
}
The recommend practice is to use toolbars in fragments instead of a common toolbar in activity. That way you can control the looks and behaviour of toolbar in fragment. Refer https://developer.android.com/guide/navigation/navigation-ui#support_app_bar_variations
I am trying to implement a navigation drawer in a BaseActivity, that shows different options depending on the activity that is currently showing. For that, I developed a BaseActivity, that implements the navigation drawer, and decides what to show, depending on the activity that is currently showing. The purpose of this, is to make all other activities that need to use the navigation drawer, expand the BaseActivity.
The following code, shows no errors, but the navigation drawer shows itself completely empty, and does not show when I click the 'home' button, neither the 'menu' button, a functionality that I implemented with the 'onKeyDown' method. It just shows, when I use the following gesture: move the finger from the left to the right in the left side of the screen.
When I do the same in each class I need, instead of using a BaseActivity, everything works perfeclty fine.
I have been trying this for days now and I still do not understand why, the content of the navigation drawer is still not showing. I would appreciate some help please. Thanks in advance.
Here, the core classes of the problem:
BaseActivity.java
public abstract class BaseActivity extends ActionBarActivity
{
public DrawerLayout drawerLayout = null;
public ActionBarDrawerToggle drawerToggle = null;
public Activity currentActivity = null;
public ArrayList<Item> navDrawerItems = new ArrayList<Item>();
public ItemListAdapter adapter = null;
public ListView drawerList = null;
int id = 0;
protected void onCreate(Bundle savedInstanceState, int resLayoutID)
{
super.onCreate(savedInstanceState);
setContentView(resLayoutID);
currentActivity = this;
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
drawerToggle = new ActionBarDrawerToggle(currentActivity,
drawerLayout,
R.drawable.ic_drawer,
R.string.open_menu,
R.string.close_menu)
{
public void onDrawerClosed(View view)
{
Log.e("", "Close drawer");
getSupportActionBar().setTitle(getTitle());
ActivityCompat.invalidateOptionsMenu(currentActivity);
}
public void onDrawerOpened(View drawerView)
{
Log.e("", "Open drawer");
getSupportActionBar().setTitle(getTitle());
ActivityCompat.invalidateOptionsMenu(currentActivity);
}
};
drawerLayout.setDrawerListener(drawerToggle);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
// Populate navigation drawer depending on the activity
// that is currently showing.
if (this.getClass().getSimpleName().equals("Z"))
setUpNavigationForZActivity();
}
private void setUpNavigationForZActivity()
{
Log.e("", "In setUpNavigationForZActivity");
// Prepare list items.
id = R.string.title_activity_A;
navDrawerItems.add(new NavigationDrawerItem(getString(id), Utils.activityIcon().get(id)));
id = R.string.title_activity_B;
navDrawerItems.add(new NavigationDrawerItem(getString(id), Utils.activityIcon().get(id)));
// Populate view.
drawerList = (ListView) findViewById(R.id.left_menu);
adapter = new ItemListAdapter(currentActivity, navDrawerItems;
drawerList.setAdapter(adapter);
drawerList.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView<?> parent, View view, int position,
long id)
{
Intent intent = null;
switch(position)
{
case 0:
intent = new Intent(currentActivity, A.class);
startActivity(intent)
break;
case 1:
intent = new Intent(currentActivity, B.class);
startActivity(intent)
break;
default:
}
}
});
}
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
if (drawerToggle.onOptionsItemSelected(item))
return true;
return super.onOptionsItemSelected(item);
}
#Override
protected void onPostCreate(Bundle savedInstanceState)
{
super.onPostCreate(savedInstanceState);
drawerToggle.syncState();
}
#Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
drawerToggle.onConfigurationChanged(newConfig);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent e)
{
Log.e("", "onKeyDown");
if (keyCode == KeyEvent.KEYCODE_MENU)
{
if(!drawerLayout.isDrawerOpen(Gravity.LEFT))
drawerLayout.openDrawer(Gravity.LEFT);
else
drawerLayout.closeDrawer(Gravity.LEFT);
}
return super.onKeyDown(keyCode, e);
}
}
Z.java
public class Z extends BaseActivity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState, R.layout.Z);
setContentView(R.layout.Z);
//Other things to do...
}
}
Z.xml
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- Main content view -->
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true" >
// Other layout and views configurations...
</ScrollView>
<!-- Navigation drawer -->
<ListView
android:id="#+id/left_menu"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#color/gray_7_5"
android:choiceMode="singleChoice" />
</android.support.v4.widget.DrawerLayout>
I do not exactly know why you would want to implement it that way but if you still want to do it this way i would suggest making your navdrawer use a listview with an adapter to draw items from an array and then have your base activity have a getData() call that you can override in your derived activities that will supply the data that the Adapter so that the listview will then draw the appropriate items in the navdrawer. You will then have to implement the onItemClick event for each listview per activity.