Reputation: 12559
I have upgraded targetSdkVersion
and compileSdkVersion
to 33
.
I am now getting a warning telling me that onBackPressed is deprecated.
I see suggestions to use android.window.OnBackInvokedCallback or androidx.activity.OnBackPressedCallback to handle back navigation instead. Can anyone can help me use the updated method?
I use if (isTaskRoot) {}
inside the onBackPressed() method to check whether the activity is the last one on the activity stack.
override fun onBackPressed() {
if (isTaskRoot) { // Check whether this activity is last on the activity stack. (Check whether this activity opened from a Push Notification.)
startActivity(Intent(mContext, Dashboard::class.java))
finish()
} else {
finishWithResultOK()
}
}
Upvotes: 230
Views: 165443
Reputation: 41
If you use fragments and bottom navigation. To perform the original behavior, that works for me
Remove onBackPressed() and add the code below:
Kotlin
private val backPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
navigateUp(navController, appBarCofig)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
appBarConfig = AppBarConfiguration
.Builder(HashSet<Int>(obtainTopLevel(bottomNav)))
.build()
// topLevel is a list of menuItem ids from your bottom menu, fragments where burger (not arrow) is shown, at the left of toolbar
//...
getOnBackPressedDispatcher().addCallback(this, backPressedCallback)
}
private fun obtainTopLevel(bottomNav: BottomNavigationView): List<Int> {
val topLevel: MutableList<Int> = ArrayList()
bottomNav.menu.forEach {
topLevel.add(it.itemId)
}
return topLevel
}
Java
// to init:
// bottomNav, appBarConfig, navController etc.
private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
NavigationUI.navigateUp(navController, appBarCofig);
}
};
@Override
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
appBarConfig =
new AppBarConfiguration
.Builder(new HashSet<>(obtainTopLevel(bottomNav)))
.build();
// topLevel is a list of menuItem ids from your bottom menu, fragments where burger (not arrow) is shown, at the left of toolbar
//...
getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
}
private List<Integer> obtainTopLevel(BottomNavigationView bottomNav) {
List<Integer> topLevel = new ArrayList<>();
for (int i = 0; i < bottomNav.getMenu().size(); i++) {
topLevel.add( bottomNav.getMenu().getItem(i).getItemId() );
}
return topLevel;
};
if you use drawer:
private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
} else {
NavigationUI.navigateUp(navController, drawerLayout);
}
}
};
@Override
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawerNavView = findViewById(R.id.drawer_nav_view);
drawerLayout = findViewById(R.id.drawer_layout);
bottomNav = findViewById(R.id.bottomNav);
bottomNav.inflateMenu(R.menu.bottomMenu);
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
navController = NavHostFragment.findNavController(navHostFragment);
NavigationUI.setupWithNavController(drawerNavView, navController);
NavigationUI.setupWithNavController(bottomNav, navController);
List<Integer> topLevelDestinations = obtainTopLevel(bottomNav);
AppBarConfiguration appBarConfig =
new AppBarConfiguration.Builder(new HashSet<>(topLevelDestinations))
.setOpenableLayout(drawerLayout)
.build();
NavigationUI.setupWithNavController(toolbar, navController, appBarConfig);
//...
getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
}
Upvotes: 1
Reputation: 28
For fragments using Jetpack navigation you can create an extension function, which will handle both the back and up press events.
Back press -> It is the action of pressing button by gesture navigation or soft back button
Up press -> It is the action of pressing the back button in navigation to top left of the access bar. Its global id is android.R.id.home
FragmentExtension.kt
//Global Extension Listener to call on Back or Up pressed
fun Fragment.onBackOrUpButtonPressed(navController: NavController, callback: () -> Boolean) {
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
performBackPress(navController, callback)
}
})
requireActivity().addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
// No need to inflate menu items for the "Up" button, this is just to manage options
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
android.R.id.home -> {
performBackPress(navController, callback)
}
}
return true
}
}, viewLifecycleOwner)
}
//Handle back press without navController
fun Fragment.performBackPress() {
requireActivity().onBackPressedDispatcher.onBackPressed()
}
//Handle back press from fragment
fun Fragment.performBackPress(navController: NavController, callback: () -> Boolean) {
if (!callback()) {
if (navController.currentBackStackEntry != null && navController.popBackStack()) {
// If NavController can handle the back press, let it do so
} else {
performBackPress()
}
}
}
//Handle back press with navController
fun Fragment.performBackPress(navController: NavController) {
if (navController.currentBackStackEntry != null && navController.popBackStack()) {
// If NavController can handle the back press, let it do so
} else {
performBackPress()
}
}
HomeFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onBackOrUpButtonPressed(findNavController()) {
// Do your task here
// Return "false" if you want to system perform back
// Return "true" if you want to override the back functionality
true
}
//To manually perform the back action
//Perform back press without navController
performBackPress()
//-OR-
//Perform back press with navController
performBackPress(findNavController())
}
This will give the ability to handle the back/up press with cleaner fragments without any hassle.
Upvotes: 1
Reputation: 2037
Here is a more recent answer. You can find the documentation here(https://codelabs.developers.google.com/handling-gesture-back-navigation#4). Below add the code to your onCreate() inside main Activity. Unless your project requires other usage, reference the documentation.
val onBackPressedCallback = object: OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
when {
//when back button pressed do you own code.
}
}
}
}
this.onBackPressedDispatcher
.addCallback(onBackPressedCallback) (this -> inside activity , otherwise use requireActivity())
Upvotes: 1
Reputation: 5339
Replace onBackPressed()
with the below code.
onBackPressedDispatcher.onBackPressed()
getOnBackPressedDispatcher().onBackPressed();
Upvotes: 174
Reputation: 1
if you're overriding the onBackPressed() its okay but if you want to call this method, use this code and you'll find: onBackPressedDispatcher.onBackPressed()
Upvotes: 0
Reputation: 22168
Based on all answers above (this, and this) I created these simple extensions which give you the same experience of using the onBackPressed function. we use onBackPressedDispatcher
for handling the back button.
First, add appcompat:1.6.1
(or any newer version of this library) to your gradle config.
implementation 'androidx.appcompat:appcompat:1.6.1'
Then based on your target view you can use one of these extensions:
fun Activity.onBackButtonPressed(callback: (() -> Boolean)) {
(this as? FragmentActivity)?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!callback()) {
remove()
performBackPress()
}
}
})
}
fun Activity.performBackPress() {
(this as? FragmentActivity)?.onBackPressedDispatcher?.onBackPressed()
}
fun Fragment.onBackButtonPressed(callback: (() -> Boolean)) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (!callback()) {
remove()
performBackPress()
}
}
})
}
fun Fragment.performBackPress() {
requireActivity().onBackPressedDispatcher.onBackPressed()
}
fun Dialog.onBackButtonPressed(callback: (() -> Boolean)) {
setOnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
callback()
} else {
false
}
}
}
Add this function to your OnCreate()
method:
onBackButtonPressed {
// Do your job
// Return "false" if you want to system perform back
// Return "true" if you want to override the back functionality
true
}
If you want to fully override the back button you can return true
and in case of letting the system handle the back button, you need to return false
at the end of the callback.
Last but not least, if you want to manually perform the back button in your Activity or Fragment, just simply call:
performBackPress() // replacement for deprecated onBackPressed()
Upvotes: 2
Reputation: 101
Remove onBackPressed()
Add dependencies
implementation 'androidx.appcompat:appcompat:1.6.1'
In your onCreate method
onBackInvokedDispatcher.registerOnBackInvokedCallback for API level 33+
onBackPressedDispatcher callback for backword compatibility "API level 13+"
import androidx.activity.OnBackPressedCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, new OnBackInvokedCallback() {
@Override
public void onBackInvoked() {
finish();
}
}
);
} else {
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
finish();
}
});
}
}
Upvotes: 1
Reputation: 2660
If you like the back button handled you need this part in your onCreate() method of your activity. Don't forget to actually finish the activity in the handleOnBackPressed().
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
//Add custom actions here (like before in onBackPressed())
finish();
}
});
If you have a up arrow inside your activity, this involves the method onSupportNavigateUp() for AppCompatActivity.
So you also need to handle this. Do it this way.
@Override
public boolean onSupportNavigateUp() {
getOnBackPressedDispatcher().onBackPressed();
return true;
}
Upvotes: 4
Reputation: 224
If you're using Java then here's a way I'm doing it.
// Creating a global variable
private final OnBackPressedDispatcher onBackPressedDispatcher = getOnBackPressedDispatcher();
// In your onCreate method
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
// Add callback listener
onBackPressedDispatcher.addCallback(new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
// Handle onback pressed
}
});
}
Then if you want to force call onBackPressed you can do it using:
mBtnBack.setOnClickListener(v -> onBackPressedDispatcher.onBackPressed());
Upvotes: 4
Reputation: 169
you can use onBackPressedDispatcher. Below an example:
val back = this.onBackPressedDispatcher
back.addCallback(this, object : OnBackPressedCallback(true){
override fun handleOnBackPressed() {
//println("back pressed")
}
})
Upvotes: 4
Reputation: 3046
Use like below,
override fun onClick(v: View?) {
when (v?.id) {
R.id.iv_back -> onBackPressedMethod()
}
}
and now create that method for handling back event
private fun onBackPressedMethod(){
if (Build.VERSION.SDK_INT >= 33) {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT) {
onBackPressed.invoke()
}
} else {
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
onBackPressed.invoke()
}
})
}
}
That's it!
Upvotes: -3
Reputation: 4537
Most answers are calling finish()
when you want to close the activity. This works fine for most cases but in some situations this doesn't work. For example, in Android 13, when you press back on the last activity in the stack and return to the home screen, onDestroy()
is not called immediately and app remains in memory. If you open up the app again right then, it starts from onStart()
.
So, in some situations its better to let the system handle the closing of the app, or in the other words, let super.onBackPressed()
happen.
To replace this
override fun onBackPressed() {
if(showPopUpBeforeClosing){
showDialog()
} else {
super.onBackPressed()
}
}
do this -
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (showPopUpBeforeClosing) {
showDialog()
} else {
//Removing this callback
remove()
onBackPressedDispatcher.onBackPressed()
}
}
})
If you add a callback to the onBackPressedDispatcher
, and call onBackPressedDispatcher.onBackPressed()
, it always calls handleOnBackPressed()
. There is no method like onSuperBackPressed() or something to let the system know to handle the backPress on its own once you're done. If you don't add the callback, then the system does its own thing, but if you've added it, then calling onBackPress will invoke handleOnBackPressed()
. So what we do is, once you're done handling the back-press, you callback.remove()
the callback removes itself and now when you do onBackPressedDispatcher.onBackPressed()
, it will not invoke handleOnBackPressed()
and handle the back-press as the system would do, which is equivalent to super.onBackPressed()
.
Upvotes: 5
Reputation: 21
first,we have to add a callback object because, onBackPressedDispatcher.onBackPressed()
triggers a call to the currently added callbacks in reverse order in which they were added.
So after adding the callback only we should call the method onBackPressed()
The code (in kotlin) is as follows:
onBackPressedDispatcher.addCallback(this,object :OnBackPressedCallback(true){
override fun handleOnBackPressed() {
Log.i("TAG","back has been called")
finish()
}
})
onBackPressedDispatcher.onBackPressed()
Upvotes: 2
Reputation: 1022
Simply replace
override fun onBackPressed() {
super.onBackPressed() // Replace this deprecated line
}
with
override fun onBackPressed() {
onBackPressedDispatcher.onBackPressed() // with this line
}
Given that override fun onBackPressed()
is deprecated, replace it with the following code:
onBackPressedDispatcher.addCallback(this, object: OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
println("Back button pressed")
// Code that you need to execute on back press, e.g. finish()
}
})
Add the above code to an activity lifecycle function like onCreate(savedInstanceState:)
.
Upvotes: 57
Reputation: 395
I solved this by simply adding an extension to AppCompatActivity
:
fun AppCompatActivity.onBackButtonPressed(callback: (() -> Unit)? = null){
val onBackPressed: OnBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
callback?.let { callback() } ?: run { finish() }
}
}
this.onBackPressedDispatcher.addCallback(this, onBackPressed)
}
So I can use it by calling "onBackButtonPressed" in two ways
1- Implement back pressed to finish activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// implement back pressed
onBackButtonPressed()
}
2- Handle back pressed clicks:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// implement back pressed
onBackButtonPressed {
// handle back pressed click here,
}
}
Upvotes: 3
Reputation: 343
To set up ability to override default behaviour:
(call setupBackPressedHandling()
From Activity onCreate()
):
private fun setupBackPressedHandling() {
onBackPressedDispatcher.addCallback(this) {
if (!backPressHandled())
invokeDefaultBackPressedHandling()
}
}
open fun backPressHandled(): Boolean = false
Global extension function:
fun AppCompatActivity.invokeDefaultBackPressedHandling(navController: NavController?) {
when(navController) {
null -> {
if (!supportFragmentManager.isStateSaved && !supportFragmentManager.popBackStackImmediate())
finish()
}
else -> {
if (!navController.popBackStack())
finish()
}
}
}
Upvotes: 1
Reputation: 81
For Kotlin Users:
If you are trying to call the Default Native Back Button function using the 'new' way, you can use the code bellow inside your Activity.
[email protected]()
example:
myCloseButton.setOnClickListener { [email protected]() }
The example above sets a click function inside a button, to make the button act like the Native Android Back Button.
However, if you want to customize the onBackPressedDispatcher, you can follow the example bellow, always inside your Activity, because this behaviour needs an Activity Context to work.
Example of Customized OnBackPressed:
override fun onCreate(savedInstanceState: Bundle?) {
val callback: OnBackPressedCallback = object : OnBackPressedCallBack(true) {
override fun handleOnBackPressed() {
//ToDo Implement your custom event here
}
}
[email protected](this@MyActivity, callback)
}
The expected result is to make any onBackPressedDispatcher event to do whatever you want, or do nothing at all. But this is not recommended, since your user might get stuck in a screen, without being able to use his Phone's Back Button. Because of that, avoid leaving handleOnBackPressed override empty. So try something like this:
override fun handleOnBackPressed() {
[email protected]()
}
Upvotes: 7
Reputation: 598
import android.os.Bundle
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class SampleActivity : AppCompatActivity(R.layout.activity_sample) {
private val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
showAppClosingDialog()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
}
private fun showAppClosingDialog() {
MaterialAlertDialogBuilder(this)
.setTitle("Warning")
.setMessage("Do you really want to close the app?")
.setPositiveButton("Yes") { _, _ -> finish() }
.setNegativeButton("No", null)
.show()
}
}
Upvotes: 10
Reputation: 200
use onBackPressedDispatcher.onBackPressed() instead of super.onBackPressed()
override fun onBackPressed() {
onBackPressedDispatcher.onBackPressed()
}
Upvotes: 5
Reputation: 41
override the onDismiss() function for BottomSheets.
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
//TODO your logic.
}
Upvotes: 0
Reputation: 40878
According your API level register:
onBackInvokedDispatcher.registerOnBackInvokedCallback
for API level 33+onBackPressedDispatcher
callback for backword compatibility "API level 13+"This requires to at least use appcompat:1.6.0-alpha03
; the current is 1.6.0-alpha04
:
implementation 'androidx.appcompat:appcompat:1.6.0-alpha04'
// kotlin
import androidx.activity.addCallback
if (BuildCompat.isAtLeastT()) {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT
) {
// Back is pressed... Finishing the activity
finish()
}
} else {
onBackPressedDispatcher.addCallback(this /* lifecycle owner */, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Back is pressed... Finishing the activity
finish()
}
})
}
// ====================================================
/* Or for lambda simplicity: */
// ====================================================
if (BuildCompat.isAtLeastT()) {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT
) {
// Back is pressed... Finishing the activity
finish()
}
} else {
onBackPressedDispatcher.addCallback(this /* lifecycle owner */) {
// Back is pressed... Finishing the activity
finish()
}
}
Thanks to @ianhanniballake comment; you can just use OnBackPressedDispatcher
even in API level 33+
The OnBackPressedDispatcher is already going to be using the Android T specific API internally when using Activity 1.6+,
So, you can just do:
// kotlin
import androidx.activity.addCallback
onBackPressedDispatcher.addCallback(this /* lifecycle owner */, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Back is pressed... Finishing the activity
finish()
}
})
// ====================================================
/* Or for lambda simplicity: */
// ====================================================
onBackPressedDispatcher.addCallback(this /* lifecycle owner */) {
// Back is pressed... Finishing the activity
finish()
}
// java
import androidx.activity.OnBackPressedCallback;
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
// Back is pressed... Finishing the activity
finish();
}
});
Note that you shouldn't override the onBackPressed()
as that will make the onBackPressedDispatcher
callback not to fire; check this answer for clarifying that.
Upvotes: 153
Reputation: 1762
With a combination of top answers. Here is a solution :
1. When you need to press the back button, copy this :
Note: it will automatically destroy your activity.
button.setOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
2. When you need to handle the back button pressed, copy this :
onBackPressedDispatcher.addCallback(this, object: OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Whatever you want
// when back pressed
println("Back button pressed")
finish()
}
})
Upvotes: 48
Reputation: 734
Here is the extension function to implement OnBackPressedCallback in activity.
fun AppCompatActivity.addOnBackPressedDispatcher(onBackPressed: () -> Unit = { finish() }) {
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
onBackPressed.invoke()
}
}
)
}
Usage:
addOnBackPressedDispatcher {
//doSomething()
}
Upvotes: 3
Reputation: 4651
In Kotlin, this way is working
1- Remove onBackPressed()
2- below onCreate(savedInstanceState: Bundle?)
add these lines:
if (Build.VERSION.SDK_INT >= 33) {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT
) {
exitOnBackPressed()
}
} else {
onBackPressedDispatcher.addCallback(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
Log.i("TAG", "handleOnBackPressed: Exit")
exitOnBackPressed()
}
})
}
3- Define a new function for handling
fun exitOnBackPressed() {
}
Upvotes: 16
Reputation: 379
Bonus: To close DrawerLayout when onBackPressed use like below (according to this I/O talk),
val callback = onBackPressedDispatcher.addCallback(this, false) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
}
binding.drawerLayout.addDrawerListener(object : DrawerListener {
override fun onDrawerOpened(drawerView: View) {
callback.isEnabled = true
}
override fun onDrawerClosed(drawerView: View) {
callback.isEnabled = false
}
override fun onDrawerSlide(drawerView: View, slideOffset: Float) = Unit
override fun onDrawerStateChanged(newState: Int) = Unit
})
Upvotes: 3
Reputation: 1737
You could use the onBackPressedDispatcher
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
}
})
in here "this" means the lifeCycleOwner
Upvotes: 8
Reputation: 91
You can use the OnBackInvokedCallback
as described in the documentation and follow this guide here to update your code
Upvotes: 4