React Native Integration

Build mobile AI experiences with the Fig1 SDK in React Native.

Setup

The SDK is a REST API—no native modules required.

// lib/fig1-client.ts
const API_BASE = 'https://app.fig1.ai/api/sdk';

class Fig1Client {
  private apiKey: string;

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
    const response = await fetch(`${API_BASE}${endpoint}`, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        'X-Fig1-API-Key': this.apiKey,
        ...options.headers,
      },
    });

    const data = await response.json();
    if (!data.success) throw new Error(data.error);
    return data.data;
  }

  async chat(message: string, sessionId?: string, options?: {
    personaId?: string;
    preferences?: Record<string, any>;
  }) {
    return this.request<{
      message: string;
      sessionId: string;
      actions?: any[];
    }>('/agent/chat', {
      method: 'POST',
      body: JSON.stringify({ message, sessionId, ...options }),
    });
  }
}

export const fig1 = new Fig1Client('YOUR_API_KEY');

Chat Hook

// hooks/useChat.ts
import { useState, useCallback } from 'react';
import { fig1 } from '../lib/fig1-client';

interface Message {
  id: string;
  role: 'user' | 'assistant';
  content: string;
}

export function useChat(personaId?: string) {
  const [messages, setMessages] = useState<Message[]>([]);
  const [sessionId, setSessionId] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  const sendMessage = useCallback(async (text: string) => {
    const userMessage: Message = {
      id: Date.now().toString(),
      role: 'user',
      content: text,
    };

    setMessages(prev => [...prev, userMessage]);
    setLoading(true);

    try {
      const response = await fig1.chat(text, sessionId || undefined, { personaId });

      setSessionId(response.sessionId);

      const assistantMessage: Message = {
        id: (Date.now() + 1).toString(),
        role: 'assistant',
        content: response.message,
      };

      setMessages(prev => [...prev, assistantMessage]);
      return response;
    } finally {
      setLoading(false);
    }
  }, [sessionId, personaId]);

  const clearChat = useCallback(() => {
    setMessages([]);
    setSessionId(null);
  }, []);

  return { messages, sendMessage, clearChat, loading, sessionId };
}

Chat Screen

// screens/ChatScreen.tsx
import React, { useState, useRef } from 'react';
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  FlatList,
  KeyboardAvoidingView,
  Platform,
  StyleSheet,
} from 'react-native';
import { useChat } from '../hooks/useChat';

export function ChatScreen() {
  const [input, setInput] = useState('');
  const { messages, sendMessage, loading } = useChat();
  const flatListRef = useRef<FlatList>(null);

  const handleSend = async () => {
    if (!input.trim() || loading) return;
    const text = input;
    setInput('');
    await sendMessage(text);
  };

  return (
    <KeyboardAvoidingView
      style={styles.container}
      behavior={Platform.OS === 'ios' ? 'padding' : undefined}
      keyboardVerticalOffset={90}
    >
      <FlatList
        ref={flatListRef}
        data={messages}
        keyExtractor={(item) => item.id}
        onContentSizeChange={() => flatListRef.current?.scrollToEnd()}
        contentContainerStyle={styles.messageList}
        renderItem={({ item }) => (
          <View style={[
            styles.messageBubble,
            item.role === 'user' ? styles.userBubble : styles.assistantBubble
          ]}>
            <Text style={[
              styles.messageText,
              item.role === 'user' && styles.userText
            ]}>
              {item.content}
            </Text>
          </View>
        )}
      />

      {loading && (
        <View style={styles.loadingContainer}>
          <Text style={styles.loadingText}>Thinking...</Text>
        </View>
      )}

      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          value={input}
          onChangeText={setInput}
          placeholder="Type a message..."
          editable={!loading}
          onSubmitEditing={handleSend}
          returnKeyType="send"
        />
        <TouchableOpacity
          style={[styles.sendButton, (!input.trim() || loading) && styles.sendButtonDisabled]}
          onPress={handleSend}
          disabled={!input.trim() || loading}
        >
          <Text style={styles.sendButtonText}>Send</Text>
        </TouchableOpacity>
      </View>
    </KeyboardAvoidingView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  messageList: {
    padding: 16,
  },
  messageBubble: {
    maxWidth: '80%',
    padding: 12,
    borderRadius: 16,
    marginBottom: 8,
  },
  userBubble: {
    backgroundColor: '#007AFF',
    alignSelf: 'flex-end',
  },
  assistantBubble: {
    backgroundColor: '#F0F0F0',
    alignSelf: 'flex-start',
  },
  messageText: {
    fontSize: 16,
  },
  userText: {
    color: '#fff',
  },
  loadingContainer: {
    padding: 8,
    alignItems: 'center',
  },
  loadingText: {
    color: '#666',
  },
  inputContainer: {
    flexDirection: 'row',
    padding: 12,
    borderTopWidth: 1,
    borderTopColor: '#E0E0E0',
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#E0E0E0',
    borderRadius: 20,
    paddingHorizontal: 16,
    paddingVertical: 8,
    fontSize: 16,
  },
  sendButton: {
    marginLeft: 8,
    backgroundColor: '#007AFF',
    borderRadius: 20,
    paddingHorizontal: 20,
    justifyContent: 'center',
  },
  sendButtonDisabled: {
    opacity: 0.5,
  },
  sendButtonText: {
    color: '#fff',
    fontWeight: '600',
  },
});

Handling Actions

// hooks/useAgentActions.ts
import { useCallback } from 'react';
import { Linking } from 'react-native';
import { useNavigation } from '@react-navigation/native';

interface ClientAction {
  type: string;
  payload: Record<string, any>;
  immediate?: boolean;
}

export function useAgentActions() {
  const navigation = useNavigation();

  const handleActions = useCallback((actions: ClientAction[]) => {
    for (const action of actions) {
      if (action.immediate === false) continue;

      switch (action.type) {
        case 'navigate':
          if (action.payload.route) {
            navigation.navigate(action.payload.route as never);
          }
          break;

        case 'open_url':
          if (action.payload.url) {
            Linking.openURL(action.payload.url);
          }
          break;

        case 'show_product':
          if (action.payload.productId) {
            navigation.navigate('ProductDetail' as never, {
              productId: action.payload.productId
            } as never);
          }
          break;
      }
    }
  }, [navigation]);

  return { handleActions };
}

Environment Variables

For React Native, use react-native-config or a similar solution:

# .env
FIG1_API_KEY=fig1_sdk_your_api_key
import Config from 'react-native-config';

const fig1 = new Fig1Client(Config.FIG1_API_KEY);

Security Note

For production apps, route requests through your own backend to keep API keys secure. The mobile app should call your server, which then calls the Fig1 API.