alexrintt / shared-storage

Flutter plugin to work with Android external storage.
http://alexrintt.io/shared-storage/
MIT License
53 stars 25 forks source link

Write to a file #61

Closed jfaltis closed 2 years ago

jfaltis commented 2 years ago

How would you use this plugin to write to a file in a folder which access was granted through SAF?

I tried to use the File class and writeAsString from dart:io but I was not able to obtain a proper path through the Uri with toFilePath().

final Uri uri = widget.partialFile.metadata!.uri!;
final file = File(uri.toFilePath());

causes

E/flutter (18993): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: Unsupported operation: Cannot extract a file path from a content URI
E/flutter (18993): #0      _Uri.toFilePath (dart:core/uri.dart:2825:7)
E/flutter (18993): #1      _FolderFileCardState.build.<anonymous closure> (package:shared_storage_example/screens/folder_files/folder_file_card.dart:182:39)
E/flutter (18993): #2      _FolderFileCardState.build.<anonymous closure> (package:shared_storage_example/screens/folder_files/folder_file_card.dart:180:22)
E/flutter (18993): #3      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:1005:21)
E/flutter (18993): #4      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:198:24)
E/flutter (18993): #5      TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:613:11)
E/flutter (18993): #6      BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:298:5)
E/flutter (18993): #7      BaseTapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:269:7)
E/flutter (18993): #8      GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:157:27)
E/flutter (18993): #9      GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:449:20)
E/flutter (18993): #10     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:425:22)
E/flutter (18993): #11     RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:329:11)
E/flutter (18993): #12     GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:380:7)
E/flutter (18993): #13     GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:344:5)
E/flutter (18993): #14     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:302:7)
E/flutter (18993): #15     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:285:7)
E/flutter (18993): #16     _rootRunUnary (dart:async/zone.dart:1442:13)
E/flutter (18993): #17     _CustomZone.runUnary (dart:async/zone.dart:1335:19)
E/flutter (18993): #18     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1244:7)
E/flutter (18993): #19     _invoke1 (dart:ui/hooks.dart:170:10)
E/flutter (18993): #20     PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:331:7)
E/flutter (18993): #21     _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31)

And once while testing toFilePath() did not cause an error. Opening the file did however.

E/flutter (18993): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: FileSystemException: Cannot open file, path = '/tree/primary%3ADocuments/document/primary%3ADocuments%2FSample%20File.txt' (OS Error: No such file or directory, errno = 2)
E/flutter (18993): #0      _File.open.<anonymous closure> (dart:io/file_impl.dart:356:9)
E/flutter (18993): #1      _rootRunUnary (dart:async/zone.dart:1434:47)
E/flutter (18993): #2      _CustomZone.runUnary (dart:async/zone.dart:1335:19)
E/flutter (18993): <asynchronous suspension>
E/flutter (18993): 

It would be great to extend the plugin example with this functionality.

alexrintt commented 2 years ago

Fixed on v0.4.0.

final parentFolder = openDocumentTree();

if (parentFolder == null) {
  return; // No folder was selected
}

const kTxtMimeType = 'text/plain';

final createdFile = await createFile(
  parentFolder,
  mimeType: kTxtMimeType,
  displayName: 'My Text File',
  content: 'File data, can be anything depending on mime type!',
);

Thanks for opening the question. Any other questions let me know.

jfaltis commented 2 years ago

Cool, keep up the good work. How do you leverage this plugin to write to an existing file? Will you be able to use the dart File API or can/should you use

final createdFile = await createFile(
  parentFolder,
  mimeType: kTxtMimeType,
  displayName: 'My Text File',
  content: 'File data, can be anything depending on mime type!',
);

to overwrite an existing file?

The plugin example seems to be broken in v0.4.0

[jona@jona example]$ flutter run
Running "flutter pub get" in example...                          1.123ms
Launching lib/main.dart on  in debug mode...
Warning: Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Warning: Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/03 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/03 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/03 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Warning: Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Warning: unexpected element (uri:"", local:"base-extension"). Expected elements are <{}codename>,<{}layoutlib>,<{}api-level>
lib/main.dart:2:8: Error: Error when reading 'lib/screens/persisted_uris/persisted_uri_list.dart': No such file or directory
import 'screens/persisted_uris/persisted_uri_list.dart';
       ^
lib/main.dart:17:36: Error: Method not found: 'PersistedUriList'.
    return const MaterialApp(home: PersistedUriList());
                                   ^^^^^^^^^^^^^^^^

FAILURE: Build failed with an exception.

* Where:
Script '/opt/flutter/packages/flutter_tools/gradle/flutter.gradle' line: 1156

* What went wrong:
Execution failed for task ':app:compileFlutterBuildDebug'.
> Process 'command '/opt/flutter/bin/flutter'' finished with non-zero exit value 1

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 11s
Running Gradle task 'assembleDebug'...                             11,7s
Exception: Gradle task assembleDebug failed with exit code 1
alexrintt commented 2 years ago

I tried rn but to override seems it's not supported yet.

I'll create another feature-request issue to wrap this feature to you, to us.

Thanks for requesting it.

Also, I fixed the error related to the plugin example project and added a simple GitHub action that will block any future attempts to publish with an error like that, sorry for it.

Soon I'll release a new version v0.4.1 to make this fix available on pub.dev.

alexrintt commented 2 years ago

For now you can try this workaround:

This will "update" the file.

I'm working on this issue #79 to add updateFile method which doesn't force you to delete the file so this proposal above is just a temporary workaround.

jfaltis commented 2 years ago

I noticed that you are not closing the OutputStream when creating a file. Should I fix this and include it in my PR or should it be done seperately?

https://github.com/lakscastro/shared-storage/blob/14334626b426e2f4c4a52122089e3b61b3ed55fd/android/src/main/kotlin/io/lakscastro/sharedstorage/storageaccessframework/DocumentFileApi.kt#L293-L312

alexrintt commented 2 years ago

You can do it in separated branches/PRs, please.

Would be great if we could publish the OutputStream fix as patch update and your new feature as minor update.

alexrintt commented 2 years ago

Closing since its already implemented by #85.