diff --git a/README.md b/README.md index 664ecc4..f51f35b 100644 --- a/README.md +++ b/README.md @@ -93,10 +93,11 @@ ReactNativeBrownfield.setNativeBackGestureAndButtonEnabled(true); **popToNative(animated[iOS only]: boolean)** -A method to pop to native screen used to push React Native experience. +A method to pop to back to the native screen. +Can also pass back data through the second param. ```js -ReactNativeBrownfield.popToNative(true); +ReactNativeBrownfield.popToNative(true, { result: '👋' }); ``` > NOTE: Those methods works only with native components provided by this library. diff --git a/android/src/newarch/ReactNativeBrownfieldModule.kt b/android/src/newarch/ReactNativeBrownfieldModule.kt index 0048a85..bb3d1cc 100644 --- a/android/src/newarch/ReactNativeBrownfieldModule.kt +++ b/android/src/newarch/ReactNativeBrownfieldModule.kt @@ -1,7 +1,12 @@ package com.callstack.reactnativebrownfield +import android.app.Activity; +import android.content.Intent + +import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableMap class ReactNativeBrownfieldModule(reactContext: ReactApplicationContext) : NativeReactNativeBrownfieldModuleSpec(reactContext) { @@ -10,7 +15,15 @@ class ReactNativeBrownfieldModule(reactContext: ReactApplicationContext) : } @ReactMethod - override fun popToNative(animated: Boolean) { + override fun popToNative(animated: Boolean, result: ReadableMap?) { + if (result != null) { + val bundle = Arguments.toBundle(result) + if (bundle != null) { + val intent = Intent() + intent.putExtras(bundle) + reactApplicationContext.currentActivity?.setResult(Activity.RESULT_OK, intent) + } + } shouldPopToNative = true onBackPressed() } diff --git a/android/src/oldarch/ReactNativeBrownfieldModule.kt b/android/src/oldarch/ReactNativeBrownfieldModule.kt index cbb7c9a..45b07ff 100644 --- a/android/src/oldarch/ReactNativeBrownfieldModule.kt +++ b/android/src/oldarch/ReactNativeBrownfieldModule.kt @@ -1,8 +1,13 @@ package com.callstack.reactnativebrownfield +import android.app.Activity; +import android.content.Intent + +import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.ReadableMap class ReactNativeBrownfieldModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { @@ -11,7 +16,15 @@ class ReactNativeBrownfieldModule(reactContext: ReactApplicationContext) : } @ReactMethod - fun popToNative(animated: Boolean) { + fun popToNative(animated: Boolean, result: ReadableMap?) { + if (result != null) { + val bundle = Arguments.toBundle(result) + if (bundle != null) { + val intent = Intent() + intent.putExtras(bundle) + reactApplicationContext.currentActivity?.setResult(Activity.RESULT_OK, intent) + } + } shouldPopToNative = true onBackPressed() } diff --git a/example/index.tsx b/example/index.tsx index c438864..4ec8fe0 100644 --- a/example/index.tsx +++ b/example/index.tsx @@ -56,7 +56,9 @@ function HomeScreen({ navigation, route }: Props) { if (navigation.canGoBack()) { navigation.goBack(); } else { - ReactNativeBrownfield.popToNative(true); + ReactNativeBrownfield.popToNative(true, { + result: `Hello World ${Math.random()}`, + }); } }} color={colors.secondary} diff --git a/example/kotlin/app/src/main/java/com/callstack/kotlinexample/MainActivity.kt b/example/kotlin/app/src/main/java/com/callstack/kotlinexample/MainActivity.kt index 0ffe9cc..38952e7 100644 --- a/example/kotlin/app/src/main/java/com/callstack/kotlinexample/MainActivity.kt +++ b/example/kotlin/app/src/main/java/com/callstack/kotlinexample/MainActivity.kt @@ -1,10 +1,15 @@ package com.callstack.kotlinexample +import android.app.Activity import android.content.Intent import android.os.Bundle +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.FragmentActivity +import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.* import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme @@ -15,6 +20,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView @@ -76,6 +82,24 @@ fun HomeScreen( onStartReactNativeFragment: () -> Unit, onStartReactNativeFragmentActivity: () -> Unit ) { + val context = LocalContext.current + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult(), + onResult = { result -> + val message = if (result.resultCode == Activity.RESULT_OK) { + "Result: ${result.data?.getExtras()?.get("result")}" + } else { + "Activity cancelled or failed. ${result.resultCode}" + } + AlertDialog.Builder(context) + .setTitle("Activity Result") + .setMessage(message) + .setPositiveButton("OK") { dialog, _ -> + dialog.dismiss() + } + .show() + } + ) Column( modifier = Modifier .fillMaxSize() @@ -106,6 +130,17 @@ fun HomeScreen( ) { Text("Navigate to React Native Fragment Activity") } + + Button( + onClick = { + val intent = Intent(Intent.ACTION_GET_CONTENT).apply { + setClass(context, ReactNativeFragmentActivity::class.java) + } + launcher.launch(intent) + } + ) { + Text("Navigate to React Native Fragment Activity with Result") + } } } diff --git a/example/swift/App.swift b/example/swift/App.swift index 4a76237..7a7ebcf 100644 --- a/example/swift/App.swift +++ b/example/swift/App.swift @@ -8,7 +8,7 @@ struct MyApp: App { print("loaded") } } - + var body: some Scene { WindowGroup { ContentView() @@ -16,7 +16,31 @@ struct MyApp: App { } } +class NotificationHandler: ObservableObject{ + init() { + NotificationCenter.default.addObserver( + self, + selector: #selector(handlePopToNative(_:)), + name: NSNotification.Name.popToNative, + object: nil + ) + } + + @objc func handlePopToNative(_ notification: Notification) { + if let result = notification.userInfo?["result"] as? Any { + print("Received popToNative notification with result: \(result)") + } else { + print("Received popToNative notification without result") + } + } + + deinit { + NotificationCenter.default.removeObserver(self) + } +} + struct ContentView: View { + @StateObject var notificationHandler = NotificationHandler() var body: some View { NavigationView { VStack { @@ -24,7 +48,7 @@ struct ContentView: View { .font(.title) .bold() .padding() - + NavigationLink("Push React Native Screen") { ReactNativeView(moduleName: "ReactNative") .navigationBarHidden(true) diff --git a/ios/ReactNativeBrownfieldModule.mm b/ios/ReactNativeBrownfieldModule.mm index 336f98b..580f88c 100644 --- a/ios/ReactNativeBrownfieldModule.mm +++ b/ios/ReactNativeBrownfieldModule.mm @@ -14,8 +14,8 @@ @implementation ReactNativeBrownfieldModule [ReactNativeBrownfieldModuleImpl setPopGestureRecognizerEnabled:enabled]; } -RCT_EXPORT_METHOD(popToNative:(BOOL)animated) { - [ReactNativeBrownfieldModuleImpl popToNativeWithAnimated:animated]; +RCT_EXPORT_METHOD(popToNative:(BOOL)animated result:(NSDictionary *)result) { + [ReactNativeBrownfieldModuleImpl popToNativeWithAnimated:animated result:result]; } - (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { diff --git a/ios/ReactNativeBrownfieldModule.swift b/ios/ReactNativeBrownfieldModule.swift index d783cb9..c161578 100644 --- a/ios/ReactNativeBrownfieldModule.swift +++ b/ios/ReactNativeBrownfieldModule.swift @@ -9,8 +9,11 @@ public class ReactNativeBrownfieldModuleImpl: NSObject { } } - static public func popToNative(animated: Bool) { - let userInfo = ["animated": animated] + static public func popToNative(animated: Bool, result: [String: Any]?) { + var userInfo: [String : Any] = ["animated": animated] + if result != nil { + userInfo["result"] = result + } DispatchQueue.main.async { NotificationCenter.default.post(name: Notification.Name.popToNative, object: nil, userInfo: userInfo) } diff --git a/package.json b/package.json index 748929d..fdb9911 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "license": "MIT", "author": "Michal Chudziak ", "contributors": [ - "Piotr Drapich " + "Piotr Drapich ", + "Samuel Wall " ], "homepage": "https://github.com/callstack/react-native-brownfield", "description": "Brownfield helpers for React Native", diff --git a/src/NativeReactNativeBrownfieldModule.ts b/src/NativeReactNativeBrownfieldModule.ts index 2852044..e8ddfe7 100644 --- a/src/NativeReactNativeBrownfieldModule.ts +++ b/src/NativeReactNativeBrownfieldModule.ts @@ -1,11 +1,17 @@ import type { TurboModule } from 'react-native'; import { TurboModuleRegistry } from 'react-native'; +export type Primitive = string | number | boolean | bigint; + +export type PrimitiveObject = { + [key: string]: Primitive | PrimitiveObject | Primitive[] | PrimitiveObject[]; +}; + export interface Spec extends TurboModule { /** * Navigate back to the native part of the application. */ - popToNative(animated: boolean): void; + popToNative(animated: boolean, result?: PrimitiveObject): void; /** * Enable or disable the iOS swipe back gesture. diff --git a/src/index.ts b/src/index.ts index 136e60a..a862fab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,14 @@ import { Platform } from 'react-native'; -import ReactNativeBrownfieldModule from './NativeReactNativeBrownfieldModule'; +import ReactNativeBrownfieldModule, { + type PrimitiveObject, +} from './NativeReactNativeBrownfieldModule'; const ReactNativeBrownfield = { - popToNative: (animated?: boolean): void => { + popToNative: (animated?: boolean, result?: PrimitiveObject): void => { if (Platform.OS === 'ios') { - ReactNativeBrownfieldModule.popToNative(!!animated); + ReactNativeBrownfieldModule.popToNative(!!animated, result); } else if (Platform.OS === 'android') { - ReactNativeBrownfieldModule.popToNative(false); + ReactNativeBrownfieldModule.popToNative(false, result); } else { console.warn('Not implemented: popToNative'); }