Skip to content

Commit 0648b7e

Browse files
committed
(#61) add writeToFile, writeToFileAsBytes, writeToFileAsString
1 parent 1433462 commit 0648b7e

6 files changed

Lines changed: 243 additions & 0 deletions

File tree

android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentFileApi.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
7474
call.argument<ByteArray>("content")!!
7575
)
7676
}
77+
WRITE_TO_FILE ->
78+
writeToFile(
79+
result,
80+
call.argument<String>("uri")!!,
81+
call.argument<ByteArray>("content")!!,
82+
call.argument<String>("mode")!!
83+
)
7784
PERSISTED_URI_PERMISSIONS ->
7885
persistedUriPermissions(result)
7986
RELEASE_PERSISTABLE_URI_PERMISSION ->
@@ -311,6 +318,25 @@ internal class DocumentFileApi(private val plugin: SharedStoragePlugin) :
311318
}
312319
}
313320

321+
private fun writeToFile(
322+
result: MethodChannel.Result,
323+
uri: String,
324+
content: ByteArray,
325+
mode: String,
326+
) {
327+
try {
328+
plugin.context.contentResolver.openOutputStream(Uri.parse(uri), mode)?.apply {
329+
write(content)
330+
flush()
331+
close()
332+
333+
result.success(true)
334+
}
335+
} catch (e: Exception) {
336+
result.success(false)
337+
}
338+
}
339+
314340
@RequiresApi(API_19)
315341
private fun persistedUriPermissions(result: MethodChannel.Result) {
316342
val persistedUriPermissions = plugin.context.contentResolver.persistedUriPermissions

android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/lib/StorageAccessFrameworkConstant.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const val OPEN_DOCUMENT_TREE = "openDocumentTree"
2323
const val PERSISTED_URI_PERMISSIONS = "persistedUriPermissions"
2424
const val RELEASE_PERSISTABLE_URI_PERMISSION = "releasePersistableUriPermission"
2525
const val CREATE_FILE = "createFile"
26+
const val WRITE_TO_FILE = "writeToFile"
2627
const val FROM_TREE_URI = "fromTreeUri"
2728
const val CAN_WRITE = "canWrite"
2829
const val CAN_READ = "canRead"

docs/Usage/Storage Access Framework.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,33 @@ final DocumentFile? createdFile = createFileAsBytes(
217217
);
218218
```
219219

220+
### <samp>writeToFileAsBytes</samp>
221+
222+
Write to a file using raw bytes `Uint8List`.
223+
224+
Given the document uri, opens the file in the specified `mode` and writes the `bytes` to it.
225+
226+
`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.
227+
228+
```dart
229+
final Uri documentUri = ...
230+
final String fileContent = 'My File Content';
231+
232+
/// Write to a file using a [Uint8List] as file contents [bytes]
233+
final bool? success = writeToFileAsBytes(
234+
documentUri,
235+
bytes: Uint8List.fromList(fileContent.codeUnits),
236+
mode: FileMode.write,
237+
);
238+
239+
/// Append to a file using a [Uint8List] as file contents [bytes]
240+
final bool? success = writeToFileAsBytes(
241+
documentUri,
242+
bytes: Uint8List.fromList(fileContent.codeUnits),
243+
mode: FileMode.write,
244+
);
245+
```
246+
220247
### <samp>canRead</samp>
221248

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

515+
### <samp>writeToFileAsString</samp>
516+
517+
<samp>Alias for `writeToFileAsBytes`</samp>
518+
519+
Convenient method to write to a file using `content` as `String` instead `Uint8List`.
520+
521+
```dart
522+
final Uri documentUri = ...
523+
final String fileContent = 'My File Content';
524+
525+
/// Write to a file using a [Uint8List] as file contents [bytes]
526+
final bool? success = writeToFileAsString(
527+
documentUri,
528+
content: fileContent,
529+
mode: FileMode.write,
530+
);
531+
532+
/// Append to a file using a [Uint8List] as file contents [bytes]
533+
final bool? success = writeToFileAsBytes(
534+
documentUri,
535+
content: fileContent,
536+
mode: FileMode.write,
537+
);
538+
```
539+
488540
### <samp>createFile</samp>
489541

490542
<samp>Alias for `createFileAsBytes` and `createFileAsString`</samp>
@@ -514,6 +566,50 @@ final DocumentFile? createdFile = createFile(
514566
);
515567
```
516568

569+
### <samp>writeToFile</samp>
570+
571+
<samp>Alias for `writeToFileAsBytes` and `writeToFileAsString`</samp>
572+
573+
Convenient method to write to a file using `content` as `String` **or** `bytes` as `Uint8List`.
574+
575+
You should provide either `content` or `bytes`, if both `bytes` will be used.
576+
577+
`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.
578+
579+
580+
```dart
581+
final Uri documentUri = ...
582+
final String fileContent = 'My File Content';
583+
584+
/// Write to a file using a [String] as file contents [content]
585+
final bool? success = writeToFile(
586+
documentUri,
587+
content: fileContent,
588+
mode: FileMode.write,
589+
);
590+
591+
/// Append to a file using a [String] as file contents [content]
592+
final bool? success = writeToFile(
593+
documentUri,
594+
content: fileContent,
595+
mode: FileMode.append,
596+
);
597+
598+
/// Write to a file using a [Uint8List] as file contents [bytes]
599+
final DocumentFile? createdFile = writeToFile(
600+
documentUri,
601+
content: Uint8List.fromList(fileContent.codeUnits),
602+
mode: FileMode.write,
603+
);
604+
605+
/// Append to a file using a [Uint8List] as file contents [bytes]
606+
final DocumentFile? createdFile = writeToFile(
607+
documentUri,
608+
content: Uint8List.fromList(fileContent.codeUnits),
609+
mode: FileMode.append,
610+
);
611+
```
612+
517613
## External APIs (deprecated)
518614

519615
These APIs are from external Android libraries.

example/lib/screens/file_explorer/file_explorer_card.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,13 @@ class _FileExplorerCardState extends State<FileExplorerCard> {
198198
}
199199
},
200200
),
201+
if (!_isDirectory)
202+
DangerButton(
203+
'Write to File',
204+
onTap: () async {
205+
await writeToFile(widget.partialFile.metadata!.uri!, content: 'Hello World!');
206+
},
207+
),
201208
],
202209
),
203210
],

