React Native Integration Examples

Production-ready examples showing how to combine multiple Agentforce SDK features in real-world scenarios.

These examples demonstrate integrating multiple features together. For single-feature examples, see:

Using All Delegates Together

This example shows how to register logger, navigation, and view provider delegates in a single component with proper cleanup.

import React, { useEffect, useRef } from "react";
import { View, Button, Linking, Alert, StyleSheet } from "react-native";
import {
  AgentforceService,
  LogLevel,
  NavigationRequest,
} from "@salesforce/react-native-agentforce";

export default function AllDelegatesExample() {
  const logBufferRef = useRef<Array<{ level: LogLevel; message: string; time: Date }>>([]);

  useEffect(() => {
    // 1. Logger Delegate
    AgentforceService.setLoggerDelegate({
      onLog(level, message, error) {
        logBufferRef.current.push({ level, message, time: new Date() });
        // Keep buffer manageable
        if (logBufferRef.current.length > 500) {
          logBufferRef.current = logBufferRef.current.slice(-250);
        }

        // Forward errors to your analytics
        if (level === "error") {
          console.error(`[Agentforce ERROR] ${message}`, error);
        }
      },
    });

    // 2. Navigation Delegate
    AgentforceService.setNavigationDelegate({
      onNavigate(request: NavigationRequest) {
        console.log("Navigation request:", JSON.stringify(request));

        switch (request.type) {
          case "link":
            if (request.uri) {
              Linking.openURL(request.uri as string).catch((err) =>
                console.error("Failed to open URL:", err),
              );
            }
            break;

          case "record":
            Alert.alert(
              "Record Navigation",
              `Open ${request.objectType} record: ${request.recordId}`,
            );
            break;

          case "quickAction":
            Alert.alert("Quick Action", `Execute action: ${request.actionName}`);
            break;

          case "objectHome":
            Alert.alert("Object Home", `Navigate to ${request.objectType} list`);
            break;

          default:
            console.log("Unhandled navigation type:", request.type);
        }
      },
    });

    // 3. View Provider Delegate (async)
    AgentforceService.setViewProviderDelegate({
      componentMap: {
        "copilot/richText": "CustomAgentforceView",
      },
    }).catch((err) => console.error("View provider registration failed:", err));

    // Cleanup on unmount
    return () => {
      AgentforceService.clearLoggerDelegate();
      AgentforceService.clearNavigationDelegate();
      AgentforceService.clearViewProviderDelegate();
    };
  }, []);

  const handleConfigure = async () => {
    try {
      await AgentforceService.configure({
        type: "service",
        serviceApiURL: "https://mycompany-support.my.salesforce-scrt.com",
        organizationId: "00Dxx0000001234EAA",
        esDeveloperName: "My_Service_Agent",
        featureFlags: {
          enableMultiAgent: true,
          enableMultiModalInput: false,
          enablePDFUpload: false,
          enableVoice: false,
          enableCustomViewProvider: true,
        },
      });
      Alert.alert("Success", "SDK configured");
    } catch (error) {
      Alert.alert("Config Error", String(error));
    }
  };

  const handleLaunch = async () => {
    try {
      await AgentforceService.launchConversation();
    } catch (error) {
      Alert.alert("Launch Error", String(error));
    }
  };

  const handleShowLogs = () => {
    const recent = logBufferRef.current.slice(-10);
    const text = recent.map((l) => `[${l.level}] ${l.message}`).join("\n");
    Alert.alert(`Last ${recent.length} Logs`, text || "No logs yet");
  };

  return (
    <View style={styles.container}>
      <Button title="1. Configure" onPress={handleConfigure} />
      <View style={styles.spacer} />
      <Button title="2. Launch Conversation" onPress={handleLaunch} />
      <View style={styles.spacer} />
      <Button title="Show Recent Logs" onPress={handleShowLogs} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", padding: 24 },
  spacer: { height: 16 },
});

Feature Flag Management

