I'm new to Android development, so I do not understand many, perhaps elementary things, perhaps even the stated topic does not fully reflect my questions, I apologize in advance, I could not formulate more precisely. Please help me with some questions.
I have a BottomNavigation in my application with three menu items. Like on a picture:
bottom nav
Initially, I did it as in the standard Android Studio example:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private NavController navController;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
BottomNavigationView navView = findViewById(R.id.nav_view);
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_update, R.id.navigation_notifications)
.build();
navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
}
}
In this code, the menu items switch fragments, saving the state of each of them (I don’t know how exactly, it seems to be somewhere inside the navigation controller).
But now I needed the middle button to perform a different function, i.e. not to participate in switching fragments.
The only adequate way to do this is to use setOnItemSelectedListener.
Then my code will look like this:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private NavController navController;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
BottomNavigationView navView = findViewById(R.id.nav_view);
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_update, R.id.navigation_notifications)
.build();
navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
navView.setOnItemSelectedListener(
new NavigationBarView.OnItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
navController.navigate(R.id.navigation_home);
break;
case R.id.navigation_update:
// work for middle menu item
break;
case R.id.navigation_notifications:
navController.navigate(R.id.navigation_notifications, savedInstanceState);
break;
}
return true;
}
});
}
}
It even works, except that the fragments no longer save their state. That is, for example, in the first fragment I have a RecyclerView, if I scroll it down, switch to another fragment, and then return back, then the RecyclerView is in the default state (not scrolled), that is the state has not been saved.
It turns out that in the first version of the code I can’t do individual work with the middle menu item, and in the second version the states of the fragments are not saved.
Prompt correct way to deal with this problem. So that the middle menu item can be assigned a separate job, and the state of the fragments can be saved.
instead of using navView.setOnItemClickListener, you can use navController.addOnDestinationChangedListener, and in overrided onDestinationChanged method, you can check
if(destination.id == R.id.navigation_update) {
//your custom action here
}
I was able to solve the problem with standard tools using Navigation.
I just found a menu item and added a OnClickListener to it.
BottomNavigationItemView updateBtn = findViewById(R.id.navigation_update);
updateBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// todo
}
});
Thus, both switching fragments and clicking on the middle menu item work correctly.
Related
i have one big problem i am a beginner and i don't know how to implement horizontal and vertical slide between fragments, i have around 100 main and 100 additional fragments for each of that fragment.
Can someone please explain to me the simplest way.
https://i.stack.imgur.com/5WHhD.png
My code :
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration mAppBarConfiguration;
private ActivityMainBinding binding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.appBarMain.toolbar);
DrawerLayout drawer = binding.drawerLayout;
NavigationView navigationView = binding.navView;
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav01, R.id.nav02, R.id.nav03, R.id.nav04, R.id.nav05, R.id.nav06, R.id.nav07, R.id.nav08, R.id.nav09
, R.id.nav10, R.id.nav11, R.id.nav12, R.id.nav13, R.id.nav14, R.id.nav15, R.id.nav16, R.id.nav17, R.id.nav18, R.id.nav19, R.id.nav20, R.id.nav21
, R.id.nav22, R.id.nav23, R.id.nav24, R.id.nav25, R.id.nav26, R.id.nav27, R.id.nav28, R.id.nav29, R.id.nav30, R.id.nav31, R.id.nav32, R.id.nav33,
R.id.nav34, R.id.nav35, R.id.nav36, R.id.nav37, R.id.nav38, R.id.nav39, R.id.nav40, R.id.nav41, R.id.nav42, R.id.nav43, R.id.nav44, R.id.nav45,
R.id.nav46, R.id.nav47, R.id.nav48, R.id.nav49, R.id.nav50, R.id.nav51, R.id.nav52, R.id.nav53, R.id.nav54, R.id.nav55, R.id.nav56, R.id.nav57,
R.id.nav58, R.id.nav59, R.id.nav60, R.id.nav61, R.id.nav62, R.id.nav63, R.id.nav64, R.id.nav65, R.id.nav66, R.id.nav67, R.id.nav68, R.id.nav69,
R.id.nav70, R.id.nav71, R.id.nav72, R.id.nav73, R.id.nav74, R.id.nav75, R.id.nav76, R.id.nav77,R.id.nav78,R.id.nav79,R.id.nav80,R.id.nav81,R.id.nav82,
R.id.nav83,R.id.nav84, R.id.nav85, R.id.nav86,R.id.nav87,R.id.nav88,R.id.nav89,R.id.nav90,R.id.nav91,R.id.nav92,R.id.nav93,R.id.nav94,R.id.nav95,R.id.nav96,
R.id.nav97,R.id.nav98,R.id.nav99
)
.setOpenableLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
}
The application has a BottomNavigationView on the MainActivity which deals with the navigation between Fragments. One of the Fragments has a RecyclerView in it and the items of the RecyclerView have a button.
I am trying to make the RecyclerView Items' button navigate to another Fragment and pass a value along with it but I keep receiving a null object reference with passing the data.
I created an interface to get the click to the MainActivity for the Fragment switch and created a Bundle to communicate the received value to the required Fragment
// The interface
public interface OnItemClickListener {
void onItemClick();
The Adapter class
//Define interface
OnItemClickListener listener;
//Create constructor for interface
public LocationsAdapter(Activity activity) {
listener = (MainActivity)activity;
...
#Override
public void onBindViewHolder(#NonNull LocationsViewHolder holder, int position) {
//A click listener for the button
holder.showMarkerButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Create instance of MapFragment to pass data as Bundle
MapFragment mapFragment = new MapFragment();
LatLng latLng = new LatLng(markerObject.getLatitude(),markerObject.getLongitude());
//Create Bundle and add data
Bundle bundle = new Bundle();
bundle.putString("latitude",markerObject.getLatitude().toString());
bundle.putString("longitude",markerObject.getLongitude().toString());
listener.onItemClick(bundle);
}
});
}
Then in the MainActivity I implemented the interface to switch the View of the BottomNavigation.
public class MainActivity extends AppCompatActivity implements OnItemClickListener {
BottomNavigationView navView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
navView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home,R.id.navigation_map, R.id.navigation_locations, R.id.navigation_profile)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
}
#Override
public void onItemClick() {
navView.setSelectedItemId(R.id.navigation_map);
}
}
As a final step I called on the Bundle I made in the first step but that's where the null object reference comes up.
//MapFragment
private void setCam(){
Bundle bundle = getArguments();
if (getArguments() != null ){
String SLatitude = bundle.getString("latitude");
String SLongitude = bundle.getString("longitude");
Double latitude = Double.parseDouble(SLatitude);
Double longitude = Double.parseDouble(SLongitude);
LatLng latLng = new LatLng(latitude,longitude);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 20));
Toast.makeText(getActivity(), latLng.toString(), Toast.LENGTH_SHORT).show();
}
else{
Toast.makeText(getActivity(), "error present", Toast.LENGTH_SHORT).show();
}
}
Thanks in advance.
MapFragment mapFragment = new MapFragment();
LatLng latLng = new LatLng(markerObject.getLatitude(),markerObject.getLongitude());
//Create Bundle and add data
Bundle bundle = new Bundle();
bundle.putString("position",latLng.toString());
mapFragment.setArguments(bundle);
Here the mapFragment is just a created object that is not used in the navigation graph. i.e. it's a standalone object that is independent of the navigation, so it is not involved in the navigation as it's not the same as the current mapFragment fragment in the navGraph.
Solution:
In order to solve this, you need to pass the argument whenever you switch among fragments of the BottomNavigationView, and registering OnNavigationItemSelectedListener is a good place for that:
First allow the adapter to send the arguments back to the activity through the listener callback
So, modify the listener callback to accept a parameter
interface OnItemClickListener listener {
void onItemClick(Bundle args);
}
Apply that on adapter
#Override
public void onBindViewHolder(#NonNull LocationsViewHolder holder, int position) {
//A click listener for the button
holder.showMarkerButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
LatLng latLng = new LatLng(markerObject.getLatitude(),markerObject.getLongitude());
//Create Bundle and add data
Bundle bundle = new Bundle();
bundle.putString("position",latLng.toString());
//Interface to transport the click event to the MainActivity to switch Fragment in the BottomNavigationView
listener.onItemClick(bundle);
}
});
}
And finally, register OnNavigationItemSelectedListener to the navView and modify the callback to accept the Bundle in activity:
public class MainActivity extends AppCompatActivity implements OnItemClickListener {
BottomNavigationView navView;
private Bundle mapArgs;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
navView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home,R.id.navigation_map, R.id.navigation_locations, R.id.navigation_profile)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
navController.navigate(item.getItemId(), mapArgs);
return true;
}
});
}
#Override
public void onItemClick(Bundle args) {
navView.setSelectedItemId(R.id.navigation_map);
mapArgs = args;
}
}
When using the navigation controller, you pass in data with parameters. Go to your navigation component XML file and make the following changes;
1.Inside the navigation action, you need to add an argument.
<action
android:id="#+id/action_id"
app:destination="#id/destinationFragment">
<argument
android:name="paramterName"
app:argType="string" />
</action>
2.Add the same argument to the destination fragment
<fragment
android:id="#+id/coursesNavFragment"
android:name="com.example.example.DestinationFragment"
android:label="destination_fragment"
tools:layout="#layout/destination_fragment" >
<argument
android:name="parameterName"
app:argType="string" />
</fragment>
You can easily do this using the design view as well.
Now the function you call to navigate to the destination fragment expects you to pass it a parameter.
navigationController.navigate(actionSourceFragmentToDestinationFragment("text123"))
Then you can extract the bundle in the destination fragment.
val parameter = DestinationFragmentArgs.fromBundle(requireArguments()).parameterName
// parameter has the value of "text123"
Since the navigation component uses safeargs, you can pass any type of parameter that inherits from Parcellable.
How do I get the hamburger icon back for the navigation drawer? the AppBarConfiguration changes it into a back arrow, why is that?
This is my code below:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
init_v3();
BottomNavigationView navView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(R.id.home, R.id.nav_tasks)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController);
}
You should add all the fragments that you don't want the up/back button to show up in the appBarConfiguration
In your example, this up/back button won't show up in R.id.nav_tasks & R.id.home fragments as you already added them. >> if you have more fragments, then add them into the below separating between them with comma.
And to show up the Drawer burger icon, you need to call setOpenableLayout(drawerLayout)
DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.home,
R.id.nav_tasks)
.setOpenableLayout(drawerLayout)
.build();
I am working on an app with a side Navigation drawer. The drawer opens fine, however the text that supposedly can be "clickable" does not seem to respond. The animation shows that there is feedback to when the drawer is tapped (you can hear the sound) however nothing results of it. I have tried to place toast messages to see if the button registers an action, but when pressed, no toast appears.
The code goes as follows (I have implemented NavigationView.OnNavigationItemSelectedListener):
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_driver_home);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home, R.id.nav_history, R.id.nav_settings,
R.id.nav_help, R.id.nav_signout)
.setDrawerLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
And then I implemented the method:
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
switch (menuItem.getItemId()){
case R.id.nav_history:
Toast.makeText(this, "fsdfuxc", Toast.LENGTH_LONG).show();
break;
case R.id.nav_help:
break;
case R.id.nav_settings:
break;
case R.id.nav_signout:
signOut();
break;
}
DrawerLayout drawer = (DrawerLayout)findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
Thank you
The line
NavigationUI.setupWithNavController(navigationView, navController);
Calls setNavigationItemSelectedListener internally to connect destinations to menu items (i.e., when you click on the R.id.nav_settings MenuItem, it'll replace the Fragment in your NavHostFragment with the one with android:id="#+id/nav_settings" set). This listener overrides the OnNavigationItemSelectedListener view you've set, which is why your custom logic doesn't run.
If you want to combine both sets of functionality together, you need to call navigationView.setNavigationItemSelectedListener(this); after setupWithNavController and trigger the default behavior with NavigationUI.onNavDestinationSelected():
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_driver_home);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home, R.id.nav_history, R.id.nav_settings,
R.id.nav_help, R.id.nav_signout)
.setDrawerLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
// This line needs to be after setupWithNavController()
navigationView.setNavigationItemSelectedListener(this);
}
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
switch (menuItem.getItemId()){
case R.id.nav_history:
Toast.makeText(this, "fsdfuxc", Toast.LENGTH_LONG).show();
break;
case R.id.nav_signout:
signOut();
break;
default:
// Trigger the default action of replacing the current
// screen with the one matching the MenuItem's ID
NavigationUI.onNavDestinationSelected(menuItem, navController);
}
DrawerLayout drawer = (DrawerLayout)findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
Just a small sample based on the accepted answer but inside the fragment, without overriding the annotation and using Kotlin.
bottomNav.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.share -> {
shareViaWhatsApp()
}
else -> {
NavigationUI.onNavDestinationSelected(it, navController!!)
}
}
true
}
I am trying to use the new Navigation components from Android jetpack to create a navigation drawer. For some reason, the corresponding burger button does show up on the screen but doesn't react to clicks at all.
I've tried several tutorials online but to no avail. The last thing I tried was the offcial approach from https://developer.android.com/guide/navigation/navigation-ui.
MainActivity.java:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupNavigation();
}
private void setupNavigation() {
drawer = findViewById(R.id.drawer_layout);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
navController = Navigation.findNavController(this, R.id.nav_host_fragment);
AppBarConfiguration appBarConfiguration =
new AppBarConfiguration.Builder(navController.getGraph())
.setDrawerLayout(drawer)
.build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationView navView = findViewById(R.id.nav_view);
NavigationUI.setupWithNavController(navView, navController);
}
I don't understand why it isn't reacting to clicks at all.
As per the ActionBar section of that same page:
Next, override onSupportNavigateUp() to handle Up navigation:
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController, appBarConfiguration)
|| super.onSupportNavigateUp()
}
Note that the AppBarConfiguration you create will need to be a variable at the class level as well so it can be used in both setupNavigation() and onSupportNavigateUp().