Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
call.argument<ByteArray>("content")!!
)
}
WRITE_TO_FILE ->
writeToFile(
result,
call.argument<String>("uri")!!,
call.argument<ByteArray>("content")!!,
call.argument<String>("mode")!!
)
PERSISTED_URI_PERMISSIONS ->
persistedUriPermissions(result)
RELEASE_PERSISTABLE_URI_PERMISSION ->
Expand Down Expand Up @@ -311,6 +318,25 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
}
}

private fun writeToFile(
result: MethodChannel.Result,
uri: String,
content: ByteArray,
mode: String
) {
try {
plugin.context.contentResolver.openOutputStream(Uri.parse(uri), mode)?.apply {
write(content)
flush()
close()

result.success(true)
}
} catch (e: Exception) {
result.success(false)
}
}

@RequiresApi(API_19)
private fun persistedUriPermissions(result: MethodChannel.Result) {
val persistedUriPermissions = plugin.context.contentResolver.persistedUriPermissions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const val OPEN_DOCUMENT_TREE = "openDocumentTree"
const val PERSISTED_URI_PERMISSIONS = "persistedUriPermissions"
const val RELEASE_PERSISTABLE_URI_PERMISSION = "releasePersistableUriPermission"
const val CREATE_FILE = "createFile"
const val WRITE_TO_FILE = "writeToFile"
const val FROM_TREE_URI = "fromTreeUri"
const val CAN_WRITE = "canWrite"
const val CAN_READ = "canRead"
Expand Down
95 changes: 95 additions & 0 deletions docs/Usage/Storage Access Framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,33 @@ final DocumentFile? createdFile = createFileAsBytes(
);
```

### <samp>writeToFileAsBytes</samp>

Write to a file using raw bytes `Uint8List`.

Given the document uri, opens the file in the specified `mode` and writes the `bytes` to it.

`mode` represents the mode in which the file will be opened for writing. Use `FileMode.write` for truncating (overwrite) and `FileMode.append` for appending to the file.

```dart
final Uri documentUri = ...
final String fileContent = 'My File Content';

/// Write to a file using a [Uint8List] as file contents [bytes]
final bool? success = writeToFileAsBytes(
documentUri,
bytes: Uint8List.fromList(fileContent.codeUnits),
mode: FileMode.write,
);

/// Append to a file using a [Uint8List] as file contents [bytes]
final bool? success = writeToFileAsBytes(
documentUri,
bytes: Uint8List.fromList(fileContent.codeUnits),
mode: FileMode.write,
);
```

### <samp>canRead</samp>

<samp>Mirror of [`DocumentFile.canRead`](<https://developer.android.com/reference/androidx/documentfile/provider/DocumentFile#canRead()>)</samp>
Expand Down Expand Up @@ -485,6 +512,31 @@ final DocumentFile? createdFile = createFileAsString(
);
```

### <samp>writeToFileAsString</samp>

<samp>Alias for `writeToFileAsBytes`</samp>

Convenient method to write to a file using `content` as `String` instead `Uint8List`.

```dart
final Uri documentUri = ...
final String fileContent = 'My File Content';

/// Write to a file using a [Uint8List] as file contents [bytes]
final bool? success = writeToFileAsString(
documentUri,
content: fileContent,
mode: FileMode.write,
);

/// Append to a file using a [Uint8List] as file contents [bytes]
final bool? success = writeToFileAsBytes(
documentUri,
content: fileContent,
mode: FileMode.write,
);
```

### <samp>createFile</samp>

<samp>Alias for `createFileAsBytes` and `createFileAsString`</samp>
Expand Down Expand Up @@ -514,6 +566,49 @@ final DocumentFile? createdFile = createFile(
);
```

### <samp>writeToFile</samp>

<samp>Alias for `writeToFileAsBytes` and `writeToFileAsString`</samp>

Convenient method to write to a file using `content` as `String` **or** `bytes` as `Uint8List`.

You should provide either `content` or `bytes`, if both `bytes` will be used.

`mode` represents the mode in which the file will be opened for writing. Use `FileMode.write` for truncating and `FileMode.append` for appending to the file.

```dart
final Uri documentUri = ...
final String fileContent = 'My File Content';

/// Write to a file using a [String] as file contents [content]
final bool? success = writeToFile(
documentUri,
content: fileContent,
mode: FileMode.write,
);

/// Append to a file using a [String] as file contents [content]
final bool? success = writeToFile(
documentUri,
content: fileContent,
mode: FileMode.append,
);

/// Write to a file using a [Uint8List] as file contents [bytes]
final bool? success = writeToFile(
documentUri,
content: Uint8List.fromList(fileContent.codeUnits),
mode: FileMode.write,
);

/// Append to a file using a [Uint8List] as file contents [bytes]
final bool? success = writeToFile(
documentUri,
content: Uint8List.fromList(fileContent.codeUnits),
mode: FileMode.append,
);
```

## External APIs (deprecated)

These APIs are from external Android libraries.
Expand Down
7 changes: 7 additions & 0 deletions example/lib/screens/file_explorer/file_explorer_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ class _FileExplorerCardState extends State<FileExplorerCard> {
}
},
),
if (!_isDirectory)
DangerButton(
'Write to File',
onTap: () async {
await writeToFile(widget.partialFile.metadata!.uri!, content: 'Hello World!');
},
),
],
),
],
Expand Down
36 changes: 36 additions & 0 deletions lib/src/saf/document_file.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:io';
import 'dart:typed_data';

import '../common/functional_extender.dart';
Expand Down Expand Up @@ -155,6 +156,41 @@ class DocumentFile {
displayName: displayName,
content: content,
);

/// {@macro sharedstorage.saf.writeToFileAsBytes}
Future<bool?> writeToFileAsBytes({
required Uint8List bytes,
FileMode? mode,
}) =>
saf.writeToFileAsBytes(
uri,
bytes: bytes,
mode: mode,
);

/// {@macro sharedstorage.saf.writeToFile}
Future<bool?> writeToFile({
String? content,
Uint8List? bytes,
FileMode? mode,
}) =>
saf.writeToFile(
uri,
content: content,
bytes: bytes,
mode: mode,
);

/// Alias for [writeToFile] with [content] param
Future<bool?> writeToFileAsString({
required String content,
FileMode? mode,
}) =>
saf.writeToFile(
uri,
content: content,
mode: mode,
);

/// {@macro sharedstorage.saf.length}
Future<int?> get length => saf.documentLength(uri);
Expand Down
76 changes: 75 additions & 1 deletion lib/src/saf/saf.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import '../../saf.dart';
Expand Down Expand Up @@ -201,7 +202,7 @@ Future<DocumentFile?> createDirectory(Uri parentUri, String displayName) async {
}

/// {@template sharedstorage.saf.createFile}
/// Convenient method to create files using either String or raw bytes.
/// Convenient method to create files using either [String] or raw bytes [Uint8List].
///
/// Under the hood this method calls `createFileAsString` or `createFileAsBytes`
/// depending on which argument is passed.
Expand Down Expand Up @@ -281,6 +282,79 @@ Future<DocumentFile?> createFileAsString(
);
}

/// {@template sharedstorage.saf.writeToFile}
/// Convenient method to write to a file using either [String] or raw bytes [Uint8List].
///
/// Under the hood this method calls `writeToFileAsString` or `writeToFileAsBytes`
/// depending on which argument is passed.
///
/// If both (bytes and content) are passed, the bytes will be used and the content will be ignored.
/// {@endtemplate}
Future<bool?> writeToFile(
Uri uri, {
Uint8List? bytes,
String? content,
FileMode? mode,
}) {
assert(
bytes != null || content != null,
'''Either [bytes] or [content] should be provided''',
);

return bytes != null
? writeToFileAsBytes(
uri,
bytes: bytes,
mode: mode,
)
: writeToFileAsString(
uri,
content: content!,
mode: mode,
);
}

/// {@template sharedstorage.saf.writeToFileAsBytes}
/// Write to a file.
/// - `uri` is the URI of the file.
/// - `bytes` is the content of the document as a list of bytes `Uint8List`.
/// - `mode` is the mode in which the file will be opened for writing. Use `FileMode.write` for truncating and `FileMode.append` for appending to the file.
///
/// Returns `true` if the file was successfully written to.
/// {@endtemplate}
Future<bool?> writeToFileAsBytes(
Uri uri, {
required Uint8List bytes,
FileMode? mode,
}) async {
final writeMode =
mode == FileMode.append || mode == FileMode.writeOnlyAppend ? 'wa' : 'wt';

final args = <String, dynamic>{
'uri': '$uri',
'content': bytes,
'mode': writeMode,
};

return kDocumentFileChannel.invokeMethod<bool>('writeToFile', args);
}

/// {@template sharedstorage.saf.writeToFileAsString}
/// Convenient method to write to a file.
/// using `content` as [String] instead [Uint8List].
/// {@endtemplate}
Future<bool?> writeToFileAsString(
Uri uri, {
required String content,
FileMode? mode,
}) {
return writeToFileAsBytes(
uri,
bytes: Uint8List.fromList(content.codeUnits),
mode: mode,
);
}

/// {@template sharedstorage.saf.length}
/// Equivalent to `DocumentFile.length`.
///
Expand Down