From fae01900aef6706ec78bb5b9a9f5082886b8adcc Mon Sep 17 00:00:00 2001 From: Bohdan Pomohaibo Date: Mon, 9 Nov 2026 20:13:16 +0200 Subject: [PATCH] Register the ReactActivity back callback on Android 13+ for apps targeting SDK 36 --- .../com/facebook/react/ReactActivity.java | 7 +- .../react/ReactActivityBackCallbackTest.kt | 76 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/ReactActivityBackCallbackTest.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java index b375d1859dbc..dbd698a0b80c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java @@ -9,6 +9,7 @@ import android.content.Intent; import android.content.res.Configuration; +import android.os.Build; import android.os.Bundle; import android.view.KeyEvent; import androidx.activity.OnBackPressedCallback; @@ -60,7 +61,11 @@ protected ReactActivityDelegate createReactActivityDelegate() { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mDelegate.onCreate(savedInstanceState); - if (AndroidVersion.isAtLeastTargetSdk36(this)) { + // From API 33, an app that sets android:enableOnBackInvokedCallback="true" receives Back + // through OnBackInvokedDispatcher, and onBackPressed() is not called. Register the callback + // on API 33+ so that BackHandler keeps working there, not only on API 36+. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && getApplicationInfo().targetSdkVersion >= AndroidVersion.VERSION_CODE_BAKLAVA) { getOnBackPressedDispatcher().addCallback(this, mBackPressedCallback); } } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/ReactActivityBackCallbackTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/ReactActivityBackCallbackTest.kt new file mode 100644 index 000000000000..49822fef6849 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/ReactActivityBackCallbackTest.kt @@ -0,0 +1,76 @@ +/* + * 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 + +import android.os.Bundle +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +class ReactActivityBackCallbackTest { + + @Test + @Config(sdk = [33, 34, 35]) + fun targetSdk36_backFromDispatcher_reachesReactDelegate() { + val activity = createActivity(targetSdkVersion = 36, handledByJs = true) + + assertThat(activity.onBackPressedDispatcher.hasEnabledCallbacks()).isTrue() + + activity.onBackPressedDispatcher.onBackPressed() + + assertThat(activity.delegatedBackPressCount).isEqualTo(1) + assertThat(activity.isFinishing).isFalse() + assertThat(activity.onBackPressedDispatcher.hasEnabledCallbacks()).isTrue() + } + + @Test + @Config(sdk = [33, 34, 35]) + fun targetSdk36_backNotHandledByJs_finishesActivity() { + val activity = createActivity(targetSdkVersion = 36, handledByJs = false) + + activity.onBackPressedDispatcher.onBackPressed() + + assertThat(activity.delegatedBackPressCount).isEqualTo(1) + assertThat(activity.isFinishing).isTrue() + } + + @Test + @Config(sdk = [33, 35]) + fun targetSdk35_doesNotRegisterCallback() { + val activity = createActivity(targetSdkVersion = 35, handledByJs = true) + + assertThat(activity.onBackPressedDispatcher.hasEnabledCallbacks()).isFalse() + } + + private fun createActivity(targetSdkVersion: Int, handledByJs: Boolean): TestReactActivity { + RuntimeEnvironment.getApplication().applicationInfo.targetSdkVersion = targetSdkVersion + val activity = Robolectric.buildActivity(TestReactActivity::class.java).create().start().get() + activity.handledByJs = handledByJs + return activity + } + + class TestReactActivity : ReactActivity() { + var delegatedBackPressCount: Int = 0 + var handledByJs: Boolean = true + + override fun createReactActivityDelegate(): ReactActivityDelegate = + object : ReactActivityDelegate(this, "Test") { + override fun onCreate(savedInstanceState: Bundle?) = Unit + + override fun onBackPressed(): Boolean { + delegatedBackPressCount++ + return handledByJs + } + } + } +}