98

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?

enter image description here

Should I use "pop to" may be?

Kiryl Tkach
  • 2,150
  • 2
  • 16
  • 28

26 Answers26

179

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}'
Machavity
  • 28,730
  • 25
  • 78
  • 91
Jurij Pitulja
  • 3,968
  • 4
  • 15
  • 20
  • 4
    Does this still exist in Jetpack? – Bryan Bryce Feb 04 '19 at 23:37
  • Should I have it if I'm using AppCompatActivity? What class is it a part of? – Bryan Bryce Feb 05 '19 at 00:03
  • 1
    If your AppCompat activity isn`t from support lib but from androidX lib then yes, you should – Jurij Pitulja Feb 05 '19 at 00:06
  • 4
    Using AndroidX AppCompatActivity and Androidx Fragment and I don't see this as an available option either – Omega142 Feb 07 '19 at 16:09
  • It should. Check version or try clean and rebuild project. – Jurij Pitulja Feb 07 '19 at 19:24
  • 1
    Omega142 I believe you should extend from ComponentActivity to be able to override this method – Onur D. Feb 07 '19 at 19:31
  • 1
    ComponentActivity, a new base class of the existing FragmentActivity and AppCompatActivity – filthy_wizard Feb 25 '19 at 16:42
  • Be sure to add "androidx.activity:activity-ktx:" to your list of dependencies. If that doesn't fix it, then Android Studio is likely referencing androidx.core.app.ComponentActivity instead of androidx.activity.ComponentActivity. developer.android.com/reference/androidx/appcompat/app/…, clearly shows that AppCompatActivity is subclass of androidx.activity.ComponentActivity. The problem is AppCompatActivity is referencing androidx.core.app.ComponentActivity. – wooldridgetm Feb 27 '19 at 21:41
  • I know this is a old thread but im hoping you see it, i have this handleBackClick implemented, its working fine going through all the fragments that have it set but its still activating MainActivity onBackPressed(). I thought if one of the fragments had consumed it it ended there? – Dave Mar 09 '19 at 12:32
  • Think your return value from "handleOnBackPressed" is "false" that`s only reason it invoke activity "onBackPressed". Change it to "true". – Jurij Pitulja Mar 09 '19 at 14:49
  • addOnBackPressedCallback is a method of androidx.activity.ComponentActivity which extends androidx.core.app.ComponentActivity. This means that if you use for example androidx.fragment.app.FragmentActivity which extends androidx.core.app.ComponentActivity as well, you have addOnBackPressedCallback not available. – Nantoka Mar 25 '19 at 17:27
  • Yes. You should use **AppCompatActivity**. Also in future releases of androidX it could be separated from activity. – Jurij Pitulja Mar 25 '19 at 17:49
  • @JurijPitulja androidx.appcompat.app.AppCompatActivity extends androidx.fragment.app.FragmentActivity, so you don't have the addOnBackPressedCallback method in AppCompatActivity as well, at least with the current stable version 1.0.2 of the androidx compatibility library. – Nantoka Mar 25 '19 at 19:43
  • Yes, you are right. There is no such method in stable release. I`.m using last alpha. First link in post shows from when and what version it is available . – Jurij Pitulja Mar 25 '19 at 22:59
  • 1
    I think from April 16, you would need one more update, because it seems that OnBackPressedCallback became abstract class, and there is no multiple inheritance so the scenario with BaseFragment giving "this" as parameter to addCalback() falls into water – Brcinho Apr 26 '19 at 13:48
  • This WORKS WELL. I'd recommend you use the first - updated solution. – Mitch Apr 29 '19 at 22:26
  • Where do I file a bug? If activity gets killed by the system. Then the last callback will be not the one you add in "onCreate()" method but the one from NavHostFragment. – Kęstas Venslauskas May 20 '19 at 11:32
  • 3
    OnBackPressedCallback is an abstract class now! So this solution does not work for me. – Junia Montana Sep 13 '19 at 01:26
  • @JuniaMontana so you can create private field mOnBackPressedCallback and pass it instead of passing "this". – Kiryl Tkach Sep 13 '19 at 14:28
  • I was wondering why this solution is not working for me. Later i found that i have overridden onBackPressed() but didn't called super.onBackPressed() from the overridden method. All callbacks registered via addCallback are evaluated when you call super.onBackPressed(). onBackPressed is always called, regardless of any registered instances of OnBackPressedCallback. – Anik Dey Oct 21 '19 at 08:37
  • 3
    How to do the normal back operation after doing some logic on - handleIOnBackPressed() ? – krishnakumarcn Nov 01 '19 at 11:59
  • For me this does not call if the back button in the toolbar is pressed. – nullmn Feb 18 '20 at 18:06
  • It depends which toolbar you are using. There is different cases – Jurij Pitulja Feb 18 '20 at 18:21
  • 1
    in your newest solution, if i want to have a condition in my callback, and if condition has true do something, else return back control to activity, How should I do this? Example: OnBackPressedCallback callback = new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { if(condition) { // do something} else { // activity.onSupportNavigationUp() } } }; – roghayeh hosseini Apr 01 '20 at 21:12
  • I wish they'd simplify it further. Writing a back press listener to work globally for the navigation component isn't ideal mostly--it might make sense to have an API property in the type of the nav graph to allow setting whether you want a custom handler for navigation within that class or null for disabling back press. – Saifur Rahman Mohsin Aug 11 '20 at 07:57
  • In newest version of android 11 this throws an error if we call it in onCreate ` java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView() ` – androidtitan Dec 15 '20 at 22:57
  • It's not necessary to call removeOnBackPressedCallback from onDestroyView because the activity automatically removes callbacks based on the lifespan of the viewLifeCycleOwner you passed to your addOnBackPressedCallback call. – Monte Creasor Jan 11 '21 at 22:12
24

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.

Kiryl Tkach
  • 2,150
  • 2
  • 16
  • 28
19

The recommended approach is to add an OnBackPressedCallback to the activity's OnBackPressedDispatcher.

requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { 
    // handle back event
}
Sanjeev
  • 3,431
  • 1
  • 24
  • 34
  • 1
    better if owner of the callback is also passed like this `.addCallback(viewLifecycleOwner) {}`, otherwise you will keep receiving callbacks even after fragment is destroyed. – Marat Nov 06 '19 at 14:16
14

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

Abtin Gramian
  • 1,351
  • 9
  • 12
  • 1
    This is the best answer. You should be able to handle back press from toolbar and normal backpress. Thumbs up – odifek Jan 14 '20 at 10:11
  • This helped me a lot! My toolbar back button wasn't triggering `OnBackPressedCallback` but after adding `toolbar.setNavigationOnClickListener { onBackPressed() }` it worked so that now the hardware back and the toolbar back work the same. Thanks for the clear detailed answer! – poby May 03 '20 at 07:18
  • This is the correct and most logical solution. Will sure stand the test of time. – Otieno Rowland Jun 21 '20 at 19:09
  • I only used the code for Fragment back override, it works just fine! – sud007 Nov 29 '20 at 16:48
11

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()
        }
    }
} 
Alex
  • 7,423
  • 3
  • 28
  • 32
9

I written in main activity like this,

override fun onSupportNavigateUp(): Boolean {
        return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
    }   
Sai
  • 224
  • 3
  • 6
7

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
}
solaza
  • 885
  • 1
  • 6
  • 23
6

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();
     }
Biplob Das
  • 1,304
  • 11
  • 10
5

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

Onur D.
  • 344
  • 2
  • 11
  • Thanks for showing me the release notes! Found out that _android:menuCategory="secondary"_ avoids popping the back stack! – Valentin Gavran Jan 06 '19 at 17:00
  • for me this works with toolbar only, but not with Back button – Jurij Pitulja Feb 07 '19 at 19:26
  • Same ^ works for the toolbar, how to handle the back button? – Gauri Gadkari Apr 21 '21 at 19:17
  • @GauriGadkari Thanks for the feedback! Unfortunately you're right, my original answer doesn't cover handling system back presses. So I updated my answer with a sample usage of the recommended approach. Please also see the accepted answer. – Onur D. Apr 22 '21 at 21:09
3

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

Gstuntz
  • 360
  • 3
  • 10
3

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

1

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);
Ahmet
  • 266
  • 3
  • 9
1

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);
}
Matteo
  • 71
  • 1
  • 5
1

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

Atul Bhardwaj
  • 6,364
  • 5
  • 39
  • 60
1

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.

Cyd
  • 661
  • 1
  • 9
  • 14
1

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.

QuartZ
  • 136
  • 2
  • 11
0

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()
    }
John Joe
  • 10,340
  • 9
  • 48
  • 104
0

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.

Nantoka
  • 3,749
  • 1
  • 28
  • 32
0

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()
    }
}
Amin Keshavarzian
  • 2,335
  • 1
  • 26
  • 28
0

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.
}

}

Happy Singh
  • 1,142
  • 5
  • 18
0

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
        }
    })
Junia Montana
  • 285
  • 5
  • 8
0

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();
}
Duc Trung Mai
  • 733
  • 1
  • 6
  • 13
0

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 }
David Buck
  • 3,439
  • 29
  • 24
  • 31
Fabrice P.
  • 21
  • 3
0

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
    }
}
Roman
  • 61
  • 2
0

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
}
Mohsin H
  • 1
  • 2
  • Since a `Fragment`'s lifecycle is slightly different than of `View`'s, I think it's best to pass `viewLifecycleOwner` instead. Like this: `requreActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner)` – Onur D. Apr 22 '21 at 21:14
  • Thanks, @OnurD. I've edited my snippet. – Mohsin H Apr 24 '21 at 20:25
-1

My Opinion requireActivity().onBackPressed()

requireActivity().onBackPressed()
nirazverma
  • 541
  • 3
  • 10