I would like to attach a callback to when the Android Navigation Controller navigates up from a specific fragment (findNavController().navigateUp()). How can I achieve this functionality?
I've already heard about requireActivity().onBackPressedDispatcher.addCallback(this). This only listens to the system's back button not the back arrow on the toolbar. I'd like to listen to the event where the user presses the back arrow on the toolbar in the top-left corner.
I have managed to find a solution. Here's what the onCreate() of my MainActivity looks like:
val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration = AppBarConfiguration(navController.graph)
my_toolbar.setupWithNavController(navController, appBarConfiguration)
// The code below solved it for me. It overrides the toolbar back and then calls the OS back which is overridden in my fragment.
my_toolbar.setNavigationOnClickListener {
onBackPressed()
}
And in my fragment, I do this in onActivityCreated()
requireActivity().onBackPressedDispatcher.addCallback(this) {
// override custom back
}
Now I can override both toolbar back button and OS back button in a single handler.
In your MainActivity that contains the NavHostFragment you can add onDestinationChanged to the NavController listener
navController = findNavController(MainActivity.this, R.id.navHostFragment);
navController.addOnDestinationChangedListener(this);
#Override
public void onDestinationChanged(#NonNull NavController controller, #NonNull NavDestination destination, #Nullable Bundle arguments) {
int destinationId = destination.getId();
if (destinationId == R.id.YOUR_DESIRED_FRAGMENT_ID) // your own behavior...
}
you can then add any behavior you desire.
I hope this help.
Related
I am trying to implement a way for the menuitem button to navigate to a different fragment based on the current fragment.
For example, if I am in the fragment_home, I can access the dog_fragment and from the dog_fragment I can access another fragment like cat_fragmentand vice versa.
So far I am only able to navigate from fragment_home to dog_fragment, however when I try to access any other fragments from the dog_fragment my app crash. If I try to access another fragment from any other fragment accept from the home_fragment my app crashes
//This is in the Mainactivity
fun onComposeAction(item: MenuItem) {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
val navController = navHostFragment.navController
when (item.getItemId()) {
R.id.Hamster -> {
navController.navigate(R.id.action_home2_to_hamster)
}
R.id.Dog -> {
navController.navigate(R.id.action_home2_to_dog)
}
R.id.Cat ->{
navController.navigate(R.id.action_home2_to_cat)
}
}
}
Simply tie the menuitems to the destination fragments you have.
I am using a navigation graph in my app. I have a bottom navigation bar. I am staring my navigation components with the following in my Main Activity:
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(setOf(
R.id.navigation_artist_list, R.id.navigation_dashboard, R.id.navigation_notifications))
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
This starts my navigation components, which inflate the fragement setup in R.id.navigation_artist_list.
From an observer in that fragment I am navigating to another fragment with:
viewModel.selectedAlbum.observe(this, Observer { artist ->
val action = HomeFragmentDirections.actionNavigationHomeToAlbumFragment(artist)
root.findNavController().navigate(action)
})
However, none of the mechanisms available for navigation work properly. The back button just refreshes the fragment and populates the list in the fragment again. The back button on the action bar is completely ignored.
Do I need to do something else to have the proper back button behavior, to go back to the previous fragment?
In yout MainActivity, override the onSupportNavigateUp() method to call navigateUp() in the navigation controller
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.nav_host_fragment)
return navController.navigateUp()
}
I need to navigate a stack of fragments and I am navigating back using the toolbar back button. Can I override the back button pressed to set a custom animation, for example slide out?
Here is the code for the toolbar.
private fun setupToolbar() {
val appBarConfiguration = AppBarConfiguration(navController.graph, drawer_layout)
val toolbar = toolbar as Toolbar
setSupportActionBar(toolbar)
toolbar.setupWithNavController(navController, appBarConfiguration)
val ab: ActionBar? = supportActionBar
ab?.setDisplayShowTitleEnabled(false) // disable the default title element here (for centered title)
setupSearchQueryListener()
}
In your setup code, one more thing is needed:
toolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
In your fragments, you could do this:
protected lateinit var backPressedCallback: OnBackPressedCallback
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
backPressedCallback = requireActivity().onBackPressedDispatcher.addCallback(this) {
// your code
}
}
The above will enable you to intercept back navigation uniformly and execute your code in question (you can even block/unblock it by playing with backPressedCallback.isEnabled flag). The above was tested. Speaking of setting navigation animation, I was only playing with xml defined action based animations:
<action
android:id="#+id/toYourDest"
app:destination="#+id/yourDest"
app:enterAnim="#anim/your_slide_in_right"
app:exitAnim="#anim/your_slide_out_left"
app:popEnterAnim="#anim/your_slide_in_left"
app:popExitAnim="#anim/your_slide_out_right" />
I need a second activity with a nav graph and have a return button in toolbar to the first activity that also contains a nav graph
In my second activity I have onSupportNavigateUp and setupActionBarWithNavController when entering the fragments if the arrow back button appears but in the activity no.
Try adding setHomeButtonEnabled and setDisplayHomeAsUpEnabled in both the activity and the fragment and if the button appears back, but when I enter some fragment in front and return to the fragment startDestination disappears the button back
I just need to keep the button back in the activity and solve my problem
You can do it by specifying a setFallbackOnNavigateUpListener:
private fun setupToolbar() {
val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration =
AppBarConfiguration.Builder()
.setFallbackOnNavigateUpListener { onNavigateUp() }
.build()
dataBinding.toolbar.apply {
setupWithNavController(navController, appBarConfiguration)
}
}
And then do whatever you want in the Activity's:
override fun onNavigateUp(): Boolean {
finish()
return true
}
You can't, activity has their own toolbars and in your case they have two different NavControllers. So your second activity manage NavUp Button for his fragment and when start Destination fragment comes NavUpButton(Backbutton) disappear because it has no destination left behind. And if you programmatically show NavUp Button on start destination of that (2nd activity) and manage onClick and start first activity that always goes to Start destination of first activity's fragment because it has it's own Nav Controller.
Problem is that Navigation UI not works like that. The better approach is use only one activity with multiple fragments. And use any other approach to solve your problem within the same nav controller.
Add setHomeButtonEnabled func. to your returning action. If you are returning with button add it to onClick or with backPress, override backPress.
With this solve : You will set your button enable, when you try to return your startDestination.
I created an interface to show/hide up button from the nav host activity. Here is how the activity implements the interface methods to show/hide up button:
override fun showUpButton() {
val navController = this.findNavController(R.id.nav_host)
val listener = AppBarConfiguration.OnNavigateUpListener { navController.navigateUp() }
val abc = AppBarConfiguration.Builder().setFallbackOnNavigateUpListener(listener).build()
NavigationUI.setupActionBarWithNavController(this, navController, abc)
}
override fun hideUpButton() {
val navController = this.findNavController(R.id.nav_host)
NavigationUI.setupActionBarWithNavController(this, navController)
}
Here the method in the activity when up button pressed:
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.nav_host)
if(!navController.navigateUp()){ // When in start destination
onBackPressed()
}
return navController.navigateUp()
}
In a fragment can listen whenever back button (NOT up button) pressed:
private fun setupBackPress() {
requireActivity()
.onBackPressedDispatcher
.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
}
})
}
I have no idea how to, using the new navigation architecture component, navigate from my main screen (with a FloatingActionButton attatched to a BottomAppBar) to another screen without the app bar.
When I click the fab I want my next screen (fragment?) to slide in from the right. The problem is where do I put my BottomAppBar? If I put it in my MainActivity then I have the issue of the FloatingActionButton not having a NavController set. I also cannot put my BottomAppBar in my Fragment. I am at a loss.
Ran into this issue today and I found out that there is a simple and elegant solution for it.
val navController = findNavController(R.id.navHostFragment)
fabAdd.setOnClickListener {
navController.navigate(R.id.yourFragment)
}
This takes care of the navigation. Then you must control the visibility of your BottomAppBar inside your Activity.
You could have your BottomAppBar in MainActivity and access your FloatingActionButton in your fragment as follows
activity?.fab?.setOnClickListener {
/*...*/
findNavController().navigate(R.id.action_firstFragment_to_secondFragment, mDataBundle)
}
You could hide the BottomAppBar from another activity as follows
(activity as AppCompatActivity).supportActionBar?.hide()
Make sure you .show() the BottomAppBar while returning to previous fragment
Put it in MainActivity and setOnClickListener in onStart() of the activity and it will work fine.
override fun onStart() {
super.onStart()
floatingActionButton.setOnClickListener {
it.findNavController().navigate(R.id.yourFragment)
}
}
Note:This solution is like and hack and better is to follow Activity LifeCycle and setUp OnClickListener when the activity is ready to interact.
Similar question [SOLVED]
if you wanted to navigate to certain fragment (not the star one) in the beginning for some reason, and also you have to graphs for one activity, here is what I suggest:
this method will start activity
companion object {
const val REQUEST_OR_CONFIRM = "request_or_confirm"
const val IS_JUST_VIEW = "IS_JUST_VIEW"
const val MODEL = "model"
fun open(activity: Activity, isRequestOrConfirm: Boolean, isJustView: Boolean = false, model: DataModel? = null) {
val intent = Intent(activity, HostActivity::class.java)
intent.putExtra(REQUEST_OR_CONFIRM, isRequestOrConfirm)
intent.putExtra(IS_JUST_VIEW, isJustView)
intent.putExtra(MODEL, model)
activity.startActivity(intent)
}
}
and then in, onCreate method of Host Activity, first decide which graph to use and then pass the intent extras bundle so the start fragment can decide what to do:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_purchase_nav)
if (intent.getBooleanExtra(REQUEST_OR_CONFIRM, true)) {
findNavController(R.id.nav_host_fragment).setGraph(R.navigation.nav_first_scenario, intent.extras)
} else {
findNavController(R.id.nav_host_fragment).setGraph(R.navigation.nav_second_scenario, intent.extras)
}
}
and here's how you can decide what to do in start fragment:
if (arguments != null && arguments!!.getBoolean(HostActivity.IS_JUST_VIEW)){
navigateToYourDestinationFrag(arguments!!.getParcelable<DataModel>(HostActivity.MODEL))
}
and then navigate like you would do normally:
private fun navigateToYourDestinationFrag(model: DataModel) {
val action = StartFragmentDirections.actionStartFragmentToOtherFragment(model)
findNavController().navigate(action)
}
here's how your graph might look in case you wanted to jump to the third fragment in the beginning
PS: make sure you will handle back button on the third fragment, here's a solution
UPDATE:
as EpicPandaForce mentioned, you can also start activities using Navigation Components:
to do that, first add the Activity to your existing graph, either by the + icon (which didn't work for me) or by manually adding in the xml:
<activity
android:id="#+id/secondActivity"
tools:layout="#layout/activity_second"
android:name="com.amin.SecondActivity" >
</activity>
you can also add arguments and use them just like you would in a fragment, with navArgs()
<activity
android:id="#+id/secondActivity"
tools:layout="#layout/activity_second"
android:name="com.amin.SecondActivity" >
<argument
android:name="testArgument"
app:argType="string"
android:defaultValue="helloWorld" />
</activity>
in koltin,here's how you would use the argument, First declare args with the type of generated class named after you activity, in this case SecondActivityArgs in top of your activity class:
val args: SecondActivityArgsby by navArgs()
and then you can use it like this:
print(args.testArgument)
This doesn't destroy BottomAppBar. Add this to MainActivity only and don't do anything else
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
fabAdd.setOnClickListener {
findNavController(navHostFragment).navigate(R.id.fab)
}