Handle Toolbar back button with Navigation component - android

I'm following single activity approach. I have navigation toolbar, whenever i go to other screens (fragments) instead of hamburger icon i will have back arrow.
What i want to achieve is, pop my current fragment using action on pressing toolbar back arrow.
I've tried
requireActivity().getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
NavHostFragment.findNavController(EventDetailsFragment.this)
.navigate(R.id.action_nav_event_details_to_nav_home);
}
});
But not getting the call over there, i checked by running app in debug mode.

in Activity oncreate:
navController = findNavController(R.id.my_nav_host)
//my_nav_host defined in activity xml file as id of fragment or FragmentContainerView
val appBarConfiguration = AppBarConfiguration(navController.graph)
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration)
and:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return true
}
then in your fragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callback: OnBackPressedCallback =
object : OnBackPressedCallback(true /* enabled by default */) {
override fun handleOnBackPressed() {
//do what you want here
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}

Add this code in parent activity
Add in onCreate method
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Add this method also in parent activity
#Override
public boolean onSupportNavigateUp() {
return super.onSupportNavigateUp();
}

If you are using custom toolbar in your xml, you may consider using below approach
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
setSupportActionBar(activity_chat_toolbar)
activity_chat_toolbar.setNavigationOnClickListener {
onBackPressed() <-- custom toolbar's back press
}
val navHostFrag =
supportFragmentManager.findFragmentById(R.id.chat_fragment_container_view) as NavHostFragment
navController = navHostFrag.navController
navController.setGraph(R.navigation.chat_nav_graph, intent.extras)
setupActionBarWithNavController(navController)
}
// Function to check startDestination to finish() the parent activity
override fun onBackPressed() {
if(navController.graph.startDestination == navController.currentDestination?.id) {
finish()
} else {
super.onBackPressed()
}
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}

Toolbar toolbar = findviewbyid(R.id.toolbar);
toolbar.setnavigationonclicklistener(new view.onclicklistener(){
code here
});
setsupportactionbar(toolbar);

Related

Android Kotlin Click Event for Back Button in Action Bar

I try to get action after pressing back button in top toolbar
class TagsFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activity as AppCompatActivity?)?.supportActionBar?.title = "$selectedItemText Tags"
(activity as AppCompatActivity?)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
Log.d(InTorry.TAG, "TagsFragment: back BTN Pressed")
}
}
}
Unfortunately, it doest log anything
I found I should add OnBackPressedCallback but it doesnt work as well :
class TagsFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val selectedItemText = arguments?.getString("selectedItemText")//get arguments
(activity as AppCompatActivity?)?.supportActionBar?.title = "$selectedItemText Tags"
(activity as AppCompatActivity?)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
(activity as AppCompatActivity?)?.onBackPressedDispatcher?.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.d(InTorry.TAG, "Fragment back pressed invoked")
// Do custom work here
// if you want onBackPressed() to be called as normal afterwards
if (isEnabled) {
isEnabled = false
requireActivity().onBackPressed()
}
}
}
)
}
Kind regards
Jack
put this in onCreate method
getActionBar().setDisplayHomeAsUpEnabled(true);
//and then override this method and find id.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
I know sometimes it is painful when android just deprecates on of their on Event functions ,Anyways this is your code :
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getActivity().onBackPressed();
}
});
and you can just remove the onClickListener Interface and shortcut it if you desire like this :
toolbar.setNavigationOnClickListener {
requireActivity().onBackPressedDispatcher.onBackPressed()
}
should be working fine
Thank you all
The code I use now:
For device back button, in Activity onCreate()
class HomeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
setSupportActionBar(findViewById(R.id.toolbar))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.d(InTorry.TAG,"device back btn click")
//finish()
}
})
Toolbar Back Button arrow - Now I know that it works like one big menu
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {// this is my back button - simply home
Log.d(InTorry.TAG,"Toolbar Back BTN Click")
...
}
R.id.top_menu_settings -> {
Log.d(InTorry.TAG,"top_menu_settings click")
true
}
R.id.top_menu_logout -> {
Log.d(InTorry.TAG,"top_menu_logout click")
Firebase.auth.signOut()
gotoLogin()
true
}
else -> super.onOptionsItemSelected(item)
}
}