lib/src/saf/document_file.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:io';
12
import 'dart:typed_data';
23

34
import '../common/functional_extender.dart';
@@ -155,6 +156,41 @@ class DocumentFile {
155156
displayName: displayName,
156157
content: content,
157158
);
159+
160+
/// {@macro sharedstorage.saf.writeToFileAsBytes}
161+
Future<bool?> writeToFileAsBytes({
162+
required Uint8List bytes,
163+
FileMode? mode,
164+
}) =>
165+
saf.writeToFileAsBytes(
166+
uri,
167+
bytes: bytes,
168+
mode: mode,
169+
);
170+
171+
/// {@macro sharedstorage.saf.writeToFile}
172+
Future<bool?> writeToFile({
173+
String? content,
174+
Uint8List? bytes,
175+
FileMode? mode,
176+
}) =>
177+
saf.writeToFile(
178+
uri,
179+
content: content,
180+
bytes: bytes,
181+
mode: mode,
182+
);
183+
184+
/// Alias for [writeToFile] with [content] param
185+
Future<bool?> writeToFileAsString({
186+
required String content,
187+
FileMode? mode,
188+
}) =>
189+
saf.writeToFile(
190+
uri,
191+
content: content,
192+
mode: mode,
193+
);
158194

159195
/// {@macro sharedstorage.saf.length}
160196
Future<int?> get length => saf.documentLength(uri);

lib/src/saf/saf.dart

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:async';
2+
import 'dart:io';
23
import 'dart:typed_data';
34

45
import '../../saf.dart';
@@ -281,6 +282,82 @@ Future<DocumentFile?> createFileAsString(
281282
);
282283
}
283284

285+
/// {@template sharedstorage.saf.writeToFile}
286+
/// Convenient method to write to a file using either String or raw bytes.
287+
///
288+
/// Under the hood this method calls `writeToFileAsString` or `writeToFileAsBytes`
289+
/// depending on which argument is passed.
290+
///
291+
/// If both (bytes and content) are passed, the bytes will be used and the content will be ignored.
292+
/// {@endtemplate}
293+
Future<bool?> writeToFile(
294+
Uri uri, {
295+
Uint8List? bytes,
296+
String? content,
297+
FileMode? mode,
298+
}) {
299+
assert(
300+
bytes != null || content != null,
301+
'''Either [bytes] or [content] should be provided''',
302+
);
303+
304+
return bytes != null
305+
? writeToFileAsBytes(
306+
uri,
307+
bytes: bytes,
308+
mode: mode,
309+
)
310+
: writeToFileAsString(
311+
uri,
312+
content: content!,
313+
mode: mode,
314+
);
315+
}
316+
317+
/// {@template sharedstorage.saf.writeToFileAsBytes}
318+
/// Write to a file.
319+
/// - `uri` is the URI of the file.
320+
/// - `bytes` is the content of the document as a list of bytes `Uint8List`.
321+
/// - `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.
322+
///
323+
/// Returns `true` if the file was successfully written to.
324+
/// {@endtemplate}
325+
Future<bool?> writeToFileAsBytes(
326+
Uri uri, {
327+
required Uint8List bytes,
328+
FileMode? mode,
329+
}) async {
330+
var writeMode = 'wt';
331+
332+
if (mode == FileMode.append || mode == FileMode.writeOnlyAppend) {
333+
writeMode = 'wa';
334+
}
335+
336+
final args = <String, dynamic>{
337+
'uri': uri.toString(),
338+
'content': bytes,
339+
'mode': writeMode,
340+
};
341+
342+
return kDocumentFileChannel.invokeMethod<bool>('writeToFile', args);
343+
}
344+
345+
/// {@template sharedstorage.saf.writeToFileAsString}
346+
/// Convenient method to write to a file.
347+
/// using `content` as String instead Uint8List.
348+
/// {@endtemplate}
349+
Future<bool?> writeToFileAsString(
350+
Uri uri, {
351+
required String content,
352+
FileMode? mode,
353+
}) {
354+
return writeToFileAsBytes(
355+
uri,
356+
bytes: Uint8List.fromList(content.codeUnits),
357+
mode: mode,
358+
);
359+
}
360+
284361
/// {@template sharedstorage.saf.length}
285362
/// Equivalent to `DocumentFile.length`.
286363
///

0 commit comments

Comments
 (0)