Skip to content

chore(deps)!: use non-legacy Expo FS in Resource Fetcher, bump SDK version to 56 in demo apps#1222

Open
chmjkb wants to merge 11 commits into
mainfrom
@chmjkb/expo-56-fs
Open

chore(deps)!: use non-legacy Expo FS in Resource Fetcher, bump SDK version to 56 in demo apps#1222
chmjkb wants to merge 11 commits into
mainfrom
@chmjkb/expo-56-fs

Conversation

@chmjkb

@chmjkb chmjkb commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR bumps Expo SDK to 56 across all demo apps using Expo. It also refactors the Expo Resource Fetcher to use the aforementioned version. For users using <56, a /legacy import for ExpoResourceFetcher is still available.

Introduces a breaking change?

  • Yes
  • No

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

  • In an app on Expo SDK <56 (e.g. SDK 54/55), install this package and import { ExpoResourceFetcher } from 'react-native-executorch-expo-resource-fetcher'. Confirm it throws RnExecutorchError with code ResourceFetcherFileSystemApiUnavailable (0xbc) and a message pointing at the /legacy entry point — not the cryptic File.createDownloadTask is not a function.
  • In the same old-SDK app, switch the import to react-native-executorch-expo-resource-fetcher/legacy and confirm fetch, pause/resume/cancel, deleteResources, and listDownloadedFiles all still work.
  • In an app on Expo SDK 56+, import from the default entry point and confirm the guard does not fire and downloads work end-to-end.
  • Run yarn install at the repo root and confirm no new peer-dep warnings on the SDK-56 demo apps under apps/.

Screenshots

Related issues

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

@chmjkb chmjkb self-assigned this Jun 8, 2026
@chmjkb chmjkb added the dependencies Pull requests that update a dependency file label Jun 8, 2026
@chmjkb chmjkb linked an issue Jun 8, 2026 that may be closed by this pull request
@chmjkb chmjkb changed the title chore(deps): use non-legacy Expo FS in Resource Fetcher, bump SDK version to 56 in demo apps chore(deps)!: use non-legacy Expo FS in Resource Fetcher, bump SDK version to 56 in demo apps Jun 9, 2026
@chmjkb chmjkb marked this pull request as ready for review June 9, 2026 10:56
@msluszniak

Copy link
Copy Markdown
Member

I'll check why CI has diffs on comments in *.h files. This is definitely related to my addition to CI which suppose to check if error codes are regenerated.

@msluszniak msluszniak self-requested a review June 9, 2026 13:57
@msluszniak msluszniak added the 3rd party package Issue related to 3rd party packages, but not ExecuTorch, e.g. Expo label Jun 9, 2026
@chmjkb

chmjkb commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

I'll check why CI has diffs on comments in *.h files. This is definitely related to my addition to CI which suppose to check if error codes are regenerated.

im pretty sure pre-commit does a lint, and yarn codegen:errors returns something that is not yet linted properly:)
so changed on this branch are linted, while the ones generated by the codegen are not, hence the diff. not sure tho

@msluszniak

msluszniak commented Jun 9, 2026

Copy link
Copy Markdown
Member

Yeah, makes sense. That's not the reason. This branch has stale main tip and it caused the CI failure.

@msluszniak msluszniak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actually the reason why CI fails is because you are not rebased on main, please rebase it first ;)

@chmjkb chmjkb force-pushed the @chmjkb/expo-56-fs branch from c1ff419 to 7cd0041 Compare June 10, 2026 06:43
@msluszniak

Copy link
Copy Markdown
Member

Can we drop the react-native-executorch devDependency bump?

In packages/react-native-executorch/package.json this PR bumps the core package's dev baseline to RN 0.85:

- "@types/react": "~19.1.10",
- "react": "19.1.0",
- "react-native": "0.81.5",
+ "@types/react": "~19.2.0",
+ "react": "19.2.3",
+ "react-native": "0.85.3",

I don't think the core package should move off 0.81, for a few reasons:

  • It's not needed for this PR. The only RNE-core change here is the new ResourceFetcherFileSystemApiUnavailable enum (8 lines across ErrorCodes.ts + ErrorCodes.h) — no RN 0.85 APIs. All the actual expo-file-system work lives in packages/expo-resource-fetcher, which already carries its own react-native@0.85.3 / expo@^56 devDeps. That's the right place for it, since File.createDownloadTask is SDK-56-only.

  • peerDependencies are react-native: "*", so there's no install-time floor on consumers. The dev RN version is effectively the only thing pinning what we type-check and test core against — bumping it to 0.85 silently drops our 0.81 coverage with nothing to catch a regression.

  • It works against this PR's own goal. expo-resource-fetcher/legacy exists for Expo SDK <56 users, who are on RN 0.81/0.83 — never 0.85. If core RNE is only developed against 0.85, the exact users /legacy is for are running a core that's no longer tested on their RN. Same for bare-resource-fetcher (untouched here, still peer react-native: "*" / dev 0.81.5) and apps/bare-rn (pinned 0.81.5).

Could we revert just those three devDep lines and keep the SDK-56 / RN-0.85 bumps scoped to the demo apps and expo-resource-fetcher? The core package staying on 0.81 is what makes the legacy + bare lanes meaningful.

@chmjkb

chmjkb commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Can we drop the react-native-executorch devDependency bump?

In packages/react-native-executorch/package.json this PR bumps the core package's dev baseline to RN 0.85:

- "@types/react": "~19.1.10",
- "react": "19.1.0",
- "react-native": "0.81.5",
+ "@types/react": "~19.2.0",
+ "react": "19.2.3",
+ "react-native": "0.85.3",

I don't think the core package should move off 0.81, for a few reasons:

  • It's not needed for this PR. The only RNE-core change here is the new ResourceFetcherFileSystemApiUnavailable enum (8 lines across ErrorCodes.ts + ErrorCodes.h) — no RN 0.85 APIs. All the actual expo-file-system work lives in packages/expo-resource-fetcher, which already carries its own react-native@0.85.3 / expo@^56 devDeps. That's the right place for it, since File.createDownloadTask is SDK-56-only.
  • peerDependencies are react-native: "*", so there's no install-time floor on consumers. The dev RN version is effectively the only thing pinning what we type-check and test core against — bumping it to 0.85 silently drops our 0.81 coverage with nothing to catch a regression.
  • It works against this PR's own goal. expo-resource-fetcher/legacy exists for Expo SDK <56 users, who are on RN 0.81/0.83 — never 0.85. If core RNE is only developed against 0.85, the exact users /legacy is for are running a core that's no longer tested on their RN. Same for bare-resource-fetcher (untouched here, still peer react-native: "*" / dev 0.81.5) and apps/bare-rn (pinned 0.81.5).

Could we revert just those three devDep lines and keep the SDK-56 / RN-0.85 bumps scoped to the demo apps and expo-resource-fetcher? The core package staying on 0.81 is what makes the legacy + bare lanes meaningful.

While I agree that this PR maybe is not the best place to bump it, since it involves no internal changes I think opening up another PR is frankly a waste of time.

Also, I've seen Reanimated and RNGH bump to latest RN versions, so I think it might make sense to bump it. I guess it would make sense to use the latest version to keep up with eventual breaking changes as they come.

@msluszniak

Copy link
Copy Markdown
Member

Ok, as these are devDeps, we can indeed follow the latest version. Let's keep it.

@msluszniak msluszniak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A few code-level notes from a pass over the resource-fetcher refactor (separate from the devDep comment above). All non-blocking except possibly the first, which is a genuine question about the new download API's error behavior.

.then(async (downloadedFile) => {
// null means the task was paused before completion — resume() will continue it.
// missing handle means the task was canceled — cancel() already rejected.
if (!downloadedFile) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Question: does the new downloadAsync() reject on non-2xx responses (404/500), or can it resolve with a File containing the error body? The old implementation explicitly guarded result.status against OK/PARTIAL_CONTENT; here the only non-success case handled is null (pause). If a 404/500 body can resolve successfully, it gets moved to fileUri and cached as a "valid" model — and because handleRemote short-circuits on new File(fileUri).exists, every later fetch then returns the corrupt file (wasDownloaded: false) until a manual delete. If the new API doesn't reject on non-2xx out of the box, it'd be worth restoring an explicit status check (same applies to resume() in ResourceFetcher.ts).

// null means the task was paused before completion — resume() will continue it.
// missing handle means the task was canceled — cancel() already rejected.
if (!downloadedFile) {
throw new RnExecutorchError(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor readability: routing the paused case (downloadAsync() resolving null) through throw DownloadInterrupted just to swallow it in .catch when status === PAUSED is a little hard to follow. An explicit early return here when paused (mirroring how resume() handles it in ResourceFetcher.ts) would avoid constructing a throwaway error.

await downloadedFile.move(new File(fileUri));
} catch (error) {
downloads.delete(source);
reject(error);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Consistency nit: the download .catch below wraps failures in RnExecutorchError(ResourceFetcherDownloadFailed, …, cause), but this move-failure path rejects with the raw error. Worth wrapping it too so callers get a consistent error code/shape.

export async function checkFileExists(fileUri: string) {
const fileInfo = await getInfoAsync(fileUri);
return fileInfo.exists;
return new File(fileUri).exists;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

checkFileExists looks unused now — all call sites were inlined to new File(...).exists, so this could be removed. If it's kept, worth noting it's file-only: new File(dir).exists isn't equivalent to the old getInfoAsync for directory paths (which is exactly why createDirectoryIfNoExists correctly switched to Directory).

@@ -0,0 +1,195 @@
/**

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This legacy/ tree duplicates ~500 lines from src/ (ResourceFetcher / handlers / Utils), differing only in the expo-file-system import and a few call shapes. The pause/resume/cancel orchestration is subtle and now lives in two copies that will drift over time. Could the orchestration be shared (some already lives in BaseResourceFetcherClass) with just the FS primitives injected via a thin adapter? At minimum a cross-link comment + a "keep these in sync" note would help.

typeof (File as unknown as { createDownloadTask?: unknown })
.createDownloadTask !== 'function'
) {
throw new RnExecutorchError(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This top-level throw runs at module-eval (bundle load / app start), not at fetcher use, and it sits between two import groups (imports are hoisted, so it runs after all of them regardless of placement). Consider moving the check into the class constructor / a lazy init so the module stays import-safe and the error surfaces at instantiation with a cleaner stack. Purely stylistic.

@msluszniak msluszniak linked an issue Jun 11, 2026 that may be closed by this pull request
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3rd party package Issue related to 3rd party packages, but not ExecuTorch, e.g. Expo dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resource fetcher - aggregated issue Rewrite expo-resource-fetcher to the new API

2 participants