Skip to content

[pull] main from expo:main#648

Merged
pull[bot] merged 12 commits intocode:mainfrom
expo:main
Mar 4, 2026
Merged

[pull] main from expo:main#648
pull[bot] merged 12 commits intocode:mainfrom
expo:main

Conversation

@pull
Copy link

@pull pull bot commented Mar 4, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

vonovak and others added 12 commits March 4, 2026 11:36
# Why

the docs don't mention the library can be used outside of Apple's
platforms

TODO update release notes after merge

# How

  - Updated platform support from iOS-only to iOS, Android, web, tvOS
- Added docs for cross-platform name object syntax with per-platform
symbol names
  - Added docs for fallback prop
  - Added docs for weights and the androidWeights import pattern
  - Added links to browse SF Symbols and Material Symbols catalogs
  - Updated package.json description and README

# Test Plan

- bare expo

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
-->

- [ ] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
# Why
Removes some duplication

# How
- `useAudioRecorderState` is identical between web and native.
- Updated some incorrectly documented functions
- `isBuffering` was identical between players and playlists

# Test Plan
Bare expo
# Why
Similar to android, add `Playable` protocol so players and playlists can be treated the same in the component registry

# Test Plan
Bare expo
# Why
Further reduce the duplication between players and playlists

# How
Add a base class the has all the common functionality 

# Test Plan
Bare expo
…2887)

# Why

Legacy FileSystem supported copying from SAF and other `content://` URIs. This is very limited or missing in the new file system - it relies heavily on `javaFile` which is usually not available for content URIs.

# How

- Added non-SAF `ContentProviderFile` implementation of `UnifiedFileInterface` to let FileSystem support some subset of common operations
- Created a delegate to the unified interface that handles copying and moving between JavaFile, SAF, Asset and ContentProviderFile
  - handled edge cases and optimizations (assets stay read-only; JavaFiles are atomically copied using native Java File APIs)
  - to avoid spaghetti of nested if-else, came up with kinda over-engineered solution with two sealed classes:
    -  `CopyMoveStrategy` to handle each file type that can be copied and prepare destination config
    -  `DestinationSink` to handle creating the destination file and writing to it
- Introduced a `overwrite` boolean that can be easily exposed to the JS API in a later PR (legacy filesystem was always overwriting, new does not do it).

# Test Plan

- New Test Suite tests
- Added instrumentation tests

# Checklist

- [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin).
- [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
# Why

Noticed that copy/move behavior was different in legacy file-system. `FileSystemLegacy.copyAsync()` defaulted to overwrite the destination ([iOS](https://github.com/expo/expo/blob/bd3a47afff01ac3c082481267f6b7beecc61ffa4/packages/expo-file-system/ios/Legacy/EXFileSystemLocalFileHandler.m#L62), [Android](https://github.com/expo/expo/blob/bd3a47afff01ac3c082481267f6b7beecc61ffa4/packages/expo-file-system/android/src/main/java/expo/modules/filesystem/legacy/FileSystemLegacyModule.kt#L312)), while the new file-system throws if destination exist

Also, this is useful in some scenarios when we do a copy repeatedly and want to overwrite previous content.

# How

Android: passed the flag introduced in #42887 to JS
iOS: Added logic to remove the destination if it exists before copying/moving

# Test Plan

- NCL in #43083
- Added more test-suite tests

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
-->

- [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin).
- [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
…2983)

# Why

Resolves #42273

Adds `FileHandle` support for SAF URIs, effectively enabling streaming read/write (`File.readableStream()` and `File.writableStream()`)

# How

- Updated FileHandle to be openable for Android SAF files too.
- I had to introduce `FileMode` ("r", "rw", etc) argument to the `File.open()` function because SAF files cannot be easily opened in "rw" mode
  - It's backwards compatible - for iOS and `file://` URIs the mode defaults to "rw", maintaining previous behavior

### Technical considerations

<details>
<summary>Unfold</summary>

Contrary to most LLMs initially suggest, it's not possible to use `contentResolver.openFileDescriptor()` -> create `FileChannel` with read-write permission.

> LLMs for some reason think that there is a `RandomAccessFile(fd: FileDescriptor, mode: String)` constructor, which is not true. The first argument is Java `File`, not `FileDescriptor`.

#### The working way

The "correct" way that works is what I implemented

```kotlin
val parcelFileDesctiptor = contentResolver.openFileDescriptor(uri, "rw") // this "rw" doesn't give much
// can be either FileInputStream or FileOutputStream
val stream = when (mode) { 
  READ -> FileInputStream(pfd.fileDescriptor) 
  WRITE -> FileOutputStream(pfd.fileDescriptor) 
}
val channel: FileChannel = stream.channel
```


#### What else I have tried

Theoretically, `FileOutputStream` is a thin wrapper over the file descriptor that is already opened in "rw" mode and owned by `pfd`. So `FileOutputStream(pfd.fileDescriptor).channel` should operate on the same underlying rw-capable descriptor. Unfortunately, when looking into Android source code, `FileOutputStream` [constructs](https://android.googlesource.com/platform/libcore.git/+/refs/heads/android16-release/ojluni/src/main/java/java/io/FileOutputStream.java#532) a [FileChannelImpl](https://android.googlesource.com/platform/libcore/+/refs/heads/android16-release/ojluni/src/main/java/sun/nio/ch/FileChannelImpl.java#104) that has `readable` set to `false` (and analogously `writable=false` for `FileInputStream`)

#### Alternatives

- Use low-level `android.system.Os` calls instead of `FileChannel`. 
  ```kotlin
   val pfd = contentResolver.openFileDescriptor(uri, "rw")?.use { pfd ->
   val fd = pfd.fileDescriptor
     // Write something at a specific offset
     val dataToWrite = "Hello SAF".toByteArray()
     Os.lseek(fd, 123, OsConstants.SEEK_SET)
     Os.write(fd, dataToWrite, 0, dataToWrite.size)

     // Read it back
     Os.lseek(fd, 123, OsConstants.SEEK_SET)
     val buffer = ByteArray(9)
     val bytesRead = Os.read(fd, buffer, 0, 9)
   }
   ```
  - low-level, more error prone
  - we lose the ability of having `SharedRef<FileChannel>` which is (theoretically) breaking
- Write similiar abstraction in JNI / C++ 
  - Sounds like an overkill
  - 

</details>

# Test Plan

- NCL in #43083
- Added more test-suite tests

# Checklist

- [x] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin).
- [x] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
# Why

I needed a playground to test file operations

# How

Added options to FileSystemScreen
- Create a file in local / cache dir
- Load file from various sources: File picker, Document picker, Media Library, Contacts photo, load bundled asset
- Display file info
- `read()` and `write()` with various formats
- FileHandle operations (open/close, read/write/seek)
- Directory operations
- Copy/move files/directories, also cross-location (e.g. cache dir -> user-picked directory)
- [Android] Intent operations, content URI
- Deletion

# Test Plan

Played with all options on iOS and Android


https://github.com/user-attachments/assets/720e12eb-46b8-4d6c-a283-fd6670415e8d
# Why

Starting from React Native 0.84 HermesV1 is enabled by default. For that
reason, we must update our `useHermesV1 ` logic to check for legacy
hermes instread

# How

Update expo-build-properties `useHermesV1` check for legacy hermes


# Test Plan


In sandbox app add the following to app.json

```json
"plugins": [
      [
        "expo-build-properties",
        {
          "ios": {
            "useHermesV1": false,
            "buildReactNativeFromSource": true
          },
          "android": {
            "useHermesV1": false,
            "buildReactNativeFromSource": true
          }
        }
      ]
    ],
```
Then set your package.json to resolve the experimental version of Hermes
V1 compiler
```json
"resolutions": { "hermes-compiler": "0.15.0" }
```

To confirm that the app is running legacy Hermes, check if
`HermesInternal.getRuntimeProperties()['OSS Release Version'];` return
`"0.15.0"`
 

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
-->

- [ ] I added a `changelog.md` entry and rebuilt the package sources
according to [this short
guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
# Why

Rebase oversight in #42983

# How

`./gradlew :expo-file-system:spotlessApply`

# Test Plan

CI

<!-- disable:changelog-checks -->
@pull pull bot locked and limited conversation to collaborators Mar 4, 2026
@pull pull bot added the ⤵️ pull label Mar 4, 2026
@pull pull bot merged commit 3420b71 into code:main Mar 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants