I am working with navigation component and bottom navigation
val navController = indNavController(R.id.nav_host_fragment)
bottom_navigation.inflateMenu(R.menu.bottom_navigation_menu)
bottom_navigation.setupWithNavController(navController)
and I am facing the next issue:
When an item is selected in the bottom navigation, then a fragment is loaded. The problem comes when I press again the same item, then a new fragment will be loaded, which it does not make sense at all.
Example:
1- User selects menu item A, then FragmentA is loaded.
2- User selects again menu item A, then a new FragmentA will be loaded,
I was trying to use
bottom_navigation.setOnNavigationItemSelectedListener { }
But then the bottom navigation will not work with the navController.
So the question is: there is a way to handle this situation in order to load again a new fragment when the user is in that screen already?
Finally, I was able to fix this issue.
As I said before, this code:
bottom_navigation.setupWithNavController(navController)
is using
bottom_navigation.setOnNavigationItemSelectedListener { }
so every time I select / reselect an item, the navController will load a new fragment. I checked the javadoc for setOnNavigationItemSelectedListener() and it says:
Set a listener that will be notified when a bottom navigation item is selected. This listener * will also be notified when the
currently selected item is reselected, unless an {#link *
OnNavigationItemReselectedListener} has also been set.
So what I did is to add the next line to my code:
bottom_navigation.setOnNavigationItemReselectedListener { }
and that's all. The navController will load a fragment when an item is selected but not when an item is reselected in the bottom navigation.
I prefer to use the listener from navController:
navController.addOnDestinationChangedListener {
controller, destination, arguments ->
//destination.id for id fragment
}
So the listener is triggered when the destination changes - not by clicking bottom_navigation.
Because setOnNavigationItemSelectedListener is already used when setupWithNavController is declared.
Try this to ignore the user's click on the same selected item:
bottom_navigation.apply {
setOnNavigationItemSelectedListener {
if (it.itemId == bottom_navigation.selectedItemId) {
true
} else when (it.itemId) { ... }
when you use bottom_navigation.setOnNavigationItemSelectedListener { } before bottom_navigation.setupWithNavController(navController) the OnNavigationItemSelectedListener is overrided inside setupWithNavController function. So use
navController.addOnDestinationChangedListener {
controller, destination, arguments ->
//destination.id for id fragment
}
Related
When I'm entering MessageDetails fragment, leaving with back arrow and navigating to other fragment, getting back and trying to click on button to MessageDetails again I got crash and error:
"Fragment not associated with a fragment manager".
fun handleEvent(event: MessageListEvent) {
when (event) {
is NavToMessageDetails -> {
val action =
MessageListFragmentDirections.actionMessageListFragmentToMessageDetailsFragment(
event.id,
DateHelper.parseFullDate(event.date),
event.message,
event.title
)
findNavController().navigate(action)
}
}
This is the fragment function which is responsible for navigating to the details fragment. In app we have created BottomNavigation with app:menu values and this error occurs when navigating between those fragments.
Event (MessageListEvent) was logged right under function declaration and everything is alright with it. Also, when I'm trying to wrap findNavController() with lifecycleScope.launchWhenResumed {} it won't navigate or do anything with it.
I'm using BottomAppbar with floating action button in single activity.The Fab has different responsibility in different fragment.
HomeFragment -> adds new item
AddProblemFragment ->saves item(also icon changes)
DetailsFragment -> adds items to selected collections(also icon changes)
I have tried to use listener between activity and fragments. Icon and position changes perfectly but I couldn't handle calling methods that in fragments when clicked.
To solve clicking problem I'm getting fab reference from activity with
fab = requireActivity().findViewById(R.id.fab) in each fragment (in onViewCreated).
So, problem is after orientation changes gives
java.lang.NullPointerException: requireActivity().findViewById(R.id.fab) must not be null
Is there way to avoid getting this error after orientation changes? or Which lifecycle should choose to initialize fab?
Is it possible to initialize fab in BaseFragment and use it because almost each fragment have fab?
The Code Snippets:
//Each fragment same approach
private lateinit var fab:FloatingActionButton
override fun onViewCreated(view:View,savedInstanceState:Bundle?){
fab = requireActivity().findViewById(R.id.fab_btn_add)
fab.setImageResource(R.drawable.ic_related_icon)
fab.setOnClickListener {
//calls method
}
First you need to create an interface for you fab click listener for Example:
interface FabClickCallback {
void onFabClick()
}
and on your activity,
You Need create an instance from this Callback
private FabClickCallback fabCallback;
and you want create new method inside your Activity called for Example:
public void handleFabClickListener(FabClickCallback callback){
// TODO Here put callback
this.fabCallback = callback;
}
and you want trigger this callback when user click on Fab Button like This:
fab.setOnClickListener ( view -> {
if (this.fabCallback != null ) {
this.fabCallback.onFabClick();
}
});
finally on Each Fragment you want handle Fab Click you want call this function and pass your actual fab click code :
((YourActivity) requireActivity()).handleFabClickListener(this);
and your fragment must implements FabClickCallback interface
I have this code in my Activity:
binding.imageViewDot.setOnClickListener {
findNavController(this, R.id.navHostFragmentApp).navigate(R.id.syncFragment)
}
}
When clicking the button it navigates to this fragment. But each time the user click it it adds another one. How can I set it to only once. For example, if fragment is visible do not run this code. For example:
binding.imageViewDot.setOnClickListener {
if (//fragment not visible)
{
findNavController(this, R.id.navHostFragmentApp).navigate(R.id.syncFragment)
}
}
You can use currentDestination to get the visible Fragment and check it's id before navigating
val navController = findNavController(this, R.id.navHostFragmentApp)
if(navController.currentDestination?.getId() != R.id.syncFragment){
navController.navigate(R.id.syncFragment)
}
In my app, I want the process of creating a new record to be divided into several steps in which users are queried about different information. So I created an activity with NavHostFragment and want to use a navigation graph to switch between fragments using a next button in the toolbar of this activity.
Is it possible to configure the button to navigate between fragments based on a navigation graph? Is this a good approach? Should I rather use a new activity for each step? I am new to android development so I am not sure what is the best way to do this.
You can handle it with your navigation graph
Handle toolbar click events in your fragments:
https://stackoverflow.com/a/30077965/11982611
While handling each fragment's next button click event implement your navigation code
findNavController().navigate(Your action)
Handle all navigation process in your Activity's OnItemOpotionsSelected Listener
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val navHostFragment = supportFragmentManager.primaryNavigationFragment
val fragment = navHostFragment?.childFragmentManager?.fragments?.get(0)
when(item.itemId)
{
android.R.id.home->super.onBackPressed()
R.id.next-> {
if(fragment is Fragment1)
{
fragment.findNavController().navigate(Fragment1toFragmen2 action)}
if(fragment is Fragment2)
{
fragment.findNavController().navigate(Fragment2toFragmen3 action)}
}
return true
}
I'm studying menu and toolbar/actionbar. I'm trying the way: one activity container and many fragments. I created and setup a toolbar as actionbar for MainActivity (onCreate):
val myToolbar = this.findViewById<Toolbar>(R.id.myToolbar)
setSupportActionBar(myToolbar)
Then, I add itens by normal way with onCreateOptionMenu and handle click with onOptionsItemSelected
When a call Fragment 1, I change action bar and add back button like this (onCreate):
val actBar = (activity as AppCompatActivity).supportActionBar
actBar?.setDisplayHomeAsUpEnabled(true)
actBar?.setDisplayShowHomeEnabled(true)
actBar?.setDisplayUseLogoEnabled(false)
actBar?.title = "Fragment 1 toolbar"
actBar?.subtitle = ""
setHasOptionsMenu(true)
Then from Fragment 1, the Fragment 2 is called and setup as same way:
To handle back button click in fragments, in onOptionsItemSelected:
return if (item.itemId == android.R.id.home) {
activity?.onBackPressed()
true
} else return when (item?.itemId){
...
}
else -> super.onOptionsItemSelected(item)
}
And override onBackPressedin MainActivity:
override fun onBackPressed() {
if(supportFragmentManager.backStackEntryCount > 0){
supportFragmentManager.popBackStackImmediate()
}else{
super.onBackPressed()
}
}
The problem is: if I click on back button, it's backing as expected but the last object of action bar is showed. In MainActivity, only action itens are showed as expected:
How I can sync the bar according fragment and activity?
Note:
I'm using Kotlin, but Java solution are welcome (to convert to kotlin
later)
The fragments are added to back stack
I found a solution. I leave here for whoever interests:
I applied OnBackStackChangedListener that watch changes on back stack. Then, You can make any changes on UI.
supportFragmentManager.addOnBackStackChangedListener {
//UI changes
}
Inside, I check if has some fragment current using the fragment container:
supportFragmentManager.addOnBackStackChangedListener {
val currentFragment = supportFragmentManager.findFragmentById(R.id.you_fragment_container)
if (currentFragment == null){
//rebuild action bar here or make any another changes
}
}
In my case, I compare null that mean container has no fragment. So, if null, the root activity is on screen.
This can be used to make changes for any fragment you want to.
That's it.