Skip to content

Commit 584c1bd

Browse files
committed
feat: add multi activity example for rebind
1 parent a74e932 commit 584c1bd

6 files changed

Lines changed: 85 additions & 1 deletion

File tree

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,30 @@ public class SampleTurboModule(private val context: ReactApplicationContext) :
374374
PickUpToMedia.Request(limit, PickVisualMediaRequest(visualMediaType(mimeType))))
375375
}
376376

377+
/**
378+
* Starts a second, distinct Activity in the same task to exercise multi-Activity navigation.
379+
* With two ReactActivities alive, the new one resumes *before* the old one is destroyed, and the
380+
* old one's onHostDestroy is dropped entirely -- the exact ordering that forces
381+
* [com.facebook.react.activityresult.ReactActivityResultCallerImpl] to rebind launchers to the
382+
* current Activity's registry instead of staying attached to the previous (still-alive or dead)
383+
* one. Launched by class name so this sample module needs no compile-time dependency on the app.
384+
*/
385+
@DoNotStrip
386+
@Suppress("unused")
387+
override fun startSecondActivity() {
388+
val activity = context.currentActivity
389+
if (activity == null) {
390+
Toast.makeText(context, "No current Activity to launch from", Toast.LENGTH_LONG).show()
391+
return
392+
}
393+
// The data URI deep-links the new surface straight to the picker example via Linking; the
394+
// explicit class name keeps this an in-app navigation regardless of intent filters.
395+
val intent =
396+
Intent(Intent.ACTION_VIEW, Uri.parse("rntester://example/PhotoPickerAndroid"))
397+
.setClassName(activity, "${activity.packageName}.RNTesterSecondActivity")
398+
activity.startActivity(intent)
399+
}
400+
377401
private fun log(method: String, input: Any?, output: Any?) {
378402
toast?.cancel()
379403
val message = StringBuilder("Method :")

packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export interface Spec extends TurboModule {
7171
mimeType: ?string,
7272
maxItems: number,
7373
) => Promise<Array<string>>;
74+
readonly startSecondActivity?: () => void;
7475
}
7576

7677
export default TurboModuleRegistry.getEnforcing<Spec>(

packages/rn-tester/android/app/src/main/AndroidManifest.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@
8989
</intent-filter>
9090
</activity>
9191

92+
<!-- Second ReactActivity used by the PhotoPickerAndroid example to exercise
93+
multi-Activity navigation with ActivityResultContract launchers: starting it on top
94+
of RNTesterActivity forces the launchers to rebind to this Activity's registry. -->
95+
<activity
96+
android:name=".RNTesterSecondActivity"
97+
android:configChanges="orientation|screenSize|uiMode|fontScale"
98+
android:exported="false"
99+
android:label="@string/app_name"
100+
android:screenOrientation="fullSensor" />
101+
92102
<provider
93103
android:name="com.facebook.react.modules.blob.BlobProvider"
94104
android:authorities="@string/blob_provider_authority"

packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import com.facebook.react.devsupport.DevMenuConfiguration
2222
import java.io.FileDescriptor
2323
import java.io.PrintWriter
2424

25-
internal class RNTesterActivity : ReactActivity() {
25+
internal open class RNTesterActivity : ReactActivity() {
2626
class RNTesterActivityDelegate(val activity: ReactActivity, mainComponentName: String) :
2727
DefaultReactActivityDelegate(activity, mainComponentName) {
2828
private val PARAM_ROUTE = "route"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.uiapp
9+
10+
/**
11+
* A second ReactActivity, distinct from [RNTesterActivity], used to exercise multi-Activity
12+
* navigation against ActivityResultContract launchers registered on the ReactContext (see
13+
* SampleTurboModule.startSecondActivity and com.facebook.react.activityresult).
14+
*
15+
* When this Activity starts on top of [RNTesterActivity], its onHostResume fires while the first
16+
* Activity is still alive, so the launchers must rebind to this Activity's
17+
* ActivityResultRegistry -- a launch from here must dispatch from and deliver its result to *this*
18+
* Activity, not the one the launchers were first bound to.
19+
*/
20+
internal class RNTesterSecondActivity : RNTesterActivity()

packages/rn-tester/js/examples/PhotoPickerAndroid/PhotoPickerAndroid.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,32 @@ const PhotoPickerMultiple = (): React.Node => {
113113
);
114114
};
115115

116+
/**
117+
* Regression check for multi-Activity navigation: the launchers above were
118+
* registered against the ReactContext and first bound to the Activity that
119+
* started the app. Opening the second Activity (which resumes while the first
120+
* one is still alive) must rebind them to the new Activity's registry — the
121+
* pickers on the newly opened screen should open from and deliver their
122+
* results to that screen. Going back and picking again checks rebinding in
123+
* the other direction.
124+
*/
125+
const MultiActivity = (): React.Node => {
126+
return (
127+
<>
128+
<RNTesterText style={styles.uriText}>
129+
Opens this same example in a second Activity. Pick an image there — the
130+
result must arrive on that screen. Then go back and pick here again.
131+
</RNTesterText>
132+
<View style={styles.row}>
133+
<PickerButton
134+
label="Open in a second Activity"
135+
onPress={() => getNativeSampleTurboModule().startSecondActivity?.()}
136+
/>
137+
</View>
138+
</>
139+
);
140+
};
141+
116142
function PickerButton(props: {label: string, onPress: () => unknown}) {
117143
return (
118144
<TouchableOpacity onPress={props.onPress} style={styles.buttonContainer}>
@@ -135,6 +161,9 @@ class PhotoPickerAndroidExample extends React.Component<{}, {}> {
135161
<RNTesterBlock title="Multi select (JS-controlled limit)">
136162
<PhotoPickerMultiple />
137163
</RNTesterBlock>
164+
<RNTesterBlock title="Multi-Activity navigation">
165+
<MultiActivity />
166+
</RNTesterBlock>
138167
</>
139168
)}
140169
</RNTesterPage>

0 commit comments

Comments
 (0)