Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 46 additions & 18 deletions docs/salesforcereact/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -804,32 +804,60 @@ public void onSuccess(RestRequest request, RestResponse response) {
3. **Async operations**: All I/O is asynchronous to prevent blocking
4. **Connection pooling**: RestClient reuses OkHttp connections

## Future Architecture Considerations
## New Architecture (TurboModules)

### New Architecture Migration
As of Mobile SDK 14.0, all Android bridge modules implement React Native's TurboModule interface for improved performance and type safety.

React Native's "New Architecture" introduces:
- **TurboModules**: Lazy-loaded native modules with type safety
- **Fabric**: New rendering system
- **JSI**: JavaScript Interface for direct JS ↔ Native communication
### Implementation

Migration would involve:
1. Converting `ReactContextBaseJavaModule` to `TurboModule`
2. Defining TypeScript specs for type safety
3. Replacing callback pattern with promises/async-await
4. Direct object passing instead of string serialization
All bridge classes:
- Extend `ReactContextBaseJavaModule` (backward compat)
- Implement `TurboModule` interface (new architecture)
- Written in Kotlin
- Use unified single-callback pattern matching iOS

### Codegen Specifications
```kotlin
class SalesforceOauthReactBridge(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext), TurboModule {

Example TurboModule spec:
```typescript
export interface Spec extends TurboModule {
authenticate(): Promise<Credentials>;
getAuthCredentials(): Promise<Credentials>;
logout(): Promise<void>;
@ReactMethod
fun getAuthCredentials(args: ReadableMap, callback: Callback) {
// callback(null, result) for success
// callback(errorMessage) for error
}
}
```

### Callback Pattern

Both iOS and Android now use a unified single-callback pattern:
- **Success**: `callback.invoke(null, resultString)`
- **Error**: `callback.invoke(errorMessage)`

The JavaScript `exec()` function handles both platforms identically:
```typescript
module[methodName](args, (error, result) => {
if (error) errorCB(safeJSONparse(error));
else successCB(safeJSONparse(result));
});
```

### TypeScript Codegen Specs

TurboModule specs are defined in `react-native-force/src/specs/`:
- `NativeSFOauthReactBridge.ts`
- `NativeSFNetReactBridge.ts`
- `NativeSFSmartStoreReactBridge.ts`
- `NativeSFMobileSyncReactBridge.ts`

These specs enable React Native's Codegen to generate type-safe bindings.

## Future Architecture Considerations

### React Native Upgrade

Before the 14.0 release, an upgrade to a React Native version supporting AGP 9+ is planned. This will eliminate the AGP compatibility patching currently done in template `installandroid.js` scripts.

## Summary

The SalesforceReact architecture provides a robust bridge between React Native and native Salesforce SDK functionality through:
Expand Down
6 changes: 5 additions & 1 deletion libs/SalesforceReact/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ plugins {
id("org.jetbrains.dokka")
}

kotlin {
jvmToolchain(17)
}

dependencies {
api(project(":libs:MobileSync"))
api(libs.react.android) // TODO: This update should happen in a dedicated work item. ECJ20260423
api(libs.react.android)
implementation(libs.androidx.core.ktx)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.rules)
Expand Down
2 changes: 1 addition & 1 deletion libs/SalesforceReact/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"jsc-android": "^250231.0.0",
"react": "19.1.0",
"react-native": "0.81.5",
"react-native-force": "git+https://github.com/forcedotcom/SalesforceMobileSDK-ReactNative.git#dev"
"react-native-force": "git+https://github.com/wmathurin/SalesforceMobileSDK-ReactNative.git#rn-migration"
},
"devDependencies": {
"@babel/core": "^7.25.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2024-present, salesforce.com, inc.
* All rights reserved.
* Redistribution and use of this software in source and binary forms, with or
* without modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of salesforce.com, inc. nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission of salesforce.com, inc.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.salesforce.androidsdk.reactnative.app

import com.facebook.react.BaseReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.uimanager.ViewManager
import com.salesforce.androidsdk.reactnative.bridge.MobileSyncReactBridge
import com.salesforce.androidsdk.reactnative.bridge.SalesforceNetReactBridge
import com.salesforce.androidsdk.reactnative.bridge.SalesforceOauthReactBridge
import com.salesforce.androidsdk.reactnative.bridge.SmartStoreReactBridge

class SalesforceReactPackage : BaseReactPackage() {

override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return when (name) {
"SalesforceOauthReactBridge" -> SalesforceOauthReactBridge(reactContext)
"SalesforceNetReactBridge" -> SalesforceNetReactBridge(reactContext)
"SmartStoreReactBridge" -> SmartStoreReactBridge(reactContext)
"MobileSyncReactBridge" -> MobileSyncReactBridge(reactContext)
else -> null
}
}

override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
return ReactModuleInfoProvider {
mapOf(
"SalesforceOauthReactBridge" to ReactModuleInfo(
"SalesforceOauthReactBridge", "SalesforceOauthReactBridge", false, false, false, true
),
"SalesforceNetReactBridge" to ReactModuleInfo(
"SalesforceNetReactBridge", "SalesforceNetReactBridge", false, false, false, true
),
"SmartStoreReactBridge" to ReactModuleInfo(
"SmartStoreReactBridge", "SmartStoreReactBridge", false, false, false, true
),
"MobileSyncReactBridge" to ReactModuleInfo(
"MobileSyncReactBridge", "MobileSyncReactBridge", false, false, false, true
),
)
}
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,12 @@
import androidx.annotation.VisibleForTesting;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.salesforce.androidsdk.mobilesync.app.MobileSyncSDKManager;
import com.salesforce.androidsdk.reactnative.bridge.MobileSyncReactBridge;
import com.salesforce.androidsdk.reactnative.bridge.SalesforceNetReactBridge;
import com.salesforce.androidsdk.reactnative.bridge.SalesforceOauthReactBridge;
import com.salesforce.androidsdk.reactnative.bridge.SmartStoreReactBridge;
import com.salesforce.androidsdk.reactnative.ui.SalesforceReactActivity;
import com.salesforce.androidsdk.ui.LoginActivity;
import com.salesforce.androidsdk.util.EventsObservable;
import com.salesforce.androidsdk.util.EventsObservable.EventType;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -136,34 +126,7 @@ public String getAppType() {
* @return ReactPackage for this application
*/
public ReactPackage getReactPackage() {
return new ReactPackage() {

@NonNull
@Override
public List<NativeModule> createNativeModules(
@NonNull ReactApplicationContext reactContext
) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new SalesforceOauthReactBridge(reactContext));
modules.add(new SalesforceNetReactBridge(reactContext));
modules.add(new SmartStoreReactBridge(reactContext));
modules.add(new MobileSyncReactBridge(reactContext));
return modules;
}

/** @noinspection unused*/
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}

@NonNull
@Override
public List<ViewManager> createViewManagers(
@NonNull ReactApplicationContext reactContext
) {
return Collections.emptyList();
}
};
return new SalesforceReactPackage();
}

@NonNull
Expand Down
Loading
Loading