Complete guide for building, installing, and deploying separate Service Agent and Employee Agent apps from a single codebase
- Overview
- App Types
- Quick Start
- Installation
- Building & Running
- Project Structure
- How It Works
- Benefits
- Troubleshooting
- FAQ
- Implementation Details
This repository supports building two separate apps from a single codebase:
- Service Agent - Customer-facing, anonymous auth, no Mobile SDK
- Employee Agent - Internal workforce, OAuth auth, includes Mobile SDK
Both apps share >98% of code (JavaScript/TypeScript and core native code), with only configuration files differing.
- Purpose: Customer-facing, public service app
- Authentication: Anonymous (URL-based configuration)
- Display Name: "Service Agent"
- Mobile SDK: NOT included
- Purpose: Internal workforce app
- Authentication: OAuth via Salesforce Mobile SDK
- Display Name: "Employee Agent"
- Mobile SDK: Included (SalesforceSDKCore 13.1.1 from CocoaPods)
iOS:
- Xcode 15.0 or later
- CocoaPods
- XcodeGen (
brew install xcodegen) - Node.js >= 18
Android:
- Android Studio or Android SDK
- Gradle
- Node.js >= 18
# Service Agent only (fastest)
node installios.js service
node installandroid.js service
npm run ios:service
npm run android:service
# Employee Agent only
node installios.js employee
node installandroid.js employee
npm run ios:employee
npm run android:employee
# Both apps (backward compatible)
node installios.js all
node installandroid.js allChoose which app to install:
# Service Agent Only (no Mobile SDK)
node installios.js service
node installandroid.js service
# Employee Agent Only (with Mobile SDK)
node installios.js employee
node installandroid.js employee
# Both Apps (backward compatible default)
node installios.js all
node installandroid.js all
# No argument = 'all' (backward compatible)
node installios.js # Same as 'all'
node installandroid.js # Same as 'all'| Target | Mobile SDK | CocoaPods/Gradle |
|---|---|---|
| service | β Not included | Service Agent only |
| employee | β From Maven/CocoaPods | Employee Agent only |
| all | β From Maven/CocoaPods | Both targets |
What each script does:
- Installs npm dependencies (always)
- [employee/all only] Builds react-native-force (Mobile SDK React Native bridge)
- Configures Node.js path for Xcode (always)
- Generates Xcode project with xcodegen (always)
- Installs CocoaPods (only for selected target)
- Installs npm dependencies (always)
- Applies React Native Gradle plugin patches (always)
- [employee/all only] Builds react-native-force (Mobile SDK React Native bridge)
Employee Agent uses Salesforce Mobile SDK from published artifacts:
- iOS: CocoaPods specs (
SalesforceSDKCoreviaReactNativeAgentforce/WithMobileSDK) - Android: Maven Central (
com.salesforce.mobilesdk:SalesforceReact:13.1.1)
Service Agent has no Mobile SDK dependency.
# Service Agent
npm run ios:service
npm run android:service
# Employee Agent
npm run ios:employee
npm run android:employee# Service Agent Release
npm run build:ios:service
npm run build:android:service
# Employee Agent Release
npm run build:ios:employee
npm run build:android:employeeiOS (Xcode):
- Open
ios/ReactAgentforce.xcworkspace(NOT .xcodeproj) - Select scheme: ServiceAgent or EmployeeAgent
- Select target device
- Build and run (Cmd+R)
Android (Gradle):
cd android
# List all available build variants
./gradlew tasks | grep assemble
# Build specific variants
./gradlew assembleServiceAgentDebug
./gradlew assembleServiceAgentRelease
./gradlew assembleEmployeeAgentDebug
./gradlew assembleEmployeeAgentReleaseAndroid APKs:
android/app/build/outputs/apk/
βββ serviceAgent/
β βββ debug/app-serviceAgent-debug.apk
β βββ release/app-serviceAgent-release.apk
βββ employeeAgent/
βββ debug/app-employeeAgent-debug.apk
βββ release/app-employeeAgent-release.apk
iOS IPAs:
- Open Xcode
- Window β Organizer
- Select archive
- Distribute App β Export
ios/
βββ project.yml # XcodeGen configuration
βββ Podfile # Default: loads Podfile.employee (run pod install via installios.js)
βββ Podfile.service # Service Agent only (subset of deps)
βββ Podfile.employee # Employee Agent / both apps (full deps)
βββ Podfile.common.rb # Shared pod list and hooks
βββ Podfile.service.lock # Lock for Service Agent
βββ Podfile.employee.lock # Lock for Employee Agent / both
βββ Shared/ # Shared code (AppDelegate, main.m)
β βββ AppDelegate.{h,m}
β βββ main.m
βββ ServiceAgent/ # Service Agent specific files
β βββ Info.plist # Display name: "Service Agent"
β βββ ServiceAgent.entitlements
β βββ LaunchScreen.storyboard
β βββ PrivacyInfo.xcprivacy
βββ EmployeeAgent/ # Employee Agent specific files
βββ Info.plist # Display name: "Employee Agent"
βββ EmployeeAgent.entitlements
βββ LaunchScreen.storyboard
βββ PrivacyInfo.xcprivacy
android/app/src/
βββ main/ # Shared code
β βββ java/.../app/
β β βββ MainActivity.java
β β βββ MainApplication.java
β βββ res/
β βββ AndroidManifest.xml
βββ serviceAgent/ # Service Agent overrides
β βββ res/values/
β βββ strings.xml # app_name="Service Agent"
βββ employeeAgent/ # Employee Agent overrides
βββ res/values/
βββ strings.xml # app_name="Employee Agent"
All JavaScript/TypeScript code is 100% shared between both apps. No changes needed.
Single Project, Two Targets (XcodeGen + CocoaPods):
This project uses a single Xcode project with two targets approach, which provides:
- Platform consistency: Matches Android's product flavor architecture
- Maximum code reuse: >98% shared code, minimal duplication
- Selective installation: Install service-only or employee with Mobile SDK
- React Native CLI compatibility: Works seamlessly with standard RN tooling
Alternative Considered: Separate projects (one for each app) with independent project.yml/Podfile per app was evaluated but rejected because it would:
- Double disk space and build time (2x Pods directories)
- Break React Native CLI conventions (expects single ios/ directory)
- Create platform inconsistency (Android uses flavors, not separate projects)
- Duplicate most configuration for minimal benefit
Architecture Details:
project.ymldefines two targets (ServiceAgent, EmployeeAgent)xcodegen generatecreates.xcodeprojwith both targets- Two Podfiles:
Podfile.service(Service Agent only, subset) andPodfile.employee(both targets, full deps).installios.jscopies the right one toPodfileand uses the matching lock (Podfile.service.lockorPodfile.employee.lock). "all" uses the employee Podfile so both apps share one dependency set. - Build order fix:
Podfile.common.rbadds explicit Pods target dependencies at the Xcode project level, ensuring Pods build before app targets (fixes "React/RCTBridgeDelegate.h file not found" errors) - ServiceAgent uses
ReactNativeAgentforce/Core(no Mobile SDK) - EmployeeAgent uses
ReactNativeAgentforce/WithMobileSDK(includes Mobile SDK)
Mobile SDK Dependencies:
# EmployeeAgent target
pod 'SalesforceReact', :path => '../node_modules/react-native-force'
pod 'ReactNativeAgentforce/WithMobileSDK', :path => '../AgentforceSDK-ReactNative-Bridge/ios'
# WithMobileSDK subspec brings in SalesforceSDKCore from published specsBuild Order Solution:
The Podfile.common.rb includes an add_pods_target_dependency function that automatically adds explicit dependencies from each app target (ServiceAgent, EmployeeAgent) to its corresponding Pods target (Pods-ServiceAgent, Pods-EmployeeAgent). This ensures CocoaPods builds first, making React Native headers available during app compilation.
Product Flavors + Gradle:
build.gradledefines two flavors:serviceAgent,employeeAgent- Creates 4 build variants (service/employee Γ debug/release)
- ServiceAgent flavor: NO Mobile SDK dependency
- EmployeeAgent flavor: Mobile SDK from Maven Central
Mobile SDK Dependency:
// build.gradle - Employee Agent only
employeeAgentImplementation("com.salesforce.mobilesdk:SalesforceReact:13.1.1")The bridge automatically detects Mobile SDK availability:
Android (AgentforcePackage.kt):
private fun isMobileSdkAvailable(): Boolean {
return try {
Class.forName("com.salesforce.androidsdk.app.SalesforceSDKManager")
true
} catch (e: ClassNotFoundException) {
false
}
}iOS (Subspecs):
Coresubspec: Service Agent only, no Mobile SDKWithMobileSDKsubspec: IncludesSalesforceSDKCoredependency
Employee Agent requires SDK initialization at app startup:
Android (Conditional in Shared MainApplication):
// android/app/src/main/java/.../MainApplication.java
public void onCreate() {
super.onCreate();
// Initialize Mobile SDK for Employee Agent flavor only (uses reflection)
if ("employeeAgent".equals(BuildConfig.FLAVOR)) {
Class<?> sdkManagerClass = Class.forName("com.salesforce.androidsdk.app.SalesforceSDKManager");
Method initMethod = sdkManagerClass.getMethod("initNative", Context.class, Class.class);
initMethod.invoke(null, this, MainActivity.class);
}
// ... rest of initialization
}iOS (Flavor-Specific AppDelegate):
// ios/EmployeeAgent/AppDelegate.m (overrides Shared/AppDelegate.m)
#import <SalesforceReact/SalesforceReactSDKManager.h>
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[SalesforceReactSDKManager initializeSDK];
// ... rest of initialization
}Configuration Files:
- Android:
android/app/src/employeeAgent/res/values/bootconfig.xml - iOS:
ios/EmployeeAgent/bootconfig.plist
These files contain OAuth client configuration for Mobile SDK authentication.
- Both installable side-by-side
- Independent release cycles
- Clear separation of concerns
-
98% shared code
- Single codebase maintenance
- Consistent features across apps
- Install only what you need (service/employee/all)
- Service Agent: No Mobile SDK required
- Employee Agent: Full Mobile SDK from published artifacts
- Service Agent devs don't need to understand Mobile SDK
- Employee Agent devs get full Mobile SDK integration
- Flexible workflow for different development needs
Cause: The app target is not using the CocoaPods-generated xcconfig, or Pods are not building before the app target. This typically happens when:
- You run
xcodegen generateafterpod installβ XcodeGen overwrites the project and removes thebaseConfigurationReferenceto the Pods xcconfig that CocoaPods added. - You open the
.xcodeprojinstead of the.xcworkspaceβ Always openios/ReactAgentforce.xcworkspaceso the Pods project and settings are in scope. - You're building the wrong target β If you ran
node installios.js service, only the ServiceAgent target has Pods linked. Building EmployeeAgent will fail with missing React headers until you runnode installios.js employeeornode installios.js all. - Pods target dependency not set β The app target needs an explicit dependency on its Pods target to ensure correct build order.
Solution:
-
Use the correct install order and do not re-run XcodeGen after Pods are installed:
node installios.js service # or employee, or allThis runs
xcodegen generatethenpod install. CocoaPods then:- Wires each app target to its
Pods-<Target>.(debug|release).xcconfig - Runs the
post_installhook which adds explicit Pods target dependencies
If you need to change
project.yml, run the full install again so that XcodeGen runs first and then CocoaPods re-applies the xcconfig references and dependencies. - Wires each app target to its
-
Always open the workspace:
open ios/ReactAgentforce.xcworkspace
In Xcode, select the ServiceAgent or EmployeeAgent scheme (matching what you installed) and build.
-
If you already ran XcodeGen after pod install, re-run the installer so CocoaPods can re-attach the xcconfig and dependencies:
node installios.js service # or employee / all -
Verify Pods target dependencies: After running
installios.js, check the output for:β Added ServiceAgent -> Pods-ServiceAgent target dependency β Added EmployeeAgent -> Pods-EmployeeAgent target dependencyThis confirms the build order is properly configured.
-
UIKit / CoreGraphics / Foundation βmodule not foundβ β Often fixed by the Podfile
post_installsettings (CLANG_ENABLE_MODULES,CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES). If you still see this, ensure you are building with the workspace and the correct scheme.
Cause: The app target doesn't have an explicit dependency on its Pods target.
Solution: This is now handled automatically by installios.js. The Podfile.common.rb post_install hook adds explicit dependencies from each app target to its Pods target. If you're still seeing issues:
# Clean and reinstall
rm -rf ios/Pods ios/Podfile.lock ios/ReactAgentforce.xcodeproj ios/ReactAgentforce.xcworkspace
node installios.js service # or employee / allβ Invalid target: xyz
Solution: Use service, employee, or all:
node installios.js service # β
CorrectCause: Ran node installios.js service but trying to build Employee Agent
Solution: Install Employee Agent dependencies:
node installios.js employee
node installandroid.js employeeCause: CocoaPods or Gradle cache corruption
Solution: Clean and reinstall:
cd ios && pod deintegrate && pod install
cd android && ./gradlew cleanSolution:
cd ios
xcodegen generate
pod installCause: Ran node installios.js service but Podfile expects Employee target
Solution: This is expected - Service Agent install skips Employee pods:
npm run ios:service # Build Service Agent onlyCause: Corrupted build cache
Solution: Clean and reinstall:
rm -rf android/build android/app/build
node installandroid.js employeeThese warnings are cosmetic and don't affect functionality.
A: Yes! Just run the install script again with the new target:
node installios.js service # Install Service Agent only
# ... do Service Agent work ...
node installios.js employee # Add Employee Agent + Mobile SDKA: No, all JavaScript/TypeScript code is 100% shared between apps. This only affects native dependencies.
A: Yes! Both apps can be installed side-by-side on the same device.
A: Depends on your pipeline:
- Separate pipelines β Use specific targets (
serviceoremployee) - Monolithic pipeline β Use
all
Why Single Project with Two Targets?
This project uses a single Xcode project with two targets approach (not separate projects). This decision was made after careful analysis:
Advantages:
- β Platform consistency: Matches Android's product flavor architecture
- β Maximum code reuse: >98% shared code between targets
- β Single build graph: Pods installed once, shared between targets
- β React Native CLI compatibility: Standard RN tooling expects single ios/ directory
- β Faster builds: Single Pods directory, no duplication
- β Easier maintenance: One project.yml, one set of build settings
Alternative Considered (Rejected): Separate projects (ServiceAgent-ios/, EmployeeAgent-ios/) with independent configurations were evaluated but would introduce:
- β 2x disk space (separate Pods directories)
- β 2x pod install time
- β Platform inconsistency (Android uses flavors, not separate gradle projects)
- β React Native CLI incompatibility (expects single ios/ directory)
- β Configuration duplication (most settings identical)
The Current Issues (Now Fixed):
Build order problems requiring scheme patching workaroundβ Fixed with proper Pods target dependenciesPodfile switching complexityβ Improved with clear logging and validationTwo lock files to maintain separatelyβ Improved with explicit lock file managementLimited validation and unclear error messagesβ Fixed with pre-flight checks and verbose logging
- Multi-target iOS setup (XcodeGen + CocoaPods)
- Product flavors Android setup (Gradle)
- Selective installation (service/employee/all arguments)
- Conditional dependency resolution (flavor-specific dependencies)
- Published artifacts integration (Maven Central & CocoaPods specs)
- Build order fix (Explicit Pods target dependencies via post_install hook)
- Environment validation (Pre-flight checks for required tools)
- Verbose logging (Clear progress indicators and step-by-step feedback)
ios/project.yml- XcodeGen configurationios/ServiceAgent/- Service Agent specific filesios/EmployeeAgent/- Employee Agent specific filesAppDelegate.m- Overrides Shared/AppDelegate.m with Mobile SDK initializationbootconfig.plist- OAuth configuration
ios/Shared/- Shared iOS code (AppDelegate.m used by Service Agent)android/app/src/serviceAgent/- Service Agent flavor (uses shared MainApplication)android/app/src/employeeAgent/- Employee Agent flavorres/values/bootconfig.xml- OAuth configuration (uses shared MainApplication with conditional init)
scripts/generate-app-config.js- App mode configuration generatorscripts/build-react-native-force.js- Builds react-native-force bridge
package.json- Added run scripts, react-native-force dependency, build:force scriptinstallios.js- Added selective installation logic + react-native-force buildinstallandroid.js- Added selective installation logic + react-native-force buildios/Podfile.service- Service Agent only (subset of deps)ios/Podfile.employee- Both targets, full deps (used for employee and "all")ios/Podfile.common.rb- Shared pod list and post_install hooks- Locks:
Podfile.service.lockandPodfile.employee.lock(install script copies the active one toPodfile.lockand back) android/settings.gradle- Simplified project structureandroid/app/build.gradle- Product flavors + conditional depssrc/config/AppConfig.ts- Dynamic app mode configuration.gitignore- Added generated config file
iOS:
- XcodeGen: Makes project structure version-controllable (YAML)
- Two Podfiles + locks: Install script selects Podfile.service or Podfile.employee and the matching lock
- Published specs: Uses SalesforceReact from npm + CocoaPods specs
Android:
- Product flavors: Standard Android approach for app variants
- Flavor-specific dependencies:
employeeAgentImplementationonly for Employee Agent
Both:
- Backward compatible: No argument =
all(same as before) - Idempotent scripts: Can run multiple times safely
- Published artifacts: Uses Maven/CocoaPods for Mobile SDK
| Component | Shared % |
|---|---|
| JavaScript/TypeScript | 100% |
| iOS native (AppDelegate, main) | 100% |
| Android native (MainActivity, Application) | 100% |
| iOS configuration (Info.plist, entitlements) | 0% (app-specific) |
| Android configuration (strings.xml) | 0% (app-specific) |
| Overall | >98% |
| Aspect | Service Agent | Employee Agent |
|---|---|---|
| Mobile SDK | β Not included | β From Maven/CocoaPods |
| Authentication | Anonymous | OAuth |
| Use case | Customer-facing | Internal workforce |
node installios.js service
node installandroid.js servicenode installios.js employee
node installandroid.js employeenode installios.js all
node installandroid.js all
# Default, backward compatibleservice-pipeline:
script:
- node installios.js service
- npm run build:ios:service
employee-pipeline:
script:
- node installios.js employee
- npm run build:ios:employeefull-pipeline:
script:
- node installios.js all
- npm run build:ios:service
- npm run build:ios:employee- Main README:
../README.md- Project overview - Bridge Documentation:
../AgentforceSDK-ReactNative-Bridge/README.md - Contributing:
../CONTRIBUTING.md
This guide covered:
- β Installing Service Agent and Employee Agent separately
- β Building and running both apps
- β Understanding the multi-app architecture
- β Troubleshooting common issues
- β Optimizing for time and disk space
Choose the right install target and save time! π
Last Updated: March 3, 2026