Skip to content

Expo

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.

npm install @uploadista/expo

Peer Dependencies:

  • expo (60.0.0+)
  • expo-file-system
  • expo-image-picker
  • expo-document-picker
  • expo-crypto
  • react (18.0.0+)
  • react-native
Terminal window
npx expo install expo-file-system expo-image-picker expo-document-picker expo-crypto
import { createUploadistaClient, UploadistaProvider } from "@uploadista/expo";
// Create client
const client = createUploadistaClient({
baseUrl: "https://api.example.com",
storageId: "my-storage",
chunkSize: 1024 * 1024, // 1MB chunks
});
// Wrap your app
function App() {
return (
<UploadistaProvider client={client}>
<MainScreen />
</UploadistaProvider>
);
}
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>
);
}

The Expo client supports two authentication modes that integrate with your existing auth system.

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 storage
import * as SecureStore from 'expo-secure-store';
auth: {
mode: 'direct',
getCredentials: async () => ({
headers: { 'X-API-Key': await SecureStore.getItemAsync('apiKey') }
})
}

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'
}
});

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,
},
});
OptionTypeDefaultDescription
baseUrlstringRequiredUploadista server URL
storageIdstringRequiredStorage backend ID
chunkSizenumber1048576Chunk size in bytes
useAsyncStoragebooleantruePersist state to AsyncStorage
connectionPoolingobject-HTTP connection pool config

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>
);
}

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 (/* ... */);
}

Simplified hook for client access:

import { useUploadistaClient } from "@uploadista/expo";
function MyComponent() {
const { client, isReady } = useUploadistaClient();
if (!isReady) {
return <Text>Loading...</Text>;
}
return (/* ... */);
}

Utility class for picking files from device:

import { ExpoFileSystemProvider } from "@uploadista/expo";
const provider = new ExpoFileSystemProvider();
// Pick image from gallery
const image = await provider.pickImage({
mediaTypes: "images",
quality: 0.8,
allowsEditing: true,
});
// Take photo with camera
const photo = await provider.takePhoto({
quality: 0.8,
allowsEditing: true,
});
// Pick any document
const document = await provider.pickDocument({
type: ["application/pdf", "image/*"],
copyToCacheDirectory: true,
});
interface FileInfo {
uri: string;
name: string;
size: number;
mimeType: string;
}
interface FilePickResult {
canceled: boolean;
file?: FileInfo;
}

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:

CallbackEvent DataDescription
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

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:

CallbackDescription
onJobStartJob execution started
onJobEndJob execution completed
onFlowStartFlow began execution
onFlowEndFlow completed successfully
onFlowErrorFlow encountered an error
onFlowPauseFlow paused by user
onFlowCancelFlow cancelled
onNodeStartNode started processing
onNodeEndNode completed
onNodePauseNode waiting for input
onNodeResumeNode resumed
onNodeErrorNode encountered error

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 (/* ... */);
}

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>
);
}

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 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 Flow compound component or useFlow hook directly within UploadistaProvider.

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 client
const client = createUploadistaClient({
baseUrl: "https://api.example.com",
storageId: "s3-production",
chunkSize: 5 * 1024 * 1024,
});
// Upload screen component
function 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 provider
export 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",
},
});

Full TypeScript support with comprehensive types:

import type {
UploadistaClientOptions,
UseFlowEventsOptions,
UseUploadEventsOptions,
FlowProps,
FlowUploadState,
FlowUploadStatus,
UseFlowReturn,
FileInfo,
FilePickResult,
} from "@uploadista/expo";