Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add writeToFile, writeToFileAsBytes, writeToFileAsString #85

Merged
merged 2 commits into from
Jul 16, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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,
Copy link
Owner

Choose a reason for hiding this comment

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

Syntax error because of this comma:

Warning The feature "trailing commas" is only available since language version 1.4

) {
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
96 changes: 96 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 and `FileMode.append` for appending to the file.
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
`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.
`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,50 @@ 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.


Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change

```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 DocumentFile? createdFile = writeToFile(
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
final DocumentFile? createdFile = writeToFile(
final bool? success = writeToFile(

Guess this function returns a boolean representing the whether or not the operation was successful, if not let me know but I think it was just a typo.

documentUri,
content: Uint8List.fromList(fileContent.codeUnits),
mode: FileMode.write,
);

/// Append to a file using a [Uint8List] as file contents [bytes]
final DocumentFile? createdFile = writeToFile(
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
final DocumentFile? createdFile = writeToFile(
final bool? success = writeToFile(

Guess this function returns a boolean representing the whether or not the operation was successful, if not let me know but I think it was just a typo.

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
77 changes: 77 additions & 0 deletions 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 @@ -281,6 +282,82 @@ Future<DocumentFile?> createFileAsString(
);
}

/// {@template sharedstorage.saf.writeToFile}
/// Convenient method to write to a file using either String or raw bytes.
Copy link
Owner

@alexrintt alexrintt Jul 15, 2022

Choose a reason for hiding this comment

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

Suggested change
/// Convenient method to write to a file using either String or raw bytes.
/// Convenient method to write to a file using either [String] or raw bytes [Uint8List].

You can also apply this change for createFile function.

///
/// 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 {
var writeMode = 'wt';

if (mode == FileMode.append || mode == FileMode.writeOnlyAppend) {
writeMode = 'wa';
}
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
var writeMode = 'wt';
if (mode == FileMode.append || mode == FileMode.writeOnlyAppend) {
writeMode = 'wa';
}
final writeMode =
mode == FileMode.append || mode == FileMode.writeOnlyAppend ? 'wa' : 'wt';


final args = <String, dynamic>{
'uri': uri.toString(),
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
'uri': uri.toString(),
'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.
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
/// using `content` as String instead Uint8List.
/// using `content` as [String] instead [Uint8List].

You can also apply this change for createFileAsString function.

/// {@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