Skip to content

Commit 431ea69

Browse files
[cross_file] Updates cross_file to a package separated federated plugin (#11010)
Updates `cross_file` to a package separated federated plugin. ~~Keeping the package separation is still debatable.~~ Follows the general design outlined at https://flutter.dev/go/flutter-file-system Fixes #91869 Implementation is split into * **cross_file_io:** for use cases supportable by `dart:io` (all platforms except Web) * **cross_file_web**: for Web * Uses [Blobs](https://pub.dev/documentation/web/latest/web/Blob-extension-type.html) and [Blob URLs](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/blob). * Supports `package:web` [Files](https://pub.dev/documentation/web/latest/web/File-extension-type.html) (subclass of `Blob`. * **cross_file_android:** for Android Scoped Storage * Uses the high level abstraction of [DocumentFile](https://developer.android.com/reference/androidx/documentfile/provider/DocumentFile) to interact with [content uris](https://developer.android.com/reference/android/content/ContentUris). * Uses Kotlin. * **cross_file_darwin:** for iOS and macOS App Sandbox * Creates implementation for security scoped URI that call: * Adds support to call [URL.startAccessingSecurityScopedResource](https://developer.apple.com/documentation/foundation/nsurl/startaccessingsecurityscopedresource()) and [URL.stopaccessingsecurityscopedresource](https://developer.apple.com/documentation/foundation/nsurl/stopaccessingsecurityscopedresource()). * **Future Work**: Adds support to bookmark URLs following: https://developer.apple.com/documentation/Security/accessing-files-from-the-macos-app-sandbox#Persist-file-access-with-security-scoped-URL-bookmarks * Creates implementation for accessing an asset with PhotoKit. ## Pre-Review Checklist **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent fbc80a6 commit 431ea69

146 files changed

Lines changed: 5900 additions & 1029 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/dependabot.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ updates:
3131
- /packages/camera/camera_android/example/android/app
3232
- /packages/camera/camera_android_camerax/example/android/app
3333
- /packages/camera/camera/example/android/app
34+
- /packages/cross_file/cross_file/example/android/app
3435
- /packages/cross_file/cross_file_android/example/android/app
3536
- /packages/espresso/example/android/app
3637
- /packages/extension_google_sign_in_as_googleapis_auth/example/android/app

‎packages/cross_file/cross_file/CHANGELOG.md‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
1-
## NEXT
2-
3-
* Updates minimum supported SDK version to Flutter 3.41/Dart 3.11.
1+
## 0.4.0
2+
3+
* Updates `cross_file` to a package-separated federated plugin.
4+
* Adds `XDirectory` to access resource containers.
5+
* Adds implementation for file systems. See `FileSystemXFile` and `FileSystemXDirectory`.
6+
* Adds implementation for scoped storage APIs that use ephemeral URLS. See `ScopedStorageXFile`
7+
and `ScopedStorageXDirectory`.
8+
* **BREAKING CHANGES**:
9+
* Replaces `XFile(String path)` with `XFile.fileSystem({required String path})` or
10+
`FileSystemXFile(String path)`.
11+
* Removes `XFile.fromData`. `XFile`s can no longer be instantiated with a `Uint8List`. The web
12+
implementation can use `WebScopedStorageXFileCreationParams.fromBlob` to instantiate one with a
13+
`Blob`/`File`. See README to see how to access platform-specific features.
14+
* Removes `XFile.saveTo()`. `FileSystemXFile.writeAsBytes` has been added and the web
15+
implementation can use `WebScopedStorageXFileExtension.download`. See README to see how to
16+
access platform-specific features.
17+
* Removes `XFile.mimeType`.
18+
* Removes `XFile.path`. This has been replaced by `XFile.uri` or `FileSystemXFile.path`.
19+
* Changes `XFile.name` to a method that returns `Future<String>` rather than a field that returns
20+
`String`.
21+
* Adds Flutter as a dependency.
22+
* Updates minimum supported SDK version to Flutter 3.41/Dart 3.11.
423

524
## 0.3.5+5
625

‎packages/cross_file/cross_file/README.md‎

Lines changed: 108 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,134 @@
22

33
An abstraction to allow working with files across multiple platforms.
44

5+
<?code-excerpt path-base="example/lib"?>
6+
7+
[![pub package](https://img.shields.io/pub/v/cross_file.svg)](https://pub.dartlang.org/packages/cross_file)
8+
9+
| | Android | iOS | Linux | macOS | Web | Windows |
10+
|-------------|---------|---------|-------|--------|-----|-------------|
11+
| **Support** | SDK 24+ | iOS 13+ | Any | 10.15+ | Any | Windows 10+ |
12+
13+
## Overview
14+
15+
This package provides a unified API for interacting with resources across platforms through two
16+
primary implementation types:
17+
18+
### FileSystem
19+
20+
The `FileSystem` implementation represents resources on a traditional file system. It is used when
21+
resources are identified by standard file paths or `file://` URIs.
22+
23+
* **Classes**: `FileSystemXFile`, `FileSystemXDirectory`.
24+
* **Use Cases**: Desktop applications, app-private storage on mobile, or any environment where
25+
direct file system access is available.
26+
27+
### ScopedStorage
28+
29+
The `ScopedStorage` implementation represents resources that are managed or restricted by the
30+
platform. These resources are typically identified by platform-specific URIs rather than direct
31+
paths.
32+
33+
* **Classes**: `ScopedStorageXFile`, `ScopedStorageXDirectory`.
34+
* **Use Cases**: Android Content URIs, iOS Security-Scoped bookmarks, Web Object URLs, or Photo
35+
Library assets.
36+
* **Key Characteristic**: Access to these resources may be ephemeral or requires explicit lifecycle
37+
management (e.g., using `dispose()` or specific platform extensions).
38+
539
## Usage
640

7-
Import `package:cross_file/cross_file.dart`, instantiate a `XFile`
8-
using a path or byte array and use its methods and properties to
9-
access the file and its metadata.
41+
Instantiate a `XFile` using a uri or path and use its methods and properties to access the file and
42+
its metadata.
1043

1144
Example:
1245

13-
<?code-excerpt "example/lib/main.dart (Instantiate)"?>
46+
<?code-excerpt "readme_excerpts.dart (Instantiate)"?>
1447
```dart
15-
final file = XFile('assets/hello.txt');
48+
final file = XFile.fileSystem(path: 'assets/hello.txt');
1649
17-
print('XFile from disk:');
18-
print('- Path: ${file.path}');
19-
print('- Name: ${file.name}');
20-
print('- Length: ${await file.length()} bytes');
21-
print('- Content: ${await file.readAsString()}');
22-
```
50+
debugPrint('File information:');
51+
debugPrint('- URI: ${file.uri}');
52+
debugPrint('- Name: ${await file.name()}');
2353
24-
You will find links to the API docs on the [pub page](https://pub.dev/packages/cross_file).
25-
26-
## MIME types
54+
if (await file.exists()) {
55+
final String fileContent = await file.readAsString();
56+
debugPrint('Content of the file: $fileContent');
57+
}
58+
```
2759

28-
On web, `mimeType` reflects browser-provided file metadata when available.
29-
Native implementations do not infer MIME types from file paths or contents.
30-
On native platforms, `mimeType` returns the value explicitly passed to the
31-
`XFile` constructor or `XFile.fromData`, if any.
60+
You can find links to the API docs on the [pub page](https://pub.dev/documentation/cross_file/latest/).
3261

33-
## Web Limitations
62+
### Implementation-Specific Features
3463

35-
`XFile` on the web platform is backed by [Blob](https://api.dart.dev/be/180361/dart-html/Blob-class.html)
36-
objects and their URLs.
64+
Classes in this package contain an underlying platform implementation that provides features that
65+
are specific to an implementation.
3766

38-
It seems that Safari hangs when reading Blobs larger than 4GB (your app will stop
39-
without returning any data, or throwing an exception).
67+
To access implementation-specific features, start by adding the platform implementation packages to
68+
your app or package:
4069

41-
This package will attempt to throw an `Exception` before a large file is accessed
42-
from Safari (if its size is known beforehand), so that case can be handled
43-
programmatically.
70+
* **dart:io** [cross_file_io](https://pub.dev/packages/cross_file_io/install)
71+
* **Android Scoped Storage**: [cross_file_android](https://pub.dev/packages/cross_file_android/install)
72+
* **iOS/macOS App Sandbox**: [cross_file_darwin](https://pub.dev/packages/cross_file_darwin/install)
73+
* **Web**: [cross_file_web](https://pub.dev/packages/cross_file_web/install)
4474

45-
### Browser compatibility
75+
Next, add the imports of the implementation packages to your app or package:
4676

47-
[![Data on Global support for Blob constructing](https://caniuse.bitsofco.de/image/blobbuilder.png)](https://caniuse.com/blobbuilder)
77+
<?code-excerpt "readme_excerpts.dart (platform_imports)"?>
78+
```dart
79+
// Import for Darwin App Sandbox features.
80+
import 'package:cross_file_darwin/cross_file_darwin.dart';
81+
// Import for Web features.
82+
import 'package:cross_file_web/cross_file_web.dart';
83+
```
4884

49-
[![Data on Global support for Blob URLs](https://caniuse.bitsofco.de/image/bloburls.png)](https://caniuse.com/bloburls)
85+
Now, additional features can be accessed through the platform implementations. Classes
86+
`FileSystemXFile`, `FileSystemXDirectory`, `ScopedStorageXFile`, and `ScopedStorageXDirectory` pass
87+
their functionality to a class provided by the current platform. Below are a couple of ways to
88+
access additional functionality provided by the platform and is followed by an example.
5089

51-
## Testing
90+
1. Pass a creation params class provided by a platform implementation to a `fromCreationParams`
91+
constructor (e.g. `FileSystemXFile.fromCreationParams`, `ScopedStorageXFile.fromCreationParams`,
92+
etc.).
93+
2. Call methods on an implementation of a class by using `getExtension` method (e.g.
94+
`XFile.getExtension`, `XDirectory.getExtension`, etc.).
5295

53-
This package supports both web and native platforms. Unit tests need to be split
54-
in two separate suites (because native code cannot use `package:web`, and web code
55-
cannot use `dart:io`).
96+
Below is an example of using additional iOS/macOS and Web features for a `XFile`.
5697

57-
When adding new features, it is likely that tests need to be added for both the
58-
native and web platforms.
98+
<?code-excerpt "readme_excerpts.dart (platform_features)"?>
99+
```dart
100+
late final XFile file;
101+
102+
switch (CrossFile.implementation) {
103+
case CrossFileWeb():
104+
final params = WebScopedStorageXFileCreationParams.fromObjectUrl(
105+
objectUrl: 'blob:https://some/url:for/file',
106+
);
107+
file = ScopedStorageXFile.fromCreationParams(params);
108+
case CrossFileDarwin():
109+
file = ScopedStorageXFile.fromUri(Uri.file('/my/file.txt'));
110+
default:
111+
file = XFile.fileSystem(path: '/my/file.txt');
112+
}
113+
114+
await file
115+
.getExtension<SecurityScopedDarwinScopedStorageXFileExtension>()
116+
?.startAccessingSecurityScopedResource();
117+
118+
debugPrint(await file.readAsString());
119+
120+
if (file is ScopedStorageXFile) {
121+
await file.dispose();
122+
}
123+
```
59124

60-
### Native tests
125+
See https://pub.dev/documentation/cross_file_darwin/latest/cross_file_darwin/cross_file_darwin-library.html
126+
for more details on iOS/macOS App Sandbox features.
61127

62-
Tests for native platforms are located in the `x_file_io_test.dart`. Tests can
63-
be run with `dart test`.
128+
See https://pub.dev/documentation/cross_file_android/latest/cross_file_android/cross_file_android-library.html
129+
for more details on Android Scoped Storage features.
64130

65-
### Web tests
131+
See https://pub.dev/documentation/cross_file_io/latest/cross_file_io/cross_file_io-library.html
132+
for more details on `dart:io` features.
66133

67-
Tests for the web platform live in the `x_file_html_test.dart`. They can be run
68-
with `dart test -p chrome`.
134+
See https://pub.dev/documentation/cross_file_web/latest/cross_file_web/cross_file_web-library.html
135+
for more details on Web features.

‎packages/cross_file/cross_file/dart_test.yaml‎

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# cross_file_example
2+
3+
Demonstrates how to use the cross_file plugin.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
plugins {
2+
id("com.android.application")
3+
id("dev.flutter.flutter-gradle-plugin")
4+
}
5+
6+
android {
7+
namespace = "dev.flutter.packages.crossfileexample"
8+
compileSdk = flutter.compileSdkVersion
9+
ndkVersion = flutter.ndkVersion
10+
11+
compileOptions {
12+
sourceCompatibility = JavaVersion.VERSION_17
13+
targetCompatibility = JavaVersion.VERSION_17
14+
}
15+
16+
defaultConfig {
17+
applicationId = "dev.flutter.packages.crossfileexample"
18+
minSdk = flutter.minSdkVersion
19+
targetSdk = flutter.targetSdkVersion
20+
versionCode = flutter.versionCode
21+
versionName = flutter.versionName
22+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
23+
}
24+
25+
buildTypes {
26+
release {
27+
signingConfig = signingConfigs.getByName("debug")
28+
}
29+
}
30+
lint {
31+
disable.add("InvalidPackage")
32+
}
33+
}
34+
35+
kotlin {
36+
compilerOptions {
37+
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
38+
}
39+
}
40+
41+
flutter {
42+
source = "../.."
43+
}
44+
45+
dependencies {
46+
testImplementation("junit:junit:4.13.2")
47+
androidTestImplementation("androidx.test:runner:1.2.0")
48+
androidTestImplementation("androidx.test.espresso:espresso-core:3.2.0")
49+
api("androidx.test:core:1.4.0")
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package dev.flutter.packages.crossfileexample
6+
7+
import androidx.test.rule.ActivityTestRule
8+
import dev.flutter.plugins.integration_test.FlutterTestRunner
9+
import io.flutter.plugins.DartIntegrationTest
10+
import org.junit.Rule
11+
import org.junit.runner.RunWith
12+
13+
@DartIntegrationTest
14+
@RunWith(FlutterTestRunner::class)
15+
class MainActivityTest {
16+
@JvmField @Rule var rule = ActivityTestRule(MainActivity::class.java)
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<!-- The INTERNET permission is required for development. Specifically,
3+
the Flutter tool needs it to communicate with the running application
4+
to allow setting breakpoints, to provide hot reload, etc.
5+
-->
6+
<uses-permission android:name="android.permission.INTERNET"/>
7+
<application android:usesCleartextTraffic="true">
8+
<activity
9+
android:name="dev.flutter.packages.crossfileexample.DriverExtensionActivity"
10+
android:launchMode="singleTop"
11+
android:theme="@style/LaunchTheme"
12+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
13+
android:hardwareAccelerated="true"
14+
android:windowSoftInputMode="adjustResize">
15+
<meta-data
16+
android:name="io.flutter.Entrypoint"
17+
android:value="integrationTestMain" />
18+
</activity>
19+
</application>
20+
</manifest>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<application
3+
android:label="cross_file_example"
4+
android:name="${applicationName}"
5+
android:icon="@mipmap/ic_launcher">
6+
<activity
7+
android:name=".MainActivity"
8+
android:exported="true"
9+
android:launchMode="singleTop"
10+
android:taskAffinity=""
11+
android:theme="@style/LaunchTheme"
12+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
13+
android:hardwareAccelerated="true"
14+
android:windowSoftInputMode="adjustResize">
15+
<!-- Specifies an Android theme to apply to this Activity as soon as
16+
the Android process has started. This theme is visible to the user
17+
while the Flutter UI initializes. After that, this theme continues
18+
to determine the Window background behind the Flutter UI. -->
19+
<meta-data
20+
android:name="io.flutter.embedding.android.NormalTheme"
21+
android:resource="@style/NormalTheme"
22+
/>
23+
<intent-filter>
24+
<action android:name="android.intent.action.MAIN"/>
25+
<category android:name="android.intent.category.LAUNCHER"/>
26+
</intent-filter>
27+
</activity>
28+
<!-- Don't delete the meta-data below.
29+
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
30+
<meta-data
31+
android:name="flutterEmbedding"
32+
android:value="2" />
33+
</application>
34+
<!-- Required to query activities that can process text, see:
35+
https://developer.android.com/training/package-visibility and
36+
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
37+
38+
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
39+
<queries>
40+
<intent>
41+
<action android:name="android.intent.action.PROCESS_TEXT"/>
42+
<data android:mimeType="text/plain"/>
43+
</intent>
44+
</queries>
45+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package dev.flutter.packages.crossfileexample
6+
7+
import io.flutter.embedding.android.FlutterActivity
8+
9+
/** Test Activity that sets the name of the Dart method entrypoint in the manifest. */
10+
class DriverExtensionActivity : FlutterActivity()

0 commit comments

Comments
 (0)