icloud_storage_plus 2.1.3
icloud_storage_plus: ^2.1.3 copied to clipboard
Flutter plugin for iCloud document storage that syncs across devices with safe file operations and Files app integration.
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased] #
2.1.3 - 2026-05-03 #
Fixed #
- iOS and macOS container operations now route through a shared
UbiquityContainerResolver, includinggather, so transientFileManager.url(forUbiquityContainerIdentifier:)nil responses retry before surfacing a container-access failure. - Container resolution retry delays now preserve
Task.sleepcancellation instead of swallowing it, so cancelled calls stop before issuing an unnecessary second container lookup. - Native iOS and macOS metadata query sessions are retained for their full
query lifetimes, preventing observers from being released before
NSMetadataQuerycompletes. - Native write failures now preserve structured path and native error context for Dart typed exceptions without exposing full local filesystem paths.
2.1.2 - 2026-04-23 #
Fixed #
- iOS
readInPlaceandreadInPlaceBytesnow marshal the post-download continuation back onto the main actor before invokingreadInPlaceDocument/readInPlaceBinaryDocument. The2.1.0async rewrite ofwaitForDownloadCompletioninadvertently removed theDispatchQueue.mainhop that the callback-based waiter guaranteed, lettingUIDocument.open(completionHandler:)be called from the Swift cooperative pool. This restores the1.2.2invariant thatUIDocumentwork runs under the main queue per Apple's completion-handler contract and avoids the_os_object_retain"Resurrection of an object" crash that motivated that fix. - macOS
uploadFile,readInPlace,readInPlaceBytes,writeInPlace, andwriteInPlaceBytesnow run theirTaskbodies on the main actor. Because the macOSFlutterMethodChannelis registered without a background task queue,FlutterResultmust be invoked on the main thread; the previousTask { [self] in ... }blocks resumed on the cooperative pool afterawait, causingresult(...)to be called off-main in preflight error paths.
2.1.1 - 2026-04-22 #
Fixed #
- Added
WriteEntrypointPreflight.swiftto the explicit iOS pluginPackage.swiftsource list so consumer builds that rely on the plugin's Swift package manifest can compile the2.1.xwrite-path preflight helper. - This is a packaging hotfix only. The Dart API and native write-path behavior
introduced in
2.1.0are unchanged.
2.1.0 - 2026-04-22 #
Non-breaking behavior upgrade: writeInPlace becomes symmetric with
readInPlace by proactively downloading non-current iCloud items and
resolving unresolved conflict versions before the coordinated replace.
Public Dart API unchanged.
Added #
- Shared
WriteEntrypointPreflighthelpers and foundation tests on iOS and macOS to move write-path container lookup and parent-directory creation off the entry thread before coordinated writes begin. - Typed Dart mapping for native
invalidArgumentwrite failures viaICloudInvalidArgumentException.
Changed #
writeInPlaceand the binary / streaming overwrite paths now proactively callstartDownloadingUbiquitousItemwhen the destination is ubiquitous and not current, waiting for the download using an interactive-write schedule (~32s max) before the coordinated replace.- Inside the coordinator write block, the overwrite path now calls
Apple's canonical conflict-resolution pattern
(
NSFileVersion.unresolvedConflictVersionsOfItem→replaceItem→isResolved = true→removeOtherVersionsOfItem) before invokingreplaceItemAt, symmetric with the existingreadInPlacebehavior. - The pre-flight
E_CONFLICT,E_NOT_DOWNLOADED, andE_DOWNLOAD_IN_PROGRESSerrors now fire only as last-resort signals: when auto-download or auto-resolution itself fails. Auto-resolution failures surface with a localized description containing "auto-resolution failed" while still mapping toICloudConflictExceptionon the Dart side. - Internal refactor: unified the four textual copies of
CoordinatedReplaceWriter.swiftinto a single source of truth per platform (iOS and macOS) shared via SPMtarget.sources. - Internal refactor: extracted
waitForDownloadCompletionandresolveUnresolvedConflictsas sharedasync throwshelpers. IntroducedDownloadSchedule.interactiveWriteandDownloadSchedule.backgroundReadnamed configs so read and write paths parameterize the same waiter rather than diverge. ICloudDocument.resolveConflicts()(iOS) and the equivalent macOS observer both call the shared resolver; the duplicate implementation on iOS has been removed.listContentson iOS and macOS now does less repeated work inside the directory-enumeration loop by reusing the key set, reusing the parent relative path, and skipping hidden files before metadata lookup.- README, package metadata, and publish/package wiring were updated to match the shipped 2.1.x source layout and write-path contract.
Fixed #
- iOS and macOS overwrite-path completion handlers now preserve structured
timeout mapping so download-wait timeouts surface as
ICloudTimeoutException/E_TIMEOUTinstead of degrading to generic native failures. - CocoaPods packaging now explicitly includes the shared foundation sources needed by the coordinated overwrite implementation.
2.0.0 - 2026-04-09 #
Breaking release that hardens the Dart API contract around known-path metadata, typed request/response failures, and coordinated overwrite behavior on iOS and macOS.
BREAKING CHANGES #
- Removed the old typed
getMetadata()API in favor ofgetItemMetadata(). - Structured native request/response failures now map to typed
ICloudOperationExceptionsubclasses across the Dart API. getDocumentMetadata()remains the raw metadata escape hatch and preserves rawPlatformExceptionbehavior.
Added #
ICloudItemMetadataas the typed known-path metadata model returned bygetItemMetadata().- Typed request/response exception mapping for structured native payloads, including container access, not found, conflict, download-in-progress, item not downloaded, and timeout cases.
Changed #
- README, example code, and public Dart doc comments now document the
2.0.0contract explicitly, including the separation betweenICloudItemMetadata,ICloudFile, and rawgetDocumentMetadata()payloads. - Transfer-progress streams continue to emit
PlatformException-based error payloads in2.0.0; only request/response APIs use the new typed exception mapping. - README, changelog, and public Dart doc comments now describe the final iOS and macOS coordinated replacement behavior for existing-destination writes and copies.
- On iOS and macOS, file-write overwrite APIs and
copy()now document separate existing-destination semantics: file writes target files only, whilecopy()preserves file-or-directory copy behavior. - The iOS and macOS coordinated replacement logic now has standalone Foundation-level Swift test seams, with helper XCTest coverage for overwrite and existing-destination copy replacement behavior.
- Repository documentation now points to the hosted DeepWiki site instead of
keeping a checked-in export under
doc/deepwiki/.
Fixed #
- iOS and macOS existing-file
writeDocument,writeInPlace, andwriteInPlaceBytesnow stage replacement content outside the ubiquity container and replace the destination through coordinated atomic replacement. - iOS and macOS keep the
1.2.2download-wait completion fix that dispatchesUIDocumentcompletion back ontoDispatchQueue.main, avoiding the_os_object_retainresurrection crash from short-lived local queues. - On iOS and macOS, file-write overwrite APIs now reject existing directory destinations instead of replacing them.
- On iOS and macOS, existing ubiquitous items must be
.currentbefore replacement, and.downloadedis now rejected as not yet replace-safe. - iOS and macOS
copy()now keep existing destinations inside coordinated atomic replacement flows instead of removing the destination before copying.
1.2.2 - 2026-03-30 #
Fixed #
- iOS and macOS download-wait completion no longer uses a local
DispatchQueue. The short-lived queue could be deallocated beforeUIDocument.openWithCompletionHandler:finished retaining it (via the deprecateddispatch_get_current_queuecall in UIKit internals), causing an_os_object_retaincrash with "API MISUSE: Resurrection of an object". Completion is now dispatched onDispatchQueue.main, which is consistent with UIDocument's own completion-handler contract.
1.2.1 - 2026-03-27 #
Changed #
- iOS method-channel filesystem work now uses Flutter's background task queue
when that queue is available. Container lookup, iCloud path preflight, and
UIDocumentinitialization stay coordinated but no longer block the UI thread during in-place reads and writes on supported runtimes.
Fixed #
- iOS and macOS metadata query update handling no longer depends on
DispatchQueue.main.syncfor event-channel state checks, reducing deadlock risk when iCloud change notifications arrive while other native work is in flight. - Event stream state on iOS and macOS is now synchronized for cross-queue access, which avoids races between cancellation, progress delivery, and metadata updates.
- iOS download watchdog startup now schedules its initial timeout on the main run loop even when the method channel handler starts on a background task queue, preventing stalled in-place reads from hanging indefinitely.
- iOS and macOS download completion/cancellation paths now use a synchronized
single-fire completion gate, preventing double
FlutterResultdelivery when cancellation races with native completion.
1.2.0 - 2026-03-09 #
Added #
listContents()API for immediately-consistent container listings usingFileManager.contentsOfDirectorywith URL resource values. Unlikegather()(which reads the Spotlight metadata index),listContents()reflects filesystem mutations (rename, delete, copy) immediately.ContainerItemmodel withrelativePath,downloadStatus,isDownloading,isUploaded,isUploading,hasUnresolvedConflicts,isDirectory, and a convenienceisDownloadedgetter.- iCloud placeholder file resolution: both iOS (
.originalName.icloudstubs) and macOS Sonoma+ (APFS dataless files) are handled transparently —listContentsreturns the real filename and accurate download status. - Hidden file filtering:
listContentssuppresses system files (.DS_Store,.Trash, etc.) by filtering entries whose resolved name starts with..
Changed #
ICloudFiledartdoc now cross-referencesContainerItemand explains the eventual-consistency distinction.GatherResultdartdoc expanded to describeinvalidEntriespurpose.- Fixed typo in
InvalidArgumentExceptiondoc comment ("ued" → "used"). - README expanded with
listContentsdocumentation,gathervslistContentscomparison table, iCloud placeholder files section, andContainerItemmodel reference.
1.1.1 - 2026-02-14 #
Fixed #
- GitHub Actions automated publishing trigger for tags like
1.2.3(novprefix). - Remove example ephemeral LLDB helper files that were causing
dart pub publishvalidation warnings.
1.1.0 - 2026-02-13 #
Added #
- Swift Package Manager support for iOS and macOS (Flutter 3.24+ opt-in).
Changed #
- Native iOS/macOS sources are now packaged under
Sources/icloud_storage_plus/for SwiftPM compatibility (CocoaPods support remains via the podspecs). - Example apps now use Flutter's SwiftPM plugin integration (no CocoaPods
Podfiles in the example projects).
1.0.1 - 2026-02-11 #
Fixed #
- Avoid reading
NSMetadataItemoff the query thread by runningNSMetadataQueryon a dedicated operation queue and disabling updates during snapshot reads. - Ensure
relativePathgeneration is path-boundary-aware (avoid prefix-collision edge cases).
Changed #
- Clarify benchmark documentation around
standardizedFileURLbehavior.
1.0.0 - 2026-02-04 #
Major API update with path-based transfers for large files, coordinated in-place read/write APIs for small files, and a documentation overhaul.
BREAKING CHANGES #
Transfer API: file-path based for large files
Byte-based transfer APIs have been removed in favor of file-path methods. Large file content is no longer sent over platform channels.
Removed: upload(), download(), and related byte/JSON helpers.
New: uploadFile() and downloadFile() using local paths plus
cloudRelativePath.
Migration:
- Write data to a local file in Dart.
- Call
uploadFile(localPath, cloudRelativePath). - To read, call
downloadFile(cloudRelativePath, localPath)and read the local file in Dart.
gather() now returns GatherResult
gather() now returns a GatherResult containing:
files: parsedICloudFileentriesinvalidEntries: entries that could not be parsed (helps debug malformed metadata payloads)
ICloudFile metadata shape and nullability
ICloudFile now:
- includes
isDirectory: bool(directories are returned by metadata APIs) - may return
nullfor some fields when iCloud metadata is unavailable or the entry represents a directory (for examplesizeInBytes)
Directory detection behavior
documentExists() and getMetadata() return true/non-null for both files and
directories. Filter directories explicitly if your code expects only files.
Platform requirements updated
Minimum deployment targets match Flutter 3.10+:
- iOS: 13.0
- macOS: 10.15
Internal channel name change
The native method channel name is icloud_storage_plus (was icloud_storage).
Linting package change
Dev linting moved to very_good_analysis.
Added #
- File-path transfer methods:
uploadFile()(local → iCloud container)downloadFile()(iCloud container → local)
- Coordinated in-place access for small files:
readInPlace()/writeInPlace()(String, UTF-8)readInPlaceBytes()/writeInPlaceBytes()(Uint8List)- Optional
idleTimeouts+retryBackoffto control download watchdog/retry behavior; stalled downloads surfaceE_TIMEOUT.
- Convenience
rename()API (implemented in Dart viamove()). - Additional iCloud sync-state fields on
ICloudFile:downloadStatus,isDownloadingisUploading,isUploadedhasUnresolvedConflicts
- New public error code constants:
PlatformExceptionCode.initializationError(E_INIT)PlatformExceptionCode.timeout(E_TIMEOUT)
- Documentation overhaul:
- README updated to match the real API surface and semantics
- DeepWiki badge added to the README
- DeepWiki exported into
doc/for GitHub navigation - Added
scripts/fix_deepwiki_links.pyto keep exported docs linkable - Old
doc/research/plans removed (replaced by short notes underdoc/notes/)
Changed #
- Structural operations (
delete,move,copy) use coordinated file URL operations (NSFileCoordinator) rather than relying on metadata queries. - Existence and metadata (
documentExists,getDocumentMetadata) use direct filesystem checks (FileManager / URL resource values) rather than metadata queries. documentExists()is a filesystem existence check; it does not force a download. Usegather()for a remote-aware view of container contents.- Transfer progress streams deliver failures as
ICloudTransferProgressType.errordata events (not streamonError).
Fixed #
gather()now verifies the event channel handler exists before registering query observers (prevents leaked observers on early-return).getDocumentMetadata()now serializes download status keys as strings (.rawValue) for correct transport to Dart.- Dart relative-path validation accepts trailing slashes so directory paths from
metadata can be reused directly in operations like
delete(),move(),rename(), etc. uploadFile()/downloadFile()rejectcloudRelativePathvalues that end with/(directory-style paths).- macOS streaming writes use
.saveOperationfor existing files to avoid unintended “Save As” behavior.
Migration Guide (2.x → 1.0.0) #
- Replace byte-based reads/writes with local files +
uploadFile()/downloadFile(). - For small JSON/text stored in iCloud Drive, consider switching to in-place
access (
readInPlace/writeInPlace) for “transparent sync”. - Update call sites to handle directories via
ICloudFile.isDirectoryand add null checks for optional metadata fields. - If you use transfer progress, attach a listener immediately inside
onProgress(streams are listener-driven and may miss early events). - Run
flutter analyzeto address anyvery_good_analysislint findings.
Previous Releases #
For history prior to 1.0.0 (including the upstream lineage), see git history and the upstream repository: https://github.com/deansyd/icloud_storage