external_storage 1.0.0
external_storage: ^1.0.0 copied to clipboard
Android & iOS storage plugin: storage volumes (SD card / USB UUID), file and directory operations, file system watching and storage permission handling.
Changelog #
All notable changes to the external_storage plugin will be documented in this file.
本文档记录了 external_storage 插件的所有重要更新。
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.0.0 - 2026-10-30 #
Added #
- iOS support (iOS 13+, CocoaPods and Swift Package Manager, privacy manifest included):
storage volume information (the app's Documents directory and its capacity),
openAppSettings, every file and directory operation, and file watching. iOS has no storage permission, so the permission checks returntrue. 新增 iOS 支持。 watch()returns aStream<WatchEvent>: watching starts on listen and stops on cancel, several watches can run at once, and the stream ends when the watched path is deleted or moved.WatchEventgainedwatchedPathandisDirectory. 新增基于 Stream 的watch()。WatchEventType.maskOf,WatchEventType.fromMaskandWatchEventType.allMask.- Native permission handling (Android 11+: All files access settings page; Android 6–10: runtime permissions). 原生权限处理。
StorageDevice.uuid,StorageDevice.descriptionandStorageDevice.isPrimary.- Methods returning data (
readFile,getFileInfo,calculateMD5, …) throw aPlatformException(OPERATION_FAILED) with the error message instead of a type error.
Changed #
- File and directory operations are implemented with
dart:ioand shared by Android and iOS instead of going through Kotlin. File contents no longer cross the platform channel, operations run concurrently, recursive listings / MD5 / directory sizes run in a background isolate, and the operations work in a plainflutter test. 文件和目录操作改用dart:io实现,两个平台共用。 - Events from
startWatchingnow report the full path of the changed entry instead of the watched directory. 监视事件现在给出发生变化的文件的完整路径。 getMimeTypechecks the file header (magic numbers) before the extension.FileInfo.canRead/canWrite/canExecuteuseaccess(2), likejava.io.File.moveDirectoryfalls back to copy + delete when moving across volumes (e.g. to an SD card).StorageDevice.nameuses the system's localized volume label when available;isReadOnlyreflects whether the volume is mounted read-only rather than whether the app may write to it.readFile(length: 0)returns no data (it used to read to the end of the file).getDirectorySizeno longer follows symbolic links.FileModeis deprecated: no API uses it and it clashes withdart:io'sFileMode.- A pending permission request survives the Activity being recreated by a configuration change.
- The plugin no longer declares
MANAGE_EXTERNAL_STORAGE(norREAD_MEDIA_*); apps that need Android 11+ full access must declare it themselves (see README). 插件不再替宿主声明MANAGE_EXTERNAL_STORAGE,需在 App 中自行声明。 - Android: AGP 9.1, Kotlin 2.4, compileSdk 36, Java 17. Dart: SDK ^3.8.0, Flutter >= 3.32.
New dependencies:
crypto,mime,ffi. - Removed duplicate internal Dart files (public API unchanged).
Fixed #
checkStoragePermissions,requestStoragePermissions,shouldShowRequestPermissionRationale,openAppSettings,checkPermission,getGrantedPermissions,stopAllWatching,getWatchedPathsandisWatchingthrewMissingPluginExceptionbecause they had no native implementation. 以上方法此前没有原生实现,调用即抛出MissingPluginException,现已全部实现。startWatching/stopWatching/stopAllWatchingreturned an empty result (the native side filled it in asynchronously);startWatchingwith noeventsnow watches all events instead of none.- Watching: a single file never produced events; directories moved into a recursively watched tree
were not watched; two watches on the same directory stopped each other (Android shares one inotify
descriptor per inode); combined inotify flags could make
WatchEventType.fromValuethrow. getAllStorageDevicescrashed on Android 7–10 (StorageVolume.directoryis API 30+).readFilecrashed on empty files / offsets past EOF and could return partial data.- Integer arguments (
offset,length,size) could throwClassCastExceptionon the native side. listDirectory(recursive: true)ignoredrecursive.getMimeTypealways returned an empty string.copyFileonto the same file truncated it;copyDirectory/moveDirectoryinto a subdirectory of the source recursed until the disk was full; symbolic link cycles could recurse forever.PathUtils.existsreturnedfalsefor directories,PathUtils.isAccessiblereturnedfalsefor empty directories, andPathUtils.hasIllegalCharstreated every path separator as illegal.
