migrate deprecated onBackPressed() (#51096)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51096

**Problem:** `Activity.onBackPressed()` has been deprecated and with targetSdk 36, predictive back will be enforced and the API no longer called. We need to migrate to backward compatible AndroidX `OnBackPressedCallback`.
- https://developer.android.com/about/versions/16/behavior-changes-16#predictive-back.

**Solution:**
`OnBackPressedCallback` is registered conditionally only if it is `targetSdk` 36 or greater.
If the callback in enabled, `onBackPressed()` is not called and callback is used regardless of `android:enableOnBackInvokedCallback` property in <application> or <activity> set in AndroidManifest.xml.

As a workaround callback is manually calling existing `onBackPressed()`.

This is done rather than removing onBackPressed() completely and using only `OnBackPressedCallback` as we are not sure of the impact of removing the implementation entirely. Once we determine it is safe to do so then, we should remove the workaround and fully transition to `OnBackPressedCallback`

* I also surveyed child classes extending ReactActivity for overridden `onPressedBack()` usage and found only one usage which will be handled later.

NOTE: `ReactDelegate.onHostResume()` sets up the `DefaultHardwareBackBtnHandler` using `ReactActivity` (https://fburl.com/ul47tbeo) and will be called from JS `BackHanderl.exitApp` (https://fburl.com/code/a4l2pjsw). Calling `BackHanderl.exitApp` enables predictive back to work.

Changelog:
[Internal]

Reviewed By: mdvacca

Differential Revision: D74161428

fbshipit-source-id: 2e081ba6922b315e9d1746e83a41bab5277fa62e
This commit is contained in:
Alan Lee
2025-05-19 15:32:24 +00:00
committed by React Native Bot
parent 84cf4d8869
commit 0e3764c8aa
2 changed files with 50 additions and 0 deletions
@@ -11,11 +11,13 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.KeyEvent;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionAwareActivity;
import com.facebook.react.modules.core.PermissionListener;
import com.facebook.react.util.AndroidVersion;
import org.jetbrains.annotations.NotNull;
/** Base Activity for React Native applications. */
@@ -24,6 +26,18 @@ public abstract class ReactActivity extends AppCompatActivity
private final ReactActivityDelegate mDelegate;
// Due to enforced predictive back on targetSdk 36, 'onBackPressed()' is disabled by default.
// Using a workaround to trigger it manually.
private final OnBackPressedCallback mBackPressedCallback =
new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
setEnabled(false);
onBackPressed();
setEnabled(true);
}
};
protected ReactActivity() {
mDelegate = createReactActivityDelegate();
}
@@ -45,6 +59,9 @@ public abstract class ReactActivity extends AppCompatActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDelegate.onCreate(savedInstanceState);
if (AndroidVersion.isAtLeastTargetSdk36(this)) {
getOnBackPressedDispatcher().addCallback(this, mBackPressedCallback);
}
}
@Override
@@ -103,6 +120,9 @@ public abstract class ReactActivity extends AppCompatActivity
@Override
public void invokeDefaultOnBackPressed() {
// Disabling callback so the fallback logic (finish activity) can run
// as super.onBackPressed() will call all enabled callbacks in the dispatcher.
mBackPressedCallback.setEnabled(false);
super.onBackPressed();
}
@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.util
import android.content.Context
import android.os.Build
/** Helper class for checking Android version-related information. */
internal object AndroidVersion {
/**
* This is the version code for Android 16 (SDK Level 36). Delete it once we bump up the default
* compile SDK version to 36.
*/
private const val VERSION_CODE_BAKLAVA: Int = 36
/**
* This method is used to check if the current device is running Android 16 (SDK Level 36) or
* higher and the app is targeting Android 16 (SDK Level 36) or higher.
*/
@JvmStatic
fun isAtLeastTargetSdk36(context: Context): Boolean =
Build.VERSION.SDK_INT >= VERSION_CODE_BAKLAVA &&
context.applicationInfo.targetSdkVersion >= VERSION_CODE_BAKLAVA
}