Skip to content

Conversation

@rizalibnu
Copy link

@rizalibnu rizalibnu commented Jan 27, 2026

Description

This PR introduces modular resource fetcher adapters to support both Expo and bare React Native environments, replacing the previous monolithic approach with a flexible, platform-specific architecture.

Key Changes
New Adapter Packages:

  • @rn-executorch/expo-adapter: Resource fetcher for Expo projects using expo-file-system and expo-asset
  • @rn-executorch/bare-adapter: Resource fetcher for bare React Native projects using @dr.pogodin/react-native-fs and @kesha-antonov/react-native-background-downloader

Initialization Changes:

  • Added initExecutorch() function that requires explicit adapter selection
  • Users must now choose and configure the appropriate adapter for their project type
  • Provides better separation of concerns and platform-specific optimizations

Documentation Updates:

  • Created individual README.md files for each adapter package

Introduces a breaking change?

  • Yes
  • No

Migration Required:
Users must now explicitly initialize the library with a resource fetcher adapter:

// Before (no initialization needed)
import { useLLM } from 'react-native-executorch';

// After (required initialization)
import { initExecutorch, useLLM } from 'react-native-executorch';
import { ExpoResourceFetcher } from '@rn-executorch/expo-adapter'; // or BareResourceFetcher

initExecutorch({
  resourceFetcher: ExpoResourceFetcher,
});

Type of change

  • Bug fix (change which fixes an issue)
  • New feature (change which adds functionality)
  • Documentation update (improves or adds clarity to existing documentation)
  • Other (chores, tests, code style improvements etc.)

Tested on

  • iOS
  • Android

Testing instructions

For Expo projects:

  • Install dependencies: yarn add @rn-executorch/expo-adapter expo-file-system expo-asset
  • Initialize: initExecutorch({ resourceFetcher: ExpoResourceFetcher })
  • Run existing LLM example app to verify model downloads work correctly

For bare React Native projects:

  • Install dependencies: yarn add @rn-executorch/bare-adapter @dr.pogodin/react-native-fs @kesha-antonov/react-native-background-downloader
  • Initialize: initExecutorch({ resourceFetcher: BareResourceFetcher })
  • Run the bare React Native example app in PR feat: add bare React Native LLM chat example app #763

Note: A separate PR will add a dedicated bare React Native example app to make this PR easier to review. The Expo example apps can be used to verify the Expo adapter functionality.

Screenshots

Related issues

Closes #549

Checklist

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation accordingly
  • My changes generate no new warnings

Additional notes

Why This Change:

  • Different React Native environments have different filesystem APIs and c
  • apabilities
  • Expo projects benefit from using Expo's managed filesystem APIs
  • Bare React Native projects can leverage native libraries with background download support
  • Modular architecture allows for better platform-specific optimizations
  • Enables future extensibility for other environments (e.g., React Native Windows, macOS)

Split Into Multiple PRs:
To make review easier, this work has been split:

  • This PR: Core adapter infrastructure and Expo adapter implementation
  • Follow-up PR: Bare React Native example app demonstrating the bare adapter usage

BREAKING CHANGE: initExecutorch() with explicit adapter selection is now required before using any react-native-executorch hooks. Users must install and configure either @rn-executorch/expo-adapter or @rn-executorch/bare-adapter depending on their project type.

rizalibnu and others added 13 commits January 23, 2026 09:07
Add modular resource fetcher adapters to support both Expo and bare React Native environments.

## New Packages

### @rn-executorch/expo-adapter
- Expo-based resource fetcher using expo-file-system
- Supports asset bundles, local files, and remote downloads
- Download management with pause/resume/cancel capabilities

### @rn-executorch/bare-adapter
- Bare React Native resource fetcher using RNFS and background downloader
- Supports all platform-specific file operations
- Background download support with proper lifecycle management

## Core Changes

- Refactor ResourceFetcher to use adapter pattern
- Add initExecutorch() and cleanupExecutorch() for adapter management
- Export adapter interfaces and utilities
- Update LLM controller to support new resource fetching

## App Updates