This example shows a complete settings screen for managing feature flags with UI toggles.

import React, { useState, useEffect } from "react";
import { View, Text, Switch, Button, Alert, StyleSheet } from "react-native";
import { AgentforceService, FeatureFlags } from "@salesforce/react-native-agentforce";

const DEFAULT_FLAGS: FeatureFlags = {
  enableMultiAgent: true,
  enableMultiModalInput: false,
  enablePDFUpload: false,
  enableVoice: false,
  enableCustomViewProvider: false,
};

export default function FeatureFlagManager() {
  const [flags, setFlags] = useState<FeatureFlags>(DEFAULT_FLAGS);
  const [dirty, setDirty] = useState(false);

  useEffect(() => {
    loadFlags();
  }, []);

  const loadFlags = async () => {
    const stored = await AgentforceService.getFeatureFlags();
    setFlags(stored);
    setDirty(false);
  };

  const toggleFlag = (key: keyof FeatureFlags) => {
    setFlags((prev) => ({ ...prev, [key]: !prev[key] }));
    setDirty(true);
  };

  const handleSave = async () => {
    await AgentforceService.setFeatureFlags(flags);
    setDirty(false);
    Alert.alert(
      "Flags Saved",
      "Feature flags have been saved. They will take effect the next time configure() is called.",
    );
  };

  const handleSaveAndReconfigure = async () => {
    try {
      await AgentforceService.setFeatureFlags(flags);

      const info = await AgentforceService.getConfigurationInfo();
      if (info.configured && info.mode === "service") {
        const config = await AgentforceService.getConfiguration();
        if (config) {
          await AgentforceService.configure({
            ...config,
            featureFlags: flags,
          });
          Alert.alert("Applied", "Feature flags saved and applied immediately.");
        }
      } else if (info.configured && info.mode === "employee") {
        Alert.alert("Saved", "Feature flags saved. Restart the conversation to apply.");
      } else {
        Alert.alert("Saved", "Flags saved. Configure the SDK to apply them.");
      }
      setDirty(false);
    } catch (error) {
      Alert.alert("Error", String(error));
    }
  };

  const flagEntries: Array<{ key: keyof FeatureFlags; label: string; description: string }> = [
    {
      key: "enableMultiAgent",
      label: "Multi-Agent",
      description: "Allow SDK to pick from available agents when no agentId is set",
    },
    {
      key: "enableMultiModalInput",
      label: "Multi-Modal Input",
      description: "Enable camera and image attachments",
    },
    {
      key: "enablePDFUpload",
      label: "PDF Upload",
      description: "Enable PDF file upload in conversations",
    },
    {
      key: "enableVoice",
      label: "Voice Input",
      description: "Enable microphone for voice input",
    },
    {
      key: "enableCustomViewProvider",
      label: "Custom View Provider",
      description: "Enable custom React Native views for SDK output",
    },
  ];

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Feature Flags</Text>

      {flagEntries.map(({ key, label, description }) => (
        <View key={key} style={styles.flagRow}>
          <View style={styles.flagInfo}>
            <Text style={styles.flagLabel}>{label}</Text>
            <Text style={styles.flagDesc}>{description}</Text>
          </View>
          <Switch value={flags[key]} onValueChange={() => toggleFlag(key)} />
        </View>
      ))}

      {dirty && (
        <View style={styles.actions}>
          <Button title="Save (Apply on Next Configure)" onPress={handleSave} />
          <View style={styles.spacer} />
          <Button title="Save and Apply Now" onPress={handleSaveAndReconfigure} />
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16 },
  title: { fontSize: 22, fontWeight: "bold", marginBottom: 16 },
  flagRow: {
    flexDirection: "row",
    alignItems: "center",
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: "#eee",
  },
  flagInfo: { flex: 1 },
  flagLabel: { fontSize: 16, fontWeight: "600" },
  flagDesc: { fontSize: 12, color: "#666", marginTop: 2 },
  actions: { marginTop: 24 },
  spacer: { height: 12 },
});