How to go to the previous fragment by back button in action bar? (Kotlin)

I have two fragments and I want to create interaction between them using backward button in action bar. Ideally, I would like the state of previous fragment was saved.
I could find information for only activities.
For fragments I found this
private fun setupBackButton() {
if (activity is AppCompatActivity) {
(activity as AppCompatActivity?)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
}
But it only displays back button, nothing happens by clicking.
EDIT
In the first fragment I call the second like:
val fragment = UserContentFragment()
fragment.setUser(item.user)
if (fragmentManager != null) {
fragmentManager!!
.beginTransaction()
.replace(R.id.main_layout, fragment)
.addToBackStack(null)
.commit()
}
This is my UserContentFragment second fragment:
class UserContentFragment : Fragment() {
private lateinit var user: SearchUser
fun setUser(user: SearchUser) {
this.user = user
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_user_content, container, false)
val userImage = root.findViewById(R.id.user_img) as ImageView
if (context != null) {
Glide.with(context!!)
.load(user.profile_pic_url)
.circleCrop()
.into(userImage)
}
val userName: TextView = root.findViewById(R.id.user_name)
userName.text = user.full_name
val toolbar: Toolbar = root.findViewById(R.id.toolbar)
toolbar.setNavigationOnClickListener { requireActivity().onBackPressed() }
setupBackButton()
return root
}
private fun setupBackButton() {
if (activity is AppCompatActivity) {
(activity as AppCompatActivity?)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
}
}
And this its .xml file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorBlack">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/user_title"
android:layout_width="match_parent"
android:layout_height="100dp">
<ImageView
android:id="#+id/user_img"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<TextView
android:id="#+id/user_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="16dp"
android:textColor="#color/colorWhite"
android:textSize="22sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/user_img"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
In order to have a response when you hit the Home/up Button, here are a couple of options to solve this:
First Option
In the fragments that you show up the Home/UP button, override onOptionsItemSelected() method and call the activity's onBackPressed() for home button id
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle presses on the action bar menu items
when (item.itemId) {
android.R.id.home -> {
activity?.onBackPressed()
return true
}
}
return super.onOptionsItemSelected(item)
}
Side note:
Instead of showing the Home/Up button on the ActionBar using below, and you've to set the boolean value for each fragment that you need to show up the home button using the method below:
private fun setupBackButton() {
if (activity is AppCompatActivity) {
(activity as AppCompatActivity?)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
}
You can instead setup the ActionBar with AppBarConfiguration in onCreate() method of the activity as below:
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
val host: NavHostFragment = supportFragmentManager
.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment? ?: return
val navController = host.navController
appBarConfiguration = AppBarConfiguration(
setOf(R.id.second_fragment, R.id.third_fragment)) // IDs of fragments you want without the ActionBar home/up button
setupActionBarWithNavController(navController, appBarConfiguration)
}
By doing this, the up button will show up in all fragments, but R.id.second_fragment, R.id.third_fragment and you no longer need to set the setDisplayHomeAsUpEnabled() to each individual fragment to show up the home/up button. But still you need to override onOptionsItemSelected as mentioned above.
Second Option
Which is neater than the first option. First, you've to implement the above side node to allow the NavController auto controls/configure the ActionBar.
So, the past side note is a mandatory part of this option.
Then override onSupportNavigateUp() in the activity which allows NavigationUI to support proper ActionBar up navigation.
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
}
And then override onOptionsItemSelected() in activity to make Have Navigation UI Handle the OptionsMenu/ActionBar item selection
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
|| super.onOptionsItemSelected(item)
}
I would say that option 2 is neater than 1, because you write all the code in one place (activity) without touching fragments, and also it automatically configure all the fragments, no need to manually setDisplayHomeAsUpEnabled() or activity.onBackPressed() for individual fragments that you want the Home/Up button to show up.
In Kotlin Fragment with Navigation.
First, you add setHasOptionsMenu(true) in onCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
Then override the onOptionsItemSelected and when R.id.home you can control BACK Button
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.getItemId()) {
android.R.id.home ->
findNavController().navigate(R.id.action_FragmentTwo_to_FragmentOne)
}
return true
}
You need to attach a click listener to the toolbar like :
toolbar.setNavigationOnClickListener { requireActivity().onBackPressed() }
class StartActivity : FragmentActivity() {
/**
* The pager widget, which handles animation and allows swiping horizontally to access previous
* and next wizard steps.
*/
private lateinit var mPager: ViewPager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.start_activity)
val loginButton = findViewById<Button>(R.id.login_button)
loginButton.setOnClickListener {
this.didTapLoginButton()
}
}
private fun didTapLoginButton() {
val i = Intent(this, LoginActivity::class.java)
startActivity(i)
}
}
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.login_activity)
//actionbar
val actionbar = supportActionBar
//set actionbar title
actionbar!!.title = "New Activity"
//set back button
actionbar.setDisplayHomeAsUpEnabled(true)
actionbar.setDisplayHomeAsUpEnabled(true)
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
// it is important function, you need to write this function in which class/activity you want to show back arrow
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return super.onOptionsItemSelected(item)
}
}
Add this in the NavigationClickListener.
FragmentManager fm = getFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
Log.i("MainActivity", "popping backstack");
fm.popBackStack();
} else {
Log.i("MainActivity", "nothing on backstack, calling super");
super.onBackPressed();
}
Zain's Second Option is very good, but I'll show a different way for the part made in onCreate, with usage of DataBinding:
ActivityMainBinding.inflate(layoutInflater).run {
setContentView(root)
setSupportActionBar(toolbar)
(supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment)
as NavHostFragment).navController.let { navController ->
val appBarConfiguration = AppBarConfiguration(navController.graph)
toolbar.setupWithNavController(navController, appBarConfiguration)
}
}
It's a late answer but I hope it will help someone.
I did it in Kotlin and the way I managed to make it work is:
In the MainActivity I overrode the onSupportNavigateUp:
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_activity_main)
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}
And declared the AppBarConfiguration outside the onCreate()