- Update computer-vision, llm, speech-to-text, text-embeddings apps
- Add adapter initialization to each app
- Update dependencies to use workspace packages
Add a complete bare React Native example app demonstrating LLM integration with react-native-executorch.

## App: llm_bare

### Features
- Simple chat UI for LLM interactions
- Model loading with progress indicator
- Real-time streaming responses
- Send/stop generation controls
- Auto-scrolling message history

### Stack
- **Framework**: React Native 0.81.5 (bare/CLI)
- **LLM**: Uses LLAMA3_2_1B_SPINQUANT model
- **Adapter**: @rn-executorch/bare-adapter
- **Dependencies**: Minimal deps, only essential packages

### Platform Configuration

#### iOS
- Bridging header for RNBackgroundDownloader
- Background URL session handling in AppDelegate
- Background modes (fetch, processing)
- Xcode project configuration

#### Android
- Required permissions for background downloads
- Foreground service configuration
- Network state access
- Proper manifest configuration

### Infrastructure
- Babel configuration for export namespace transform

This serves as a reference implementation for using react-native-executorch in bare React Native environments (non-Expo).
@rizalibnu rizalibnu changed the title feat: add modular resource fetcher adapters for Expo and React Native feat: add modular resource fetcher adapters for Expo and bare React Native Jan 27, 2026
@IgorSwat IgorSwat requested a review from chmjkb January 27, 2026 08:42
@mkopcins mkopcins self-requested a review January 27, 2026 09:02
@msluszniak
Copy link
Member

Lint CI fails (you don't need to worry about the second failing CI ;)). Please could you fix the errors from this CI?

@rizalibnu
Copy link
Author

@msluszniak I’m not able to reproduce the lint CI failure locally — everything passes on my side.

I’ll take a closer look and investigate further to see what might be causing the discrepancy (environment, cache, or config differences). I’ll follow up with a fix or more details as soon as I find the root cause.

Screenshot 2026-01-27 at 17 19 22

@msluszniak
Copy link
Member

@rizalibnu Sure thing, maybe the configuration of the CI itself does not work with as it should. We'll also look at this, don't worry :)

@rizalibnu rizalibnu marked this pull request as draft January 27, 2026 13:22
@rizalibnu rizalibnu marked this pull request as ready for review January 27, 2026 14:06
@rizalibnu
Copy link
Author

@msluszniak Found the issue 👍
CI was failing because react-native-executorch types come from lib/typescript, which isn’t built on a fresh run. It worked locally since I already had the build artifacts.

Fixed by adding a build step before adapter type checks and bumped Node to 22 in .nvmrc due to an ESM-only dependency (arktype via react-native-builder-bob).