Employee Agent with Mobile SDK Auth

Full Employee Agent integration using the Salesforce Mobile SDK for OAuth authentication, with state management for login/logout flows.

import React, { useState, useEffect } from "react";
import { View, Button, Text, Alert, ActivityIndicator, StyleSheet } from "react-native";
import {
  AgentforceService,
  isEmployeeAgentAuthSupported,
  hasEmployeeAgentSession,
  loginForEmployeeAgent,
  logoutEmployeeAgent,
  getEmployeeAgentCredentials,
  AuthCredentials,
} from "@salesforce/react-native-agentforce";

const AGENT_ID = "0Xxxx0000001234AAA";

export default function EmployeeAgentWithAuth() {
  const [authSupported, setAuthSupported] = useState(false);
  const [loggedIn, setLoggedIn] = useState(false);
  const [loading, setLoading] = useState(true);
  const [credentials, setCredentials] = useState<AuthCredentials | null>(null);

  useEffect(() => {
    checkAuthState();
  }, []);

  const checkAuthState = async () => {
    setLoading(true);
    try {
      const supported = await isEmployeeAgentAuthSupported();
      setAuthSupported(supported);

      if (supported) {
        const session = await hasEmployeeAgentSession();
        setLoggedIn(session);
        if (session) {
          const creds = await getEmployeeAgentCredentials();
          setCredentials(creds);
        }
      }
    } catch (error) {
      console.error("Auth check failed:", error);
    } finally {
      setLoading(false);
    }
  };

  const handleLogin = async () => {
    try {
      setLoading(true);
      const creds = await loginForEmployeeAgent();
      setCredentials(creds);
      setLoggedIn(true);
    } catch (error) {
      Alert.alert("Login Failed", String(error));
    } finally {
      setLoading(false);
    }
  };

  const handleLogout = async () => {
    try {
      await AgentforceService.closeConversation();
      await logoutEmployeeAgent();
      await AgentforceService.resetSettings();
      setCredentials(null);
      setLoggedIn(false);
    } catch (error) {
      Alert.alert("Logout Failed", String(error));
    }
  };

  const handleLaunch = async () => {
    if (!credentials) {
      Alert.alert("Error", "Not logged in");
      return;
    }

    try {
      await AgentforceService.configure({
        type: "employee",
        instanceUrl: credentials.instanceUrl,
        organizationId: credentials.organizationId,
        userId: credentials.userId,
        agentId: AGENT_ID,
        accessToken: credentials.accessToken,
      });

      await AgentforceService.launchConversation();
    } catch (error) {
      Alert.alert("Launch Failed", String(error));
    }
  };

  if (loading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  if (!authSupported) {
    return (
      <View style={styles.center}>
        <Text>Employee Agent auth is not available in this build.</Text>
        <Text style={styles.hint}>Include the Mobile SDK to enable it.</Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Employee Agent</Text>

      {loggedIn ? (
        <>
          <Text style={styles.status}>Logged in as: {credentials?.userId}</Text>
          <Text style={styles.status}>Org: {credentials?.organizationId}</Text>
          <View style={styles.buttonRow}>
            <Button title="Launch Agent" onPress={handleLaunch} />
            <Button title="Logout" onPress={handleLogout} color="red" />
          </View>
        </>
      ) : (
        <>
          <Text style={styles.status}>Not logged in</Text>
          <Button title="Login with Salesforce" onPress={handleLogin} />
        </>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  center: { flex: 1, justifyContent: "center", alignItems: "center" },
  container: { flex: 1, padding: 24, justifyContent: "center" },
  title: { fontSize: 24, fontWeight: "bold", marginBottom: 16 },
  status: { fontSize: 14, color: "#666", marginBottom: 8 },
  hint: { fontSize: 12, color: "#999", marginTop: 8 },
  buttonRow: { flexDirection: "row", gap: 16, marginTop: 16 },
});

Complete HomeScreen Integration

A production-ready example combining Service Agent, Employee Agent, all delegates, context variables, and settings management in a single screen.

import React, { useState, useEffect, useRef } from "react";
import {
  View,
  Text,
  Button,
  Alert,
  ScrollView,
  Linking,
  StyleSheet,
  AppRegistry,
} from "react-native";
import {
  AgentforceService,
  isEmployeeAgentAuthSupported,
  hasEmployeeAgentSession,
  loginForEmployeeAgent,
  logoutEmployeeAgent,
  getEmployeeAgentCredentials,
  AuthCredentials,
  FeatureFlags,
  LogLevel,
  NavigationRequest,
  ViewProviderComponentData,
} from "@salesforce/react-native-agentforce";

// Custom View Component
function CustomAgentforceView({ componentData }: { componentData: ViewProviderComponentData }) {
  return (
    <View style={{ padding: 8, backgroundColor: "#e8f0fe", borderRadius: 6, margin: 4 }}>
      <Text style={{ fontWeight: "bold" }}>[Custom] {componentData.definition}</Text>
      <Text>{JSON.stringify(componentData.properties, null, 2)}</Text>
    </View>
  );
}

AppRegistry.registerComponent("CustomAgentforceView", () => CustomAgentforceView);

// Config
const SERVICE_CONFIG = {
  type: "service" as const,
  serviceApiURL: "https://mycompany-support.my.salesforce-scrt.com",
  organizationId: "00Dxx0000001234EAA",
  esDeveloperName: "My_Service_Agent",
};

export default function HomeScreen() {
  // State
  const [configured, setConfigured] = useState(false);
  const [mode, setMode] = useState<"service" | "employee" | null>(null);
  const [authSupported, setAuthSupported] = useState(false);
  const [loggedIn, setLoggedIn] = useState(false);
  const [credentials, setCredentials] = useState<AuthCredentials | null>(null);
  const [flags, setFlags] = useState<FeatureFlags | null>(null);
  const logCount = useRef(0);

  // Initialize
  useEffect(() => {
    initializeApp();
    return () => {
      AgentforceService.clearLoggerDelegate();
      AgentforceService.clearNavigationDelegate();
    };
  }, []);

  const initializeApp = async () => {
    // Set up delegates first
    setupDelegates();

    // Check current state
    const info = await AgentforceService.getConfigurationInfo();
    setConfigured(info.configured);
    setMode(info.mode);

    const storedFlags = await AgentforceService.getFeatureFlags();
    setFlags(storedFlags);

    // Check Employee Agent auth
    const supported = await isEmployeeAgentAuthSupported();
    setAuthSupported(supported);
    if (supported) {
      const session = await hasEmployeeAgentSession();
      setLoggedIn(session);
      if (session) {
        setCredentials(await getEmployeeAgentCredentials());
      }
    }
  };

  const setupDelegates = () => {
    // Logger
    AgentforceService.setLoggerDelegate({
      onLog(level: LogLevel, message: string, error?: string) {
        logCount.current++;
        if (level === "error") {
          console.error(`[SDK ERROR] ${message}`, error || "");
        }
      },
    });

    // Navigation
    AgentforceService.setNavigationDelegate({
      onNavigate(request: NavigationRequest) {
        switch (request.type) {
          case "link":
            if (request.uri) Linking.openURL(request.uri as string);
            break;
          case "record":
            Alert.alert("Record", `${request.objectType}: ${request.recordId}`);
            break;
          default:
            console.log("Navigation:", request.type, request);
        }
      },
    });

    // View Provider
    AgentforceService.setViewProviderDelegate({
      componentMap: {
        "copilot/richText": "CustomAgentforceView",
      },
    }).catch(console.error);
  };

  // Service Agent
  const handleConfigureService = async () => {
    try {
      await AgentforceService.configure({
        ...SERVICE_CONFIG,
        featureFlags: flags || undefined,
      });
      setConfigured(true);
      setMode("service");
      Alert.alert("Configured", "Service Agent ready");
    } catch (error) {
      Alert.alert("Error", String(error));
    }
  };

  // Employee Agent
  const handleLogin = async () => {
    try {
      const creds = await loginForEmployeeAgent();
      setCredentials(creds);
      setLoggedIn(true);
    } catch (error) {
      Alert.alert("Login Failed", String(error));
    }
  };

  const handleConfigureEmployee = async () => {
    if (!credentials) {
      Alert.alert("Error", "Login first");
      return;
    }
    try {
      const agentId = await AgentforceService.getEmployeeAgentId();
      await AgentforceService.configure({
        type: "employee",
        instanceUrl: credentials.instanceUrl,
        organizationId: credentials.organizationId,
        userId: credentials.userId,
        agentId: agentId || undefined,
        accessToken: credentials.accessToken,
        featureFlags: flags || undefined,
      });
      setConfigured(true);
      setMode("employee");
      Alert.alert("Configured", "Employee Agent ready");
    } catch (error) {
      Alert.alert("Error", String(error));
    }
  };

  // Conversations
  const handleLaunch = async () => {
    try {
      await AgentforceService.launchConversation();
    } catch (error) {
      Alert.alert("Launch Error", String(error));
    }
  };

  const handleNewConversation = async () => {
    try {
      await AgentforceService.startNewConversation();
    } catch (error) {
      Alert.alert("Error", String(error));
    }
  };

  const handleSetContext = async () => {
    try {
      await AgentforceService.setAdditionalContext({
        variables: [
          { name: "screen", type: "Text", value: "HomeScreen" },
          { name: "timestamp", type: "DateTime", value: new Date().toISOString() },
          { name: "userAgent", type: "Text", value: "react-native-agentforce-example" },
        ],
      });
      Alert.alert("Context Set", "3 variables sent to the agent");
    } catch (error) {
      Alert.alert("Error", String(error));
    }
  };

  // Cleanup
  const handleReset = async () => {
    if (loggedIn) {
      await logoutEmployeeAgent();
    }
    await AgentforceService.resetSettings();
    setConfigured(false);
    setMode(null);
    setLoggedIn(false);
    setCredentials(null);
    Alert.alert("Reset", "All settings cleared");
  };

  return (
    <ScrollView style={styles.container} contentContainerStyle={styles.content}>
      <Text style={styles.title}>Agentforce Demo</Text>

      {/* Status */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Status</Text>
        <Text>Configured: {configured ? "Yes" : "No"}</Text>
        <Text>Mode: {mode || "None"}</Text>
        <Text>Log messages received: {logCount.current}</Text>
      </View>

      {/* Service Agent */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Service Agent</Text>
        <Button title="Configure Service Agent" onPress={handleConfigureService} />
      </View>

      {/* Employee Agent */}
      {authSupported && (
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>Employee Agent</Text>
          <Text>Logged in: {loggedIn ? "Yes" : "No"}</Text>
          {!loggedIn ? (
            <Button title="Login" onPress={handleLogin} />
          ) : (
            <Button title="Configure Employee Agent" onPress={handleConfigureEmployee} />
          )}
        </View>
      )}

      {/* Conversation Actions */}
      {configured && (
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>Conversation</Text>
          <Button title="Launch Conversation" onPress={handleLaunch} />
          <View style={styles.spacer} />
          <Button title="New Conversation" onPress={handleNewConversation} />
          <View style={styles.spacer} />
          <Button title="Set Context Variables" onPress={handleSetContext} />
        </View>
      )}

      {/* Reset */}
      <View style={styles.section}>
        <Button title="Reset Everything" onPress={handleReset} color="red" />
      </View>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  content: { padding: 16, paddingBottom: 48 },
  title: { fontSize: 28, fontWeight: "bold", marginBottom: 24 },
  section: { marginBottom: 24 },
  sectionTitle: { fontSize: 18, fontWeight: "600", marginBottom: 8 },
  spacer: { height: 8 },
});

See Also