back press with bottomNavigationView

In my app I use bottomNavigationView with jetpack navigation
Look at the picture
I have 2 pages
When I click to second page, then click back press, I return to first fragment, but I want to close app
some code
private fun initView() {
setSupportActionBar(toolbar)
bottomNavigationView = findViewById(R.id.bottom_navigation)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
navController?.apply {
appBarConfiguration = AppBarConfiguration(setOf(
R.id.action_home,
R.id.action_favorite
))
appBarConfiguration?.let {
setupActionBarWithNavController(this, it)
}
bottomNavigationView?.let {
NavigationUI.setupWithNavController(it, this)
}
}
}
One way of doing it is if you want custom behaviour from the back button, you will need to override onBackPressed() in your activity
override fun onBackPressed() {
if (R.id.action_favorite == navController?.currentDestination?.id) {
finish()
} else {
super.onBackPressed()
}
}

Navigation Component, Control when to show hamburger or back icon

I have the following Activity
class MainActivity : AppCompatActivity() {
private lateinit var drawerLayout: androidx.drawerlayout.widget.DrawerLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
drawerLayout = drawer_layout
val navController = Navigation.findNavController(this, R.id.fragment_main_navHost)
setSupportActionBar(toolbar)
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
navView_main.setupWithNavController(navController)
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(drawerLayout,
Navigation.findNavController(this, R.id.fragment_main_navHost))
}
override fun onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
which as you can see is associated with navigation graph, and I am using a navigation drawer. When I am navigating through the items in the drawer I want to keep the hamburger icon, and only change it to up/back button when I click on an item within the fragment or popup for example and ensure that the behavior of the system reflects what the user expects based on the icon displayed. Is that possible
To control when the AppBar navigation up/back show the following need to be done
1- create AppBarConfiguration and pass to it the top level destination and drawerLayout
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.dest_one,
R.id.dest_two
),
drawerLayout
)
2- Tell the AppBar about the configration and navigation. this will help to show a title and show up arrow or drawer menu icon
setupActionBarWithNavController(navController, appBarConfig)
3- Finally override the onOptionsItemSelected and onSupportNavigateUp and the Navigation Component extension to inform the AppBar how to behave
override fun onOptionsItemSelected(item: MenuItem)= item.onNavDestinationSelected(findNavController(R.id.my_nav_host_fragment))
|| super.onOptionsItemSelected(item)
override fun onSupportNavigateUp() = findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
Reference Google Code Lab Navigation Navigation Codelab
Follow this steps
1. Bind your NavigationView with NavigationUI
NavigationUI.setupWithNavController(nav_view, hostFragment.navController)
2. Bind ActionBar With NavController
NavigationUI.setupActionBarWithNavController(this#NavActivity, hostFragment.navController)
3. Bind ActionBar and DrawerLayout With NavController
NavigationUI.setupActionBarWithNavController(this#NavActivity, hostFragment.navController, drawer_layout)
4. override onSupportNavigateUp() in your activity
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(drawer_layout, hostFragment.navController)
|| super.onSupportNavigateUp()
}
Sample:
class NavActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
lateinit var hostFragment: NavHostFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_nav)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
}
val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
drawer_layout.addDrawerListener(toggle)
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
hostFragment = supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment
NavigationUI.setupWithNavController(nav_view, hostFragment.navController)
NavigationUI.setupActionBarWithNavController(this#NavActivity, hostFragment.navController)
NavigationUI.setupActionBarWithNavController(this#NavActivity, hostFragment.navController, drawer_layout)
}
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(drawer_layout, hostFragment.navController) || super.onSupportNavigateUp()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.nav, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
when (item.itemId) {
R.id.action_settings -> return true
else -> return super.onOptionsItemSelected(item)
}
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here.
drawer_layout.closeDrawer(GravityCompat.START)
return true
}
}
Output
Home Fragment:
Fragment Two:
Fragment Tree:
So, I think that you can use the NavController.OnNavigatedListener to listen wich fragment will be shown, and then update de toolbar icon.
val navController = Navigation.findNavController(this, R.id.fragment_main_navHost)
navController.addOnNavigatedListener(contoller, destination -> {
if(destination.id == R.id.fragmentTwo){
// change the toolbar icon here
}
})
Sorry, I have no computer here, so I write this code without any IDE, this can have error. But take the idea.
Hope this help you.

Handling back button in Android Navigation Component

I'd like to know how properly handle system back button action using Navigation Controller. In my app I have two fragments (for ex. fragment1 and fragment2) and I have an action in fragment1 with destination to fragment2. Everything works well except one thing - when user presses system back button in fragment2 I want to show a dialog (using DialogFragment for example) to confirm exit. What is the best way to implement this behavior? If I use app:defaultNavHost="true" in my host fragment then it automatically goes back ignoring my rules. And, additionally, what is this component for?
Should I use "pop to" may be?
Newest Update - April 25th, 2019
New release androidx.activity ver. 1.0.0-alpha07 brings some changes
More explanations in android official guide: Provide custom back navigation
Example:
public class MyFragment extends Fragment {
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// This callback will only be called when MyFragment is at least Started.
OnBackPressedCallback callback = new OnBackPressedCallback(true /* enabled by default */) {
#Override
public void handleOnBackPressed() {
// Handle the back button event
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
// The callback can be enabled or disabled here or in handleOnBackPressed()
}
...
}
Old Updates
UPD: April 3rd, 2019
Now its simplified. More info here
Example:
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), this);
#Override
public boolean handleOnBackPressed() {
//Do your job here
//use next line if you just need navigate up
//NavHostFragment.findNavController(this).navigateUp();
//Log.e(getClass().getSimpleName(), "handleOnBackPressed");
return true;
}
Deprecated (since Version 1.0.0-alpha06
April 3rd, 2019) :
Since this, it can be implemented just using JetPack implementation OnBackPressedCallback in your fragment
and add it to activity:
getActivity().addOnBackPressedCallback(getViewLifecycleOwner(),this);
Your fragment should looks like this:
public MyFragment extends Fragment implements OnBackPressedCallback {
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity().addOnBackPressedCallback(getViewLifecycleOwner(),this);
}
#Override
public boolean handleOnBackPressed() {
//Do your job here
//use next line if you just need navigate up
//NavHostFragment.findNavController(this).navigateUp();
//Log.e(getClass().getSimpleName(), "handleOnBackPressed");
return true;
}
#Override
public void onDestroyView() {
super.onDestroyView();
getActivity().removeOnBackPressedCallback(this);
}
}
UPD:
Your activity should extends AppCompatActivityor FragmentActivity and in Gradle file:
implementation 'androidx.appcompat:appcompat:{lastVersion}'
For anyone looking for a Kotlin implementation see below.
Note that the OnBackPressedCallback only seems to work for providing custom back behavior to the built-in software/hardware back button and not the back arrow button/home as up button within the actionbar/toolbar. To also override the behavior for the actionbar/toolbar back button I'm providing the solution that's working for me. If this is a bug or you are aware of a better solution for that case please comment.
build.gradle
...
implementation "androidx.appcompat:appcompat:1.1.0-rc01"
implementation "androidx.navigation:navigation-fragment-ktx:2.0.0"
implementation "androidx.navigation:navigation-ui-ktx:2.0.0"
...
MainActivity.kt
...
import androidx.appcompat.app.AppCompatActivity
...
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)
...
val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration = AppBarConfiguration(navController.graph)
// This line is only necessary if using the default action bar.
setupActionBarWithNavController(navController, appBarConfiguration)
// This remaining block is only necessary if using a Toolbar from your layout.
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.setupWithNavController(navController, appBarConfiguration)
// This will handle back actions initiated by the the back arrow
// at the start of the toolbar.
toolbar.setNavigationOnClickListener {
// Handle the back button event and return to override
// the default behavior the same way as the OnBackPressedCallback.
// TODO(reason: handle custom back behavior here if desired.)
// If no custom behavior was handled perform the default action.
navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
/**
* If using the default action bar this must be overridden.
* This will handle back actions initiated by the the back arrow
* at the start of the action bar.
*/
override fun onSupportNavigateUp(): Boolean {
// Handle the back button event and return true to override
// the default behavior the same way as the OnBackPressedCallback.
// TODO(reason: handle custom back behavior here if desired.)
// If no custom behavior was handled perform the default action.
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
}
MyFragment.kt
...
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
...
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Handle the back button event
}
}
requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback)
}
}
The official documentation can be viewed at https://developer.android.com/guide/navigation/navigation-custom-back
So, I created an interface
public interface OnBackPressedListener {
void onBackPressed();
}
And implemented it by all fragments that need to handle back button. In main activity I overrided onBackPressed() method:
#Override
public void onBackPressed() {
final Fragment currentFragment = mNavHostFragment.getChildFragmentManager().getFragments().get(0);
final NavController controller = Navigation.findNavController(this, R.id.nav_host_fragment);
if (currentFragment instanceof OnBackPressedListener)
((OnBackPressedListener) currentFragment).onBackPressed();
else if (!controller.popBackStack())
finish();
}
So, If the top fragment of my Navigation host implements OnBackPressedListener interface, I call its onBackPressed() method, elsewhere I simply pop back stack and close application if the back stack is empty.
The recommended approach is to add an OnBackPressedCallback to the activity's OnBackPressedDispatcher.
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
// handle back event
}
Here is solution that should do what you want, but i think it is a bad solution, because it is going against Android Navigation component idea(letting the android handle the navigation).
Override "onBackPressed" inside your activity
override fun onBackPressed() {
when(NavHostFragment.findNavController(nav_host_fragment).currentDestination.id) {
R.id.fragment2-> {
val dialog=AlertDialog.Builder(this).setMessage("Hello").setPositiveButton("Ok", DialogInterface.OnClickListener { dialogInterface, i ->
finish()
}).show()
}
else -> {
super.onBackPressed()
}
}
}
I written in main activity like this,
override fun onSupportNavigateUp(): Boolean {
return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
}
Just add these lines
override fun onBackPressed() {
if(navController.popBackStack().not()) {
//Last fragment: Do your operation here
finish()
}
navController.popBackStack() will just pop your fragment if this is not your last fragment
In 2.1.0-alpha06
If you want to handle backpress only in current fragment
requireActivity().onBackPressedDispatcher.addCallback(this#LoginFragment) {
// handle back event
}
For whole Activity
requireActivity().onBackPressedDispatcher.addCallback() {
// handle back event
}
Update
Apr 22, '21
I'm updating my answer to showcase a sample of the recommended approach which is also the accepted answer above.
class MyFragment : Fragment() {
...
private val backPressedDispatcher = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Redirect to our own function
this#MyFragment.onBackPressed()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
setHasOptionsMenu(true) //Set this to true in order to trigger callbacks to Fragment#onOptionsItemSelected
(requireActivity() as AppCompatActivity).apply {
// Redirect system "Back" press to our dispatcher
onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressedDispatcher)
// Set toolbar if it is in Fragment's layout. If you have a global toolbar that lives in Activity layout, then you don't need this line.
setSupportActionBar(view.findViewById(R.id.toolbar))
// Setup action bar to work with NavController
setupActionBarWithNavController(findNavController())
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == android.R.id.home) {
// Redirect "Up/Home" button clicks to our own function
this#MyFragment.onBackPressed()
true
} else {
super.onOptionsItemSelected(item)
}
}
private fun onBackPressed() {
// Work your magic! Show dialog etc.
}
override fun onDestroyView() {
// It is optional to remove since our dispatcher is lifecycle-aware. But it wouldn't hurt to just remove it to be on the safe side.
backPressedDispatcher.remove()
super.onDestroyView()
}
}
Original answer
Jan 3 '19
A little late to the party, but with the latest release of Navigation Component 1.0.0-alpha09, now we have an AppBarConfiguration.OnNavigateUpListener.
Refer to these links for more information:
https://developer.android.com/reference/androidx/navigation/ui/AppBarConfiguration.OnNavigateUpListener
https://developer.android.com/jetpack/docs/release-notes
FragmentExtenstions.kt
fun Fragment.onBackPressedCustomAction(action: () -> Unit) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override
fun handleOnBackPressed() {
action()
}
})
}
YourPrettyFragment.kt
onBackPressedCustomAction {
// Your custom action here
}
This is 2 lines of code can listen for back press, from fragments, [TESTED and WORKING]
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
//setEnabled(false); // call this to disable listener
//remove(); // call to remove listener
//Toast.makeText(getContext(), "Listing for back press from this fragment", Toast.LENGTH_SHORT).show();
}
The recommended method worked for me but after updating my library implementation 'androidx.appcompat:appcompat:1.1.0'
Implement as below
val onBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Handle the back button event
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
using Kotlin
you can provide your custom back navigation by using OnBackPressedDispatcher
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
// Handle the back button event
// and if you want to need navigate up
//NavHostFragment.findNavController(this).navigateUp()
}
// The callback can be enabled or disabled here or in the lambda
}
}
More explanations in android official guide: https://developer.android.com/guide/navigation/navigation-custom-back
Use this if you're using fragment or add it in your button click listener. This works for me.
requireActivity().onBackPressed()
Called when the activity has detected the user's press of the back key. The getOnBackPressedDispatcher() OnBackPressedDispatcher} will be given chance to handle the back button before the default behavior of android.app.Activity#onBackPressed()} is invoked.
Using navigation components
This was good for me:
Navigation.findNavController(requireView()).popBackStack()
android documentation
If you use Navigation Component follow the codes below in your onCreateView() method (in this example I want just to close my app by this fragment)
OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
new AlertDialog.Builder(Objects.requireNonNull(getActivity()))
.setIcon(R.drawable.icon_01)
.setTitle(getResources().getString(R.string.close_app_title))
.setMessage(getResources().getString(R.string.close_app_message))
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
getActivity().finish();
}
})
.setNegativeButton(R.string.no, null)
.show();
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
And if you want the same behavior also for the toolbar back button just add this in your activity:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
getOnBackPressedDispatcher().onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
just create an extension function to the fragment
fun Fragment.onBackPressedAction(action: () -> Boolean) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object :
OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
this.isEnabled = action()
if (!this.isEnabled) {
requireActivity().onBackPressed()
}
}
})
}
and after in the fragment put the code into onCreateView (the action must return false to call the activity onBackPressed)
onBackPressedAction { //do something }
Kotlin Answer
Use popBackStack() example:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mButton.setOnClickListener {
Navigation.findNavController(view).popBackStack() // You need this line.
}
}
If you are using BaseFragment for your app then you can add onBackPressedDispatcher to your base fragment.
//Make a BaseFragment for all your fragments
abstract class BaseFragment : Fragment() {
private lateinit var callback: OnBackPressedCallback
/**
* SetBackButtonDispatcher in OnCreate
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setBackButtonDispatcher()
}
/**
* Adding BackButtonDispatcher callback to activity
*/
private fun setBackButtonDispatcher() {
callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
onBackPressed()
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}
/**
* Override this method into your fragment to handleBackButton
*/
open fun onBackPressed() {
}
}
Override onBackPressed() in your fragment by extending basefragment
//How to use this into your fragment
class MyFragment() : BaseFragment(){
private lateinit var mView: View
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
mView = inflater.inflate(R.layout.fragment_my, container, false)
return mView.rootView
}
override fun onBackPressed() {
//Write your code here on back pressed.
}
}
Try this. I think this will help you.
override fun onBackPressed() {
when (mNavController.getCurrentDestination()!!.getId()) {
R.id.loginFragment -> {
onWarningAlertDialog(this, "Alert", "Do you want to close this application ?")
}
R.id.registerFragment -> {
super.onBackPressed()
}
}
}
private fun onWarningAlertDialog(mainActivity: MainActivity, s: String, s1: String) {
val dialogBuilder = AlertDialog.Builder(this)
dialogBuilder.setMessage(/*""*/s1)
.setCancelable(false)
.setPositiveButton("Proceed", DialogInterface.OnClickListener { dialog, id ->
finish()
})
.setNegativeButton("Cancel", DialogInterface.OnClickListener { dialog, id ->
dialog.cancel()
})
// create dialog box
val alert = dialogBuilder.create()
// set title for alert dialog box
alert.setTitle("AlertDialogExample")
// show alert dialog
alert.show()
}
if you are actually trying to handle back button specifically then you could use #Jurij Pitulja answer.
But, if you want pop SecondFragment (start fragment FirstFragment) and not return to FirstFragment, then you could use :
Navigation.findNavController(view).popBackStack()
from the SecondFragment. This way you would pop the SecondFragmetn of the back stack, and not return to SecondFragment when you press back button from FirstFragment.
This is an answer if you are using the NavController:
Navigation.findNavController(view).navigateUp();
Don't try to hack other methods, like replacing the fragment using FragmentManager and replace() in a transaction. The NavController takes care of this.
Here is my solution
Use androidx.appcompat.app.AppCompatActivity for the activity that contains the NavHostFragment fragment.
Define the following interface and implement it in all navigation destination fragments
interface InterceptionInterface {
fun onNavigationUp(): Boolean
fun onBackPressed(): Boolean
}
In your activity override onSupportNavigateUp and onBackPressed:
override fun onSupportNavigateUp(): Boolean {
return getCurrentNavDest().onNavigationUp() || navigation_host_fragment.findNavController().navigateUp()
}
override fun onBackPressed() {
if (!getCurrentNavDest().onBackPressed()){
super.onBackPressed()
}
}
private fun getCurrentNavDest(): InterceptionInterface {
val currentFragment = navigation_host_fragment.childFragmentManager.primaryNavigationFragment as InterceptionInterface
return currentFragment
}
This solution has the advantage, that the navigation destination fragments don't need to worry about the unregistering of their listeners as soon as they are detached.
I tried Jurij Pitulja solution but I just wasn't able to find getOnBackPressedDispatcher or addOnBackPressedCallback
also using Kiryl Tkach's solution wasn't able to find the current fragment, so here's mine:
interface OnBackPressedListener {
fun onBackPressed(): Boolean
}
override fun onBackPressed() {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
val currentFragment = navHostFragment?.childFragmentManager!!.fragments[0]
if (currentFragment !is OnBackPressedListener || !(currentFragment as OnBackPressedListener).onBackPressed()) super.onBackPressed()
this way you can decide in fragment whether the activity should take control of back pressed or not.
Alternatively, you have BaseActivity for all your activities, you can implement like this
override fun onBackPressed() {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
if (navHostFragment != null){
val currentFragment = navHostFragment.childFragmentManager.fragments[0]
if (currentFragment !is AuthContract.OnBackPressedListener ||
!(currentFragment as AuthContract.OnBackPressedListener).onBackPressed()) super.onBackPressed()
} else {
super.onBackPressed()
}
}
Depending on your logic, if you want to close only the current fragment you have to pass viewLifecycleOwner, code is shown below:
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
requireActivity().finish()
}
})
However, if you want to close application on backPressed no matter from what fragment(probably you wouldn't want that!), don't pass the viewLifecycleOwner. Also if you want to disable the back button, do not do anything inside the handleOnBackPressed(), see below:
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// do nothing it will disable the back button
}
})
My Opinion
requireActivity().onBackPressed()
requireActivity().onBackPressed()
I have searched through many threads and none of them work. Finally I found one:
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar mToolbar = findViewById(R.id.topAppBar);
setSupportActionBar(mToolbar);
}
#Override
public boolean onSupportNavigateUp() {
navController.navigateUp();
return super.onSupportNavigateUp();
}
MyFragment.java
#Override
public void onViewCreated(#NonNull final View view, #Nullable Bundle savedInstanceState) {
Toolbar mToolbar = (MainActivity) getActivity().findViewById(R.id.topAppBar);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Do something when uses presses back button (showing modals, messages,...)
// Note that this will override behaviour of back button
}
});
}
#Override
public void onStop() {
// Reset back button to default behaviour when we leave this fragment
Toolbar mToolbar = (MainActivity) getActivity().findViewById(R.id.topAppBar);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mainActivity.onBackPressed();
}
});
super.onStop();
}
I need to support both real back button and toolbar back button with ability to override "Back" click in both cases (to show dialog or something else). I made an additional method in activity and corresponding boolean checks ('onBackPressed' in my case) in fragments:
// Process hardware Back button
override fun onBackPressed() {
if (canCloseActivity()) {
super.onBackPressed()
}
}
// Process toobar Back and Menu button
override fun onSupportNavigateUp(): Boolean {
if (canCloseActivity()) {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
return false
}
// Do real check if has unfinished tasks, return false to override activity closing
private fun canCloseActivity(): Boolean {
val currentFragment = navHostFragment.childFragmentManager.primaryNavigationFragment
return when {
currentFragment is MyFragment && currentFragment.onBackPressed() -> false
drawerLayout.isOpen -> {
drawerLayout.close()
false
}
fullScreenPreviewLayout.visibility == View.VISIBLE -> {
closeFullscreenPreview()
false
}
else -> true
}
}
Simply, in onCreate() method of your Fragment use this code after super.onCreate(savedInstanceState):
// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) {
// Handle the back button event
}

Categories

Resources