@rizalibnu rizalibnu force-pushed the feat/resource-fetcher-adapters branch from 72914c0 to 359427b Compare January 27, 2026 14:57
@msluszniak msluszniak added the feature PRs that implement a new feature label Jan 28, 2026
package.json Outdated
Comment on lines 10 to 13
"apps/computer-vision",
"apps/llm",
"apps/speech",
"apps/text-embeddings"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need to expanded apps/*

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@msluszniak We need to expand apps/* because apps/bare_rn in PR #763 must be excluded from the Yarn workspace. An alternative is explicitly excluding it in package.json as shown below.

"workspaces": [
  "apps/*",
  "!apps/bare_rn"
]

Which approach do you prefer?

Copy link
Member

@msluszniak msluszniak Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I prefer the alternative, because if we add another apps in the future, we don't need to remember to update this file. But I would like to know others opinion, cc: @chmjkb @mkopcins

Copy link
Author

@rizalibnu rizalibnu Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the alternative 👍
Using apps/* with an explicit exclusion is more future-proof.
Should we apply the same glob + exclusion pattern to packages/ as well for consistency?

"workspaces": {
  "packages": [
    "packages/*",
    "apps/*",
    "!apps/bare_rn"
  ]
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm on the alternative side as we likely won't add much bare RN example apps in the future.

Copy link
Author

@rizalibnu rizalibnu Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve applied the glob pattern to apps/ and packages/. Please re-review. @msluszniak @chmjkb

@@ -0,0 +1,34 @@
{
"name": "@rn-executorch/expo-adapter",
"version": "0.1.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to have version of adapters to be divergent from version of React Native ExecuTorch cc: @chmjkb @mkopcins ?

Copy link
Collaborator

@chmjkb chmjkb Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fine. We just need to keep in mind to add a compatibility table, if some version is not compatible at some point. We can also make sure that RNET is a peer dependency of the adapters so we can narrow down on the versions there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just remember from one project, that we wanted at some point unify the versions of packages in monorepo, but it was to late since we started with the approach as here. Same thing as Apple did with OS versions on multiple devices ;p

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s a fair concern.

While independent adapter releases are useful early on, IMO I think a good compromise might be to align major/minor versions between adapters and RNET, while allowing independent patch releases. This keeps compatibility clear for users without blocking adapter-only fixes.

Happy to follow whatever versioning direction the maintainers prefer.

"paths": {
"react-native-executorch": ["../../packages/react-native-executorch/src"]
"react-native-executorch": ["../../packages/react-native-executorch/src"],
"@rn-executorch/expo-adapter": ["../../packages/expo-adapter/src"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we're likely going to call the package react-native-executorch/expo-resource-fetcher or react-native-executorch/bare-resource-fetcher, so let's stick to that naming 👍🏻

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Please re-review. @chmjkb

package.json Outdated
Comment on lines 10 to 13
"apps/computer-vision",
"apps/llm",
"apps/speech",
"apps/text-embeddings"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm on the alternative side as we likely won't add much bare RN example apps in the future.

}

export function cleanupExecutorch() {
ResourceFetcher.setAdapter(null as unknown as ResourceFetcherAdapter);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be a tiny bit cleaner if we had a resetAdapter or something like this exposed from ResourceFetcher

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve added resetAdapter. Please re-review. @chmjkb

- Projects that need true background downloads
- Projects requiring direct native filesystem access

This adapter uses `@dr.pogodin/react-native-fs` and `@kesha-antonov/react-native-background-downloader` for enhanced file operations and background download support.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be cool to link here to their installation steps

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Please re-review. @chmjkb


export async function createDirectoryIfNoExists() {
if (!(await checkFileExists(RNEDirectory))) {
await RNFS.mkdir(RNEDirectory);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we try catch here and re-throw rnexecutorch erorr?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Please re-review. @chmjkb

extendedInfo: ResourceSourceExtended;
}

export namespace ResourceFetcherUtils {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for exporting this as a namespace?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chmjkb It follows the original implementation in the existing codebase. I kept the same pattern to stay consistent and avoid unnecessary refactors.
https://github.com/software-mansion/react-native-executorch/blob/main/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts#L50

Comment on lines 40 to 47
pauseFetching(...sources: ResourceSource[]): Promise<void>;
resumeFetching(...sources: ResourceSource[]): Promise<void>;
cancelFetching(...sources: ResourceSource[]): Promise<void>;
listDownloadedFiles(): Promise<string[]>;
listDownloadedModels(): Promise<string[]>;
deleteResources(...sources: ResourceSource[]): Promise<void>;
getFilesTotalSize(...sources: ResourceSource[]): Promise<number>;
readAsString(path: string): Promise<string>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if these should be enforced, in the library internals, we only use fetch(), meaning that in case where the user wants to implement his own resource fetcher, he only has to implement fetch() for the library internals to work. The other methods are just for convenience. We should either add a JSDoc here saying that or we should remove them from this interface completely. cc @mkopcins, wdyt?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we should require the bare minimum for the fetcher to work and mark other method as optional to make implementing custom fetcher as simple as possible

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve simplified the interface to include only the methods that are actually used. Please re-review. @mkopcins @chmjkb

Add explicit resetAdapter() method to ResourceFetcher class for cleaner API.
- Add resetAdapter() static method that sets adapter to null
- Update cleanupExecutorch() to use resetAdapter() instead of type assertion hack
- Update error message to reference new package names (@react-native-executorch/*)

This provides a cleaner, type-safe way to reset the adapter without
requiring "null as unknown as ResourceFetcherAdapter" type assertion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature PRs that implement a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Explore removing expo dependency

4 participants