expo-filesystem-android-external-storage
v1.0.3
Published
Expo native module for raw file system access on Android external storage
Maintainers
Readme
expo-filesystem-android-external-storage
An Expo Config Plugin and Native Module for accessing Android's External Storage (e.g. /sdcard/Documents/) directly using java.io.File, bypassing Expo FileSystem's scoped storage restrictions.
Why this package?
We built this because we faced significant limitations with standard approaches when developing TidGi-Mobile (a TiddlyWiki app that needs to sync with desktop):
- SAF (Storage Access Framework) is too slow: We need to sync thousands of small
.tidfiles. UsingDocumentFileand SAF APIs for batch operations was incredibly slow compared to direct file access. expo-file-systemis restricted: Even if you addMANAGE_EXTERNAL_STORAGEpermission to yourapp.json, the standardexpo-file-systemAPIs are scoped to the app's internal directory (or specific cache dirs). They generally cannot access arbitrary paths like/sdcard/Documents/TidGi/directly.- Permissions alone are insufficient: Simply adding the permissions below to
app.jsondoes not grantexpo-file-systemaccess to external storage due to its internal "scoped" design."permissions": [ "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.MANAGE_EXTERNAL_STORAGE" ] - Legacy API failures: We tried using
expo-file-system's legacy storage flags, but they often failed or were inconsistent across Android versions.
Therefore, we implemented this Native Module which uses standard java.io.File APIs in Kotlin. This requires:
- Expo Development Client: You cannot use Expo Go because this package contains custom native code. You must build a custom dev client or a production build (prebuild).
MANAGE_EXTERNAL_STORAGEPermission: The user must grant "All files access" in system settings.
Features
- Direct File Access: Read and write to any path the app has permission for.
- Bypass Scoped Storage: Works with absolute paths (e.g.
/storage/emulated/0/...). - Standard Operations:
exists,read,write,mkdir,list(recursive),delete. - Permission Helper: Check for
MANAGE_EXTERNAL_STORAGEstatus.
Installation
npm install expo-filesystem-android-external-storage
# or
pnpm add expo-filesystem-android-external-storageConfiguration
Android Manifest
This module requires the MANAGE_EXTERNAL_STORAGE permission to be effective for broad access.
Add this to your app.json / app.config.ts if you want Expo to add the permission to AndroidManifest.xml (though you might need a custom config plugin to add R.attr.requestLegacyExternalStorage or similar attributes if targeting Android 10).
Actually, this package is primarily the native implementation. You should ensure your app requests the permission at runtime (e.g. using Intent to open Settings).
Usage
import { ExternalStorage } from 'expo-filesystem-android-external-storage';
async function example() {
const path = '/sdcard/Documents/myfile.txt';
// Check existence
if (await ExternalStorage.exists(path)) {
console.log('File exists');
const content = await ExternalStorage.readFileUtf8(path);
} else {
// Write file
await ExternalStorage.writeFileUtf8(path, 'Hello World');
}
// List directory
const files = await ExternalStorage.readDir('/sdcard/Documents');
console.log('Files:', files);
}Expo Config Plugin
This package includes a config plugin to automatically add the MANAGE_EXTERNAL_STORAGE permission to your AndroidManifest.xml.
In app.json or app.config.ts:
{
"expo": {
"plugins": [
"expo-filesystem-android-external-storage"
]
}
}API
exists(path: string): Promise<boolean>
Checks if a file or directory exists.
getInfo(path: string): Promise<FileInfo>
Gets metadata (size, modification time, isDirectory).
readFileUtf8(path: string): Promise<string>
Reads a file as a UTF-8 string.
writeFileUtf8(path: string, content: string): Promise<void>
Writes a UTF-8 string to a file. Creates parent directories if needed.
readDir(path: string): Promise<string[]>
Lists files in a directory (non-recursive).
readDirRecursive(path: string): Promise<string[]>
Recursively lists all files, returning paths relative to the root. Skips .git, node_modules, etc.
mkdir(path: string): Promise<void>
Creates a directory and necessary parent directories.
deleteFile(path: string): Promise<void>
Deletes a file.
rmdir(path: string): Promise<void>
Deletes a directory recursively.
isExternalStorageManager(): Promise<boolean>
Checks if the MANAGE_EXTERNAL_STORAGE permission is granted (Android 11+).
License
MIT
