Expo
Overview
Section titled “Overview”The Expo client provides Expo-specific implementations of the Uploadista client services, enabling file uploads through Expo’s managed APIs. Supports file picking, camera capture, chunked uploads, and real-time progress tracking for React Native applications.
Installation
Section titled “Installation”npm install @uploadista/expopnpm add @uploadista/expoyarn add @uploadista/expoPeer Dependencies:
expo(60.0.0+)expo-file-systemexpo-image-pickerexpo-document-pickerexpo-cryptoreact(18.0.0+)react-native
npx expo install expo-file-system expo-image-picker expo-document-picker expo-cryptoQuick Start
Section titled “Quick Start”Basic Setup
Section titled “Basic Setup”import { createUploadistaClient, UploadistaProvider } from "@uploadista/expo";
// Create clientconst client = createUploadistaClient({ baseUrl: "https://api.example.com", storageId: "my-storage", chunkSize: 1024 * 1024, // 1MB chunks});
// Wrap your appfunction App() { return ( <UploadistaProvider client={client}> <MainScreen /> </UploadistaProvider> );}Simple Upload
Section titled “Simple Upload”import { useUploadistaClient } from "@uploadista/expo";import { View, Button, Text } from "react-native";import * as ImagePicker from "expo-image-picker";
function UploadScreen() { const { client } = useUploadistaClient();
const handleUpload = async () => { // Pick an image const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, quality: 0.8, });
if (result.canceled) return;
const asset = result.assets[0];
// Upload the file const upload = await client.upload({ uri: asset.uri, name: asset.fileName ?? "photo.jpg", type: asset.mimeType ?? "image/jpeg", });
console.log("Upload complete:", upload.id); };
return ( <View> <Button title="Pick & Upload Image" onPress={handleUpload} /> </View> );}Authentication
Section titled “Authentication”The Expo client supports two authentication modes that integrate with your existing auth system.
Direct Mode
Section titled “Direct Mode”Use direct mode when you have your own authentication (JWT, API keys, sessions, OAuth):
import { createUploadistaClient } from "@uploadista/expo";
const client = createUploadistaClient({ baseUrl: "https://api.example.com", storageId: "my-storage", chunkSize: 1024 * 1024, auth: { mode: 'direct', getCredentials: async () => ({ headers: { 'Authorization': `Bearer ${await getAccessToken()}` } }) }});Common patterns for direct mode:
// With OAuth token (e.g., Auth0, Firebase Auth)auth: { mode: 'direct', getCredentials: async () => { const token = await auth().currentUser?.getIdToken(); return { headers: { 'Authorization': `Bearer ${token}` } }; }}
// With API key from secure storageimport * as SecureStore from 'expo-secure-store';
auth: { mode: 'direct', getCredentials: async () => ({ headers: { 'X-API-Key': await SecureStore.getItemAsync('apiKey') } })}UploadistaCloud Mode
Section titled “UploadistaCloud Mode”Use UploadistaCloud mode for automatic JWT token exchange with your auth server:
const client = createUploadistaClient({ storageId: "my-storage", auth: { mode: 'uploadista-cloud', authServerUrl: 'https://api.example.com/auth/token', clientId: 'your-client-id' }});Client Configuration
Section titled “Client Configuration”createUploadistaClient
Section titled “createUploadistaClient”Create an Uploadista client with Expo-specific services:
import { createUploadistaClient } from "@uploadista/expo";
const client = createUploadistaClient({ // Required baseUrl: "https://api.example.com", storageId: "my-storage",
// Optional chunkSize: 5 * 1024 * 1024, // 5MB chunks (default: 1MB) useAsyncStorage: true, // Persist state to AsyncStorage connectionPooling: { maxConnections: 6, requestTimeout: 30000, },});| Option | Type | Default | Description |
|---|---|---|---|
baseUrl | string | Required | Uploadista server URL |
storageId | string | Required | Storage backend ID |
chunkSize | number | 1048576 | Chunk size in bytes |
useAsyncStorage | boolean | true | Persist state to AsyncStorage |
connectionPooling | object | - | HTTP connection pool config |
Provider & Context
Section titled “Provider & Context”UploadistaProvider
Section titled “UploadistaProvider”Wrap your app with UploadistaProvider to access client and events:
import { UploadistaProvider, useUploadistaContext, createUploadistaClient,} from "@uploadista/expo";
const client = createUploadistaClient({ /* ... */ });
function App() { return ( <UploadistaProvider client={client}> <Navigation /> </UploadistaProvider> );}useUploadistaContext
Section titled “useUploadistaContext”Access the client and event subscription:
import { useUploadistaContext } from "@uploadista/expo";
function UploadComponent() { const { client, subscribeToEvents } = useUploadistaContext();
// Use client for uploads // Subscribe to events for real-time updates
return (/* ... */);}useUploadistaClient
Section titled “useUploadistaClient”Simplified hook for client access:
import { useUploadistaClient } from "@uploadista/expo";
function MyComponent() { const { client, isReady } = useUploadistaClient();
if (!isReady) { return <Text>Loading...</Text>; }
return (/* ... */);}File Picking
Section titled “File Picking”ExpoFileSystemProvider
Section titled “ExpoFileSystemProvider”Utility class for picking files from device:
import { ExpoFileSystemProvider } from "@uploadista/expo";
const provider = new ExpoFileSystemProvider();
// Pick image from galleryconst image = await provider.pickImage({ mediaTypes: "images", quality: 0.8, allowsEditing: true,});
// Take photo with cameraconst photo = await provider.takePhoto({ quality: 0.8, allowsEditing: true,});
// Pick any documentconst document = await provider.pickDocument({ type: ["application/pdf", "image/*"], copyToCacheDirectory: true,});File Info Type
Section titled “File Info Type”interface FileInfo { uri: string; name: string; size: number; mimeType: string;}
interface FilePickResult { canceled: boolean; file?: FileInfo;}Event Hooks
Section titled “Event Hooks”useUploadEvents
Section titled “useUploadEvents”Listen to upload-specific events:
import { useUploadEvents } from "@uploadista/expo";
function UploadProgress() { useUploadEvents({ onProgress: (event) => { console.log(`Progress: ${event.progress}%`); console.log(`Bytes: ${event.bytesUploaded}/${event.totalBytes}`); }, onFile: (event) => { console.log("File uploaded:", event.uploadId); }, onFailed: (event) => { console.error("Upload failed:", event.error); }, onValidationSuccess: (event) => { console.log("Validation passed:", event.uploadId); }, onValidationFailed: (event) => { console.warn("Validation failed:", event.errors); }, });
return (/* ... */);}Available Callbacks:
| Callback | Event Data | Description |
|---|---|---|
onProgress | { uploadId, progress, bytesUploaded, totalBytes } | Upload progress update |
onFile | { uploadId, metadata } | File upload complete |
onFailed | { uploadId, error } | Upload failed |
onValidationSuccess | { uploadId } | File validation passed |
onValidationFailed | { uploadId, errors } | File validation failed |
onValidationWarning | { uploadId, warnings } | File validation warnings |
useFlowEvents
Section titled “useFlowEvents”Listen to flow execution events:
import { useFlowEvents } from "@uploadista/expo";
function FlowMonitor() { useFlowEvents({ onJobStart: (event) => { console.log("Job started:", event.jobId); }, onFlowStart: (event) => { console.log("Flow started:", event.flowId); }, onNodeStart: (event) => { console.log("Processing:", event.nodeName); }, onNodeEnd: (event) => { console.log("Node complete:", event.nodeName, event.result); }, onFlowEnd: (event) => { console.log("Flow complete:", event.outputs); }, onFlowError: (event) => { console.error("Flow failed:", event.error); }, });
return (/* ... */);}Available Callbacks:
| Callback | Description |
|---|---|
onJobStart | Job execution started |
onJobEnd | Job execution completed |
onFlowStart | Flow began execution |
onFlowEnd | Flow completed successfully |
onFlowError | Flow encountered an error |
onFlowPause | Flow paused by user |
onFlowCancel | Flow cancelled |
onNodeStart | Node started processing |
onNodeEnd | Node completed |
onNodePause | Node waiting for input |
onNodeResume | Node resumed |
onNodeError | Node encountered error |
useUploadistaEvents
Section titled “useUploadistaEvents”Low-level hook for all events:
import { useUploadistaEvents, isFlowEvent, isUploadEvent } from "@uploadista/expo";
function EventLogger() { useUploadistaEvents((event) => { if (isUploadEvent(event)) { console.log("Upload event:", event); } else if (isFlowEvent(event)) { console.log("Flow event:", event); } });
return (/* ... */);}Flow Integration
Section titled “Flow Integration”useFlow Hook
Section titled “useFlow Hook”The useFlow hook manages flow-based uploads with multiple inputs:
import { useFlow } from "@uploadista/expo";
function FlowUpload() { const flow = useFlow({ flowId: "image-processing", storageId: "my-storage", onComplete: (outputs) => { console.log("Flow outputs:", outputs); }, onError: (error) => { console.error("Flow error:", error); }, });
const handleAddImage = async () => { const result = await ImagePicker.launchImageLibraryAsync(); if (!result.canceled) { flow.addInput("input-1", { uri: result.assets[0].uri, name: result.assets[0].fileName ?? "image.jpg", type: result.assets[0].mimeType ?? "image/jpeg", }); } };
return ( <View> <Button title="Add Image" onPress={handleAddImage} /> <Button title="Execute Flow" onPress={flow.execute} disabled={!flow.canExecute} /> {flow.isExecuting && ( <Text>Progress: {Math.round(flow.progress * 100)}%</Text> )} </View> );}Flow Compound Component
Section titled “Flow Compound Component”Declarative UI for flow uploads:
import { Flow } from "@uploadista/expo";import { View, Text, Button, Image } from "react-native";
function FlowUploadUI() { return ( <Flow flowId="image-processing" storageId="my-storage"> {({ isExecuting, progress, canExecute }) => ( <View> <Flow.Inputs> {({ inputs }) => ( <View> {inputs.map((input) => ( <Flow.Input key={input.id} inputId={input.id}> <Flow.Input.FilePicker> {({ pickFile }) => ( <Button title="Select File" onPress={pickFile} /> )} </Flow.Input.FilePicker>
<Flow.Input.Preview> {({ file, preview }) => preview ? ( <Image source={{ uri: preview }} style={{ width: 100, height: 100 }} /> ) : null } </Flow.Input.Preview> </Flow.Input> ))} </View> )} </Flow.Inputs>
<Flow.Progress> {({ progress }) => ( <Text>Progress: {Math.round(progress * 100)}%</Text> )} </Flow.Progress>
<Flow.Status> {({ status }) => <Text>Status: {status}</Text>} </Flow.Status>
<Flow.Error> {({ error }) => error ? <Text style={{ color: "red" }}>{error.message}</Text> : null } </Flow.Error>
<Flow.Submit> {({ submit, disabled }) => ( <Button title="Process" onPress={submit} disabled={disabled} /> )} </Flow.Submit>
<Flow.Cancel> {({ cancel, disabled }) => ( <Button title="Cancel" onPress={cancel} disabled={disabled} /> )} </Flow.Cancel> </View> )} </Flow> );}FlowManagerProvider (Advanced)
Section titled “FlowManagerProvider (Advanced)”FlowManagerProvider is already included inside UploadistaProvider, so you don’t need to add it manually for basic usage. It’s only needed if you want to access the flow manager context directly for advanced scenarios like coordinating multiple concurrent flow instances:
import { useFlowManagerContext } from "@uploadista/expo";
function AdvancedFlowList() { // Access the flow manager for advanced multi-flow coordination const { getManager, releaseManager } = useFlowManagerContext();
// getManager(flowId) - Get or create a flow manager instance // releaseManager(flowId) - Release a flow manager when done}Note: For most use cases, simply use the
Flowcompound component oruseFlowhook directly withinUploadistaProvider.
Complete Example
Section titled “Complete Example”import React, { useState } from "react";import { View, Text, Button, Image, StyleSheet, FlatList } from "react-native";import * as ImagePicker from "expo-image-picker";import { createUploadistaClient, UploadistaProvider, useUploadistaClient, useUploadEvents, useFlowEvents,} from "@uploadista/expo";
// Create clientconst client = createUploadistaClient({ baseUrl: "https://api.example.com", storageId: "s3-production", chunkSize: 5 * 1024 * 1024,});
// Upload screen componentfunction UploadScreen() { const { client } = useUploadistaClient(); const [uploads, setUploads] = useState< { id: string; name: string; progress: number; status: string }[] >([]);
// Listen for upload events useUploadEvents({ onProgress: (event) => { setUploads((prev) => prev.map((u) => u.id === event.uploadId ? { ...u, progress: event.progress } : u ) ); }, onFile: (event) => { setUploads((prev) => prev.map((u) => u.id === event.uploadId ? { ...u, status: "complete" } : u ) ); }, onFailed: (event) => { setUploads((prev) => prev.map((u) => u.id === event.uploadId ? { ...u, status: "failed" } : u ) ); }, });
// Listen for flow events useFlowEvents({ onFlowEnd: (event) => { console.log("Processing complete:", event.outputs); }, });
const pickAndUpload = async () => { const result = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, quality: 0.8, });
if (result.canceled) return;
const asset = result.assets[0]; const uploadId = `upload-${Date.now()}`;
// Add to list setUploads((prev) => [ ...prev, { id: uploadId, name: asset.fileName ?? "image.jpg", progress: 0, status: "uploading", }, ]);
try { // Start upload await client.upload({ uri: asset.uri, name: asset.fileName ?? "image.jpg", type: asset.mimeType ?? "image/jpeg", }); } catch (error) { console.error("Upload error:", error); } };
return ( <View style={styles.container}> <Text style={styles.title}>Uploadista Expo Demo</Text>
<Button title="Pick Image" onPress={pickAndUpload} />
<FlatList data={uploads} keyExtractor={(item) => item.id} renderItem={({ item }) => ( <View style={styles.uploadItem}> <Text>{item.name}</Text> <Text> {item.status === "uploading" ? `${Math.round(item.progress * 100)}%` : item.status} </Text> </View> )} /> </View> );}
// App with providerexport default function App() { return ( <UploadistaProvider client={client}> <UploadScreen /> </UploadistaProvider> );}
const styles = StyleSheet.create({ container: { flex: 1, padding: 20, paddingTop: 60 }, title: { fontSize: 24, fontWeight: "bold", marginBottom: 20 }, uploadItem: { flexDirection: "row", justifyContent: "space-between", padding: 10, borderBottomWidth: 1, borderBottomColor: "#eee", },});TypeScript Support
Section titled “TypeScript Support”Full TypeScript support with comprehensive types:
import type { UploadistaClientOptions, UseFlowEventsOptions, UseUploadEventsOptions, FlowProps, FlowUploadState, FlowUploadStatus, UseFlowReturn, FileInfo, FilePickResult,} from "@uploadista/expo";Related Concepts
Section titled “Related Concepts”- Authentication - Authentication and permissions
- Uploadista Server - Server configuration
- Flows Engine - Flow processing pipelines
- React - Web React integration