Skip to content
Source
Sign inSign up

withAndroidPredictiveBackCompat.cjs

92 lines3.1 KB

1const { withMainActivity } = require("expo/config-plugins");
2​
3// RN 0.85 registers back dispatch only on Android 16 with targetSdk 36; Android 13-15 otherwise bypass JS.
4​
5const CALLBACK_PROPERTY = `
6 // Routes predictive-back gestures to JS on Android 13-15, where react-native
7 // registers no OnBackPressedDispatcher callback of its own (it only does on
8 // Android 16+). Registered in onCreate; added by withAndroidPredictiveBackCompat.
9 private val predictiveBackCompatCallback = object : OnBackPressedCallback(true) {
10 override fun handleOnBackPressed() {
11 isEnabled = false
12 onBackPressed()
13 isEnabled = true
14 }
15 }
16`;
17​
18const CALLBACK_REGISTRATION = `
19 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && Build.VERSION.SDK_INT < Build.VERSION_CODES.BAKLAVA) {
20 onBackPressedDispatcher.addCallback(this, predictiveBackCompatCallback)
21 }`;
22​
23// Disable the compat callback during default back handling to prevent dispatcher recursion.
24const INVOKE_DEFAULT_WRAPPER = `override fun invokeDefaultOnBackPressed() {
25 predictiveBackCompatCallback.isEnabled = false
26 try {
27 invokeDefaultOnBackPressedLegacy()
28 } finally {
29 predictiveBackCompatCallback.isEnabled = true
30 }
31 }
32​
33 private fun invokeDefaultOnBackPressedLegacy() {`;
34​
35function insertAfter(contents, anchor, insertion, description) {
36 const index = contents.indexOf(anchor);
37 if (index === -1) {
38 throw new Error(
39 `withAndroidPredictiveBackCompat: could not find ${description} in MainActivity — the Expo template changed; update the plugin anchors.`,
40 );
41 }
42 const end = index + anchor.length;
43 return contents.slice(0, end) + insertion + contents.slice(end);
45​
46module.exports = function withAndroidPredictiveBackCompat(config) {
47 if (config.android?.predictiveBackGestureEnabled !== true) {
48 return config;
49 }
50​
51 return withMainActivity(config, (nextConfig) => {
52 let contents = nextConfig.modResults.contents;
53 if (nextConfig.modResults.language !== "kt") {
54 throw new Error("withAndroidPredictiveBackCompat: MainActivity must be Kotlin.");
55 }
56 if (contents.includes("predictiveBackCompatCallback")) {
57 return nextConfig;
58 }
59​
60 contents = insertAfter(
61 contents,
62 "import android.os.Bundle",
63 "\nimport androidx.activity.OnBackPressedCallback",
64 "the android.os.Bundle import",
65 );
66 contents = insertAfter(
67 contents,
68 "class MainActivity : ReactActivity() {",
69 CALLBACK_PROPERTY,
70 "the MainActivity class declaration",
71 );
72 contents = insertAfter(
73 contents,
74 "super.onCreate(null)",
75 CALLBACK_REGISTRATION,
76 "the super.onCreate call",
77 );
78​
79 if (!contents.includes("override fun invokeDefaultOnBackPressed() {")) {
80 throw new Error(
81 "withAndroidPredictiveBackCompat: could not find invokeDefaultOnBackPressed in MainActivity — the Expo template changed; update the plugin anchors.",
82 );
83 }
84 contents = contents.replace(
85 "override fun invokeDefaultOnBackPressed() {",
86 INVOKE_DEFAULT_WRAPPER,
87 );
88​
89 nextConfig.modResults.contents = contents;
90 return nextConfig;
91 });
92};