expo-rollup
v0.3.2
Published
Rollup SDK for Expo and React Native: on-device camera roll index, search, query and profile (iOS).
Maintainers
Readme
expo-rollup
The Rollup SDK for Expo and React Native apps. Rollup indexes the person's camera roll on their iPhone, so your app
can search it on the device and ask about their life (query, profile). Photos never leave the iPhone.
This module is a thin wrapper over the native Rollup SDK for iOS: the Swift SDK does the indexing and search, and the module passes calls and results across the bridge as JSON. Documentation: https://docs.tryrollup.com/platforms/expo
Requirements
- iOS only. On Android and the web every call rejects with code
unsupportedPlatform, so cross-platform apps still build and run. - An iPhone with iOS 26 or later for Rollup to run. Your app can support iOS 16.0 and later: below iOS 26,
isAvailableis false and every call rejects withunsupportedPlatform. The Simulator is not supported:configurerejects withsimulatorUnsupported. Development builds on a device work (they attest in Apple's development App Attest environment, which Rollup accepts). - A development build (
npx expo run:ios --device, or EAS Build). Expo Go does not include this module. - An approved app in the Rollup developer dashboard (https://www.tryrollup.com/developers), with your Team ID and
bundle ID, and its publishable key (
rk_live_...).
Install
npx expo install expo-rollupAdd the config plugin to app.json (or app.config.js):
{
"expo": {
"ios": { "bundleIdentifier": "com.example.app" },
"plugins": [
["expo-rollup", { "photosPermission": "Your photos help personalize the app. They stay on your iPhone." }]
]
}
}Then generate the native project and build: npx expo prebuild --platform ios and npx expo run:ios --device.
The plugin sets everything the SDK needs:
NSPhotoLibraryUsageDescription(thephotosPermissionoption).BGTaskSchedulerPermittedIdentifierswith<bundle ID>.rollup.indexing.*and<bundle ID>.rollup.update, andUIBackgroundModeswithprocessing.- The App Attest entitlement (
com.apple.developer.devicecheck.appattest-environment). - An iOS deployment target of at least 16.0 (a higher one is kept).
pod 'Rollup', :podspec => '...'in the Podfile: the Rollup binary is not on CocoaPods trunk, so the app's Podfile names its podspec. The version and URL come from this package'srollup-sdk.json.
The module registers Rollup's background task at launch on its own.
| Option | Default | Meaning |
| --- | --- | --- |
| photosPermission | A short default text | The Photos permission text. Say why your app reads the person's photos. |
| appAttestEnvironment | "production" | "development" or "production". Rollup accepts both. |
| rollupPodspec | From rollup-sdk.json | Where CocoaPods gets the Rollup pod. |
Bare React Native
Set up Expo Modules (npx install-expo-modules@latest), install the package (npm install expo-rollup), then make
the changes above by hand: the Info.plist entries, the App
Attest capability, an iOS deployment target of 16.0 or later, and this line in your Podfile's app target, after
use_expo_modules!:
pod 'Rollup', :podspec => 'https://raw.githubusercontent.com/tryrollup/rollup-ios-sdk/0.2.0/Rollup.podspec'Use
import * as Rollup from 'expo-rollup';
// Once, when your app starts. Every option left out keeps the SDK's default (the last three months, growing).
await Rollup.configure('rk_live_...', {
window: Rollup.indexWindow.growing(Rollup.span.months(3)),
});
// From a screen where the person has just chosen to turn the feature on: a grant starts the first index.
const access = await Rollup.requestPhotoAccess();
// The indexing state, kept current (downloadingAssets, preparing, indexing, ready, unfinished, paused, failed, ...).
const state = Rollup.useIndexingState();
if (Rollup.isSearchable(state)) {
const results = await Rollup.search('ramen in Tokyo last spring');
const answer = await Rollup.query('Where do I eat most?');
const profile = await Rollup.profile({
type: 'object',
properties: { favorite_cuisine: { type: 'string', description: 'The cuisine they eat most often' } },
});
}
// The index's largest groups (labels, kinds of place, cuisines, food) with counts and sample photos, in one call.
const groups = await Rollup.facets({ limit: 16, samples: 4 });
// Each batch of photos a run makes searchable, as it does: add them to the screen instead of searching again.
const subscription = Rollup.addIndexedBatchListener((batch) => console.log(batch.items.length, batch.indexed, batch.total));
// Start over without downloading the models again (reset() deletes them too). Then startIndexing() builds a new index.
await Rollup.deleteIndex();Calls reject with a RollupError: a stable code (the Swift SDK's codes, plus unsupportedPlatform and
invalidOptions), a message for developers, and for errors from Rollup's server the serverCode, retryable and
retryAfter.
try {
await Rollup.query('Where do I eat most?');
} catch (error) {
if (Rollup.isRollupError(error, 'quotaExceeded')) {
// The app's monthly budget is used up until the date in error.message.
}
}Search results identify photos by their PhotoKit local identifier (result.id). Show them with
expo-media-library, or a ph:// URI in React Native's Image.
Privacy and App Review
search runs on the iPhone. query and profile send text the index derived from the person's photos (dates,
places, labels, text read in photos) to Rollup's server and its model provider. Before your app calls them, tell the
person and get their permission: App Store Review Guideline 5.1.2(i) requires it for personal data shared with
third-party AI. See https://docs.tryrollup.com/guides/privacy
