Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/cross_file/cross_file_darwin/AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Below is a list of people and organizations that have contributed
# to the Flutter project. Names should be added to the list like so:
#
# Name/Organization <email address>

Google LLC
3 changes: 3 additions & 0 deletions packages/cross_file/cross_file_darwin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 1.0.0

* Initial release.
18 changes: 18 additions & 0 deletions packages/cross_file/cross_file_darwin/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Contributing

## `ffigen`

This package uses [ffigen](https://pub.dev/packages/ffigen) to call Foundation
methods in addition to the standard Flutter plugin structure. To add new
functionality to the FFI interface, update `tool/ffigen.dart`, then run:

```bash
dart run tool/ffigen.dart
```

### Configuration philosophy

This package intentionally uses very strict filtering rules to include only the
necessary methods and functions. This is partially to keep the package small,
but mostly to avoid unnecessarily generating anything that requires additional
native code helpers.
25 changes: 25 additions & 0 deletions packages/cross_file/cross_file_darwin/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Copyright 2013 The Flutter Authors

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 changes: 25 additions & 0 deletions packages/cross_file/cross_file_darwin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# cross\_file\_darwin

The Darwin implementation of [`cross_file`][1].

This plugin provides support for traditional file systems and Apple's app sandbox, security
scoped resources, and PhotoKit assets.

When using the `FileSystem` implementations on iOS or macOS, this implementation returns the
implementation created by `CrossFileIO`.

For the `ScopedStorage` implementation, this implementation uses `dart:io` for typical file
interactions while providing access to security scoped resource features. See
https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller#Work-with-external-documents.

## Usage

This package is [endorsed][2], which means you can simply use `cross_file`
normally. This package will be automatically included in your app when you do,
so you do not need to add it to your `pubspec.yaml`.

However, if you `import` this package to use any of its APIs directly, you
should add it to your `pubspec.yaml` as usual.

[1]: https://pub.dev/packages/cross_file
[2]: https://flutter.dev/to/endorsed-federated-plugin
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import Testing

@testable import cross_file_darwin

#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif

// This plugin doesn't include native unit tests because getting access to files that are part of
// iOS App Sandbox requires user interactions outside of the app.
//
// This also serves as a placeholder for a native test because the CI of flutter/packages requires
// native tests for all iOS plugins.
struct CrossFileDarwinTests {
@Test func placeHolderTest() throws {
#expect(true == true)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
#
Pod::Spec.new do |s|
s.name = 'cross_file_darwin'
s.version = '0.0.1'
s.summary = 'A cross platform file Plugin for Flutter.'
s.description = <<-DESC
A Flutter plugin that provides access to files.
Downloaded by pub (not CocoaPods).
DESC
s.homepage = 'https://github.com/flutter/packages'
s.license = { :type => 'BSD', :file => '../LICENSE' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :http => 'https://github.com/flutter/packages/tree/main/packages/cross_file/cross_file_darwin' }
s.documentation_url = 'https://pub.dev/packages/cross_file'
s.source_files = 'cross_file_darwin/Sources/**/*.{m,swift}'
s.ios.dependency 'Flutter'
s.osx.dependency 'FlutterMacOS'
s.ios.deployment_target = '13.0'
s.osx.deployment_target = '10.15'
s.resource_bundles = {'cross_file_darwin_privacy' => ['cross_file_darwin/Sources/cross_file_darwin/Resources/PrivacyInfo.xcprivacy']}
s.ios.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.osx.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
s.xcconfig = {
'LIBRARY_SEARCH_PATHS' => '$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)/ $(SDKROOT)/usr/lib/swift',
'LD_RUNPATH_SEARCH_PATHS' => '/usr/lib/swift',
}
s.swift_version = '5.0'
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// swift-tools-version: 5.9

// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import PackageDescription

let package = Package(
name: "cross_file_darwin",
platforms: [
.iOS(.v13),
.macOS(.v10_15),
],
products: [
.library(name: "cross-file-darwin", targets: ["cross_file_darwin"])
],
dependencies: [
.package(name: "FlutterFramework", path: "../FlutterFramework")
],
targets: [
.target(
name: "cross_file_darwin",
dependencies: [
"cross_file_darwin_objc",
.product(name: "FlutterFramework", package: "FlutterFramework"),
],
resources: [
.process("Resources")
]
),
.target(
name: "cross_file_darwin_objc",
dependencies: [],
sources: [
"ffi_bindings.g.m"
],
publicHeadersPath: "include",
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import Foundation
import Photos

/// Class for handling an instance of asynchronously reading bytes from an asset.
class AssetResourceReader {
/// Begin reading bytes from PHAssetResource with `localIdentifier`.
func openRead(localIdentifier: String, delegate: AssetResourceReaderDelegate) -> Bool {
let assets = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
Comment on lines +11 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The delegate parameter passed to openRead is never assigned to self.delegate. As a result, self.delegate remains nil, and the callbacks onDataReceived and onCompletion are never triggered.

  func openRead(localIdentifier: String, delegate: AssetResourceReaderDelegate) -> Bool {
    self.delegate = delegate
    let assets = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)

@bparrishMines bparrishMines Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

very good catch. This is the down side of not being able to write tests when the api requires out of app user interactions.

if let asset = assets.firstObject {
let resources = PHAssetResource.assetResources(for: asset)
if let resource = resources.first {
let resourceManager = PHAssetResourceManager.default()

let options = PHAssetResourceRequestOptions()
options.isNetworkAccessAllowed = true

resourceManager.requestData(for: resource, options: options) { data in
DispatchQueue.main.async {
delegate.onDataReceived(reader: self, bytes: data)
}
} completionHandler: { error in
DispatchQueue.main.async {
delegate.onCompletion(reader: self, error: error?.localizedDescription)
}
}

return true
}
}

return false
}

/// Read all bytes from asset with identifier.
func readBytes(localIdentifier: String, completion: @escaping (Result<Data, Error>) -> Void) {
let assets = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
if let asset = assets.firstObject {
let manager = PHImageManager.default()
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = true
manager.requestImageDataAndOrientation(for: asset, options: options) { (data, _, _, _) in
if let data = data {
completion(.success(data))
} else {
completion(
.failure(
PigeonError(
code: "XFileNullDataError",
message: "Failed to read bytes from asset with identifier: \(localIdentifier)",
details: nil)))
}
}
} else {
completion(
.failure(
PigeonError(
code: "XFileMissingAssetError",
message: "Failed to find asset with identifier: \(localIdentifier)",
details: nil)))
}
}
}

/// Handles bytes read from calling `AssetResourceReader.startRead`.
protocol AssetResourceReaderDelegate: NSObjectProtocol {
/// Provides the requested data.
///
/// Called multiple times until all bytes are received.
///
/// Corresponds to the `dataReceivedHandler` parameter for
/// `PHAssetResourceManager.requestDataForAssetResource`.
func onDataReceived(reader: AssetResourceReader, bytes: Data)

/// Start reading bytes from the asset with the given identifier.
///
/// Returns false if the resource for the identifier could not be accessed or
/// found. Returns true if the resource can be successfully accessed.
///
/// Bytes are passed to `onDataReceived` and the request is completed when
/// `onCompletion` is called.
func onCompletion(reader: AssetResourceReader, error: String?)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import Foundation

#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif

private class AssetResourceReaderDelegateImpl: NSObject, AssetResourceReaderDelegate {
let api: PigeonApiProtocolAssetResourceReaderDelegate
unowned let registrar: ProxyAPIRegistrar

init(api: PigeonApiProtocolAssetResourceReaderDelegate, registrar: ProxyAPIRegistrar) {
self.api = api
self.registrar = registrar
}

func onDataReceived(reader: AssetResourceReader, bytes: Data) {
let data = FlutterStandardTypedData(bytes: bytes)
registrar.dispatchOnMainThread { onFailure in
self.api.onDataReceived(pigeonInstance: self, bytes: data) { result in
if case .failure(let error) = result {
onFailure("AssetResourceReaderDelegate.onDataReceived", error)
}
}
}
}

func onCompletion(reader: AssetResourceReader, error: String?) {
registrar.dispatchOnMainThread { onFailure in
self.api.onCompletion(pigeonInstance: self, error: error) { result in
if case .failure(let error) = result {
onFailure("AssetResourceReaderDelegate.onCompletion", error)
}
}
}
}
}

/// Implementation of `PigeonApiDelegateAssetResourceReaderDelegate`.
class AssetResourceReaderDelegateAPIDelegate: PigeonApiDelegateAssetResourceReaderDelegate {
func pigeonDefaultConstructor(pigeonApi: PigeonApiAssetResourceReaderDelegate) throws
-> AssetResourceReaderDelegate
{
return AssetResourceReaderDelegateImpl(
api: pigeonApi, registrar: pigeonApi.pigeonRegistrar as! ProxyAPIRegistrar)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import Foundation

#if os(iOS)
import Flutter
#elseif os(macOS)
import FlutterMacOS
#else
#error("Unsupported platform.")
#endif

/// Implementation of `PigeonApiDelegateAssetResourceReader`.
class AssetResourceReaderAPIDelegate: PigeonApiDelegateAssetResourceReader {
func pigeonDefaultConstructor(pigeonApi: PigeonApiAssetResourceReader) throws
-> AssetResourceReader
{
return AssetResourceReader()
}

func openRead(
pigeonApi: PigeonApiAssetResourceReader, pigeonInstance: AssetResourceReader,
localIdentifier: String, delegate: AssetResourceReaderDelegate
) throws -> Bool {
return pigeonInstance.openRead(localIdentifier: localIdentifier, delegate: delegate)
}

func readBytes(
pigeonApi: PigeonApiAssetResourceReader, pigeonInstance: AssetResourceReader,
localIdentifier: String, completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void
) {
pigeonInstance.readBytes(localIdentifier: localIdentifier) { result in
switch result {
case .failure(let error):
completion(.failure(error))
case .success(let bytes):
completion(.success(FlutterStandardTypedData(bytes: bytes)))
}
}

}
}
Loading
Loading