Code Monkey home page Code Monkey logo

file_picker_writable's Introduction

Pub Version

file_picker_writable

Flutter plugin to choose files which can be read, referenced and written back at a later time (persistent permissions on android, secure bookmarks on iOS).

It also offers handlers for open intents when the user wants to open associated files from other apps. In the same way it will also handle arbitrary URLs and pass them back to dart.

Requirements

iOS

  • iOS 8 + Swift 5
  • Only tested on iOS 13+, so let me know ;-)

Support for file handlers

  1. Configure an OTI Type: https://developer.apple.com/library/archive/qa/qa1587/_index.html
  2. Add to plist file:
     <key>UISupportsDocumentBrowser</key>
     <false/>
     <key>LSSupportsOpeningDocumentsInPlace</key>
     <true/>
    

Android

Support for file handlers

AndroidManifest.xlm

            <intent-filter>
                <action android:name="android.intent.action.VIEW"  />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="file" />
                <data android:scheme="content" />
                <data android:host="*"  />
                <data android:mimeType="*/*" />
                <!-- https://stackoverflow.com/a/52384331/109219 ?? -->
                <data android:pathPattern=".*\\.codeux" />
                <data android:pathPattern=".*\\..*\\.codeux" />
                <data android:pathPattern=".*\\..*\\..*\\.codeux" />
                <data android:pathPattern=".*\\..*\\..*\\..*\\.codeux" />
                <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.codeux" />
                <data android:pathPattern=".*\\..*\\..*\\..*\\.*\\..*\\.codeux" />
            </intent-filter>

MacOS

Is currently not supported. The only thing the plugin will do is listen for URL Events and pass them through to the dart side.

Getting Started

See the example on how to implement it in a simple application.

Future<void> readFile() async {
  final fileInfo = await FilePickerWritable().openFile((fileInfo, file) async {
    _logger.fine('Got picker result: $fileInfo');

    // now do something useful with the selected file...
    _logger.info('Got file contents in temporary file: $file');
    _logger.info('fileName: ${fileInfo.fileName}');
    _logger.info('Identifier which can be persisted for later retrieval:'
        '${fileInfo.identifier}');
    return fileInfo;
  });
  if (fileInfo == null) {
    _logger.info('User canceled.');
    return;
  }
}

The returned fileInfo.identifier can be used later to write or read from the data, even after an app restart.

Future<void> persistChanges(FileInfo fileInfo, Uint8List newContent) async {
  // tell FilePickerWritable plugin to write the new contents over the user selected file
  await FilePickerWritable().writeFile(
      identifier: fileInfo.identifier,
      writer: (file) async {
        // write new content into the temporary file.
        await file.writeBytes(newContent);
      });
}

Create a new file by letting the user choose a directory and file name:

final rand = Random().nextInt(10000000);
final fileInfo = await FilePickerWritable().openFileForCreate(
  fileName: 'newfile.$rand.codeux',
  writer: (file) async {
    final content = 'File created at ${DateTime.now()}\n\n';
    await file.writeAsString(content);
  },
);
if (fileInfo == null) {
  _logger.info('User canceled.');
  return;
}
final data = await _appDataBloc.store.load();
await _appDataBloc.store
    .save(data.copyWith(files: data.files + [fileInfo]));
}

This will open the following screen on Android:

create file screenshot

file_picker_writable's People

Contributors

amake avatar hpoul avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

file_picker_writable's Issues

Android: Fallback for unsupported OPEN_DOCUMENT intent

the OPEN_DOCUMENT intent is only a requirement since android 9. Before that AOSP did not require an app for OPEN_DOCUMENT .. This can be easily worked around by users by installing an app supporting this intent (e.g. https://f-droid.org/en/packages/me.zhanghai.android.files/ )

But the file picker should probably just fall back to ACTION_GET_CONTENT - see also for example signalapp/Signal-Android@6e3751a

2020-10-04 12:10:52.811869 FINE file_picker_writable - Native Log: debug: main exception while launcing file picker  Exception: No Activity found to handle Intent { act=android.intent.action.OPEN_DOCUMENT cat=[android.intent.category.OPENABLE] typ=*/* }
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.OPEN_DOCUMENT cat=[android.intent.category.OPENABLE] typ=*/* }
	at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1937)
	at android.app.Instrumentation.execStartActivity(Instrumentation.java:1616)
	at android.app.Activity.startActivityForResult(Activity.java:4532)
	at androidx.fragment.app.d.startActivityForResult(Unknown Source:10)
	at android.app.Activity.startActivityForResult(Activity.java:4490)
	at androidx.fragment.app.d.startActivityForResult(Unknown Source:10)
	at d.a.a.a.c.o(Unknown Source:35)
	at d.a.a.a.d$b.i(Unknown Source:133)
	at j.d0.j.a.a.j(Unknown Source:8)
	at kotlinx.coroutines.k0.run(Unknown Source:93)
	at android.os.Handler.handleCallback(Handler.java:790)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loop(Looper.java:164)
	at android.app.ActivityThread.main(ActivityThread.java:6499)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:442)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

Android Gradle Plugin 8.0 compatibility

When trying to upgrade my project to Android Gradle Plugin 8.0 I get the following error:

FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring project ':file_picker_writable'.
> Could not create an instance of type com.android.build.api.variant.impl.LibraryVariantBuilderImpl.
   > Namespace not specified. Please specify a namespace in the module's build.gradle file like so:

     android {
         namespace 'com.example.namespace'
     }

     If the package attribute is specified in the source AndroidManifest.xml, it can be migrated automatically to the namespace value in the build.gradle file using the AGP Upgrade Assistant; please refer to https://developer.android.com/studio/build/agp-upgrade-assistant for more information.

* 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 1s

Running on physical iphone, "The file couldn’t be opened because it doesn’t exist."

First of all, thank you so much for this plugin. I am very excited at the prospect of porting my app to iOS with the help of your plugin, as well as reducing effort of maintenance on Android. I did not trust my plugin code very much, and this plugin could potentially take all that off me.

At the time that I tested, I was on 1.1.1+1, but as I started to use my local clone of your project, I realize I should test again shortly with 1.1.1+2.

The plugin was working fine for me when I ran my app via Simulator.app, but when I try flutter run -d iphone on a physical device, I run into this error when I try to pick a fie.

flutter: PlatformException(ErrorProcessingResult, Error handling result url file:///private/var/mobile/Containers/Shared/AppGroup/8F03431C-961F-41F4-9296-BC5688DFD150/File%20Provider%20Storage/9850354/1lardL_81Lo4fmRBvjvOOlx-fKjSi4dKq/blah.test: Error Domain=NSCocoaErrorDomain Code=260 "The file couldn’t be opened because it doesn’t exist.", null)
flutter: #0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:569:7)
#1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:156:18)
<asynchronous suspension>
#2      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:329:12)
#3      MethodChannel.invokeMapMethod (package:flutter/src/services/platform_channel.dart:356:48)
#4      FilePickerWritable.openFilePicker (package:file_picker_writable/src/file_picker_writable.dart:136:24)

Have you tried running the example/ app on a physical device? I briefly tried but ran into indirectly related issues.

My next strategy to debug this, when I can make time, is to increase logging, either using your method of logging, or as well by just inserting logging statements in the swift code, within the plugin. Please let me know if you have other ideas on how to isolate the issue.

I will also try again to see if I can test your example/ app, by perhaps overwriting my lib/ directory with the example/lib/, etc.

It's late here, so I'm sending this issue as is, otherwise it would be shorter.

Persistable access to a directory

Thanks as always for the great plugin.

I have a use case for selecting a directory, rather than a file: I want to be able to resolve files relative to a file, e.g. images referenced from a document.

For instance I open a file foo/bar/baz.html and find that it references several other files in ./blah. I want to be able to obtain a persistent reference to foo/bar/blah and resolve the files within.

This plugin currently doesn't offer that functionality. Would you be interested in adding it, or would you accept a PR that implements it for iOS and Android only?

I am looking at these APIs:

  • iOS: UIDocumentPickerViewController(documentTypes: [kUTTypeFolder as String], in: .open) (docs)
  • Android: Intent.ACTION_OPEN_DOCUMENT_TREE (docs, sample code)

On the Dart side, it looks like in addition to an openDirectory(path) method we may need some sort of resolveRelative(parent, child) that would take a known security-scoped URL or bookmark and return a new security-scoped URL/bookmark for the child.

Error opening from intent on Android

Hi. Thanks very much for this plugin. I was looking for a way to obtain persistent access to external files, and happened to find your plugin from a discussion on https://github.com/miguelpruivo/flutter_file_picker.

I have had great success with your plugin when launching the picker with FilePickerWritable().openFilePicker(). However when opening a file from another app via an intent on Android I get the following error:

D/FilePickerWritable(26622): onNewIntent(content://com.android.providers.downloads.documents/document/22896)
D/FilePickerWritable(26622): Got method call: init
D/FilePickerWritable(26622): Error while handling method call io.flutter.plugin.common.MethodCall@3d1625c
D/FilePickerWritable(26622): java.lang.SecurityException: No persistable permission grants found for UID 10421 and Uri content://com.android.providers.downloads.documents/document/22896 [user 0]
D/FilePickerWritable(26622): 	at android.os.Parcel.createException(Parcel.java:2071)
D/FilePickerWritable(26622): 	at android.os.Parcel.readException(Parcel.java:2039)
D/FilePickerWritable(26622): 	at android.os.Parcel.readException(Parcel.java:1987)
D/FilePickerWritable(26622): 	at android.app.IUriGrantsManager$Stub$Proxy.takePersistableUriPermission(IUriGrantsManager.java:280)
D/FilePickerWritable(26622): 	at android.content.ContentResolver.takePersistableUriPermission(ContentResolver.java:2428)
D/FilePickerWritable(26622): 	at codeux.design.filepicker.file_picker_writable.FilePickerWritableImpl$copyContentUriAndReturnFileInfo$2.invokeSuspend(FilePickerWritableImpl.kt:227)
D/FilePickerWritable(26622): 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
D/FilePickerWritable(26622): 	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
D/FilePickerWritable(26622): 	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
D/FilePickerWritable(26622): 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
D/FilePickerWritable(26622): 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
D/FilePickerWritable(26622): 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
D/FilePickerWritable(26622): Caused by: android.os.RemoteException: Remote stack trace:
D/FilePickerWritable(26622): 	at com.android.server.uri.UriGrantsManagerService.takePersistableUriPermission(UriGrantsManagerService.java:328)
D/FilePickerWritable(26622): 	at android.app.IUriGrantsManager$Stub.onTransact(IUriGrantsManager.java:139)
D/FilePickerWritable(26622): 	at android.os.Binder.execTransactInternal(Binder.java:1021)
D/FilePickerWritable(26622): 	at android.os.Binder.execTransact(Binder.java:994)
D/FilePickerWritable(26622): 

It seems that takePersistableUriPermission won't always succeed so some error handling is necessary.

This indicates that takePersistableUriPermission is only valid for the ACTION_OPEN_DOCUMENT intent, which it seems can only be initiated by my application; for receiving other intents (like VIEW) from other applications, takePersistableUriPermission should not be invoked.

Either that, or perhaps my configuration is wrong?

I tried your example app, and I couldn't get it to appear in the app selector for .codeux files at all.

(Android 10 on a Pixel 2)

How to get the chosen file path on createFile

Hi,

I use the lib for having a save dialog. I use it like this:

    final fileInfo = await FilePickerWritable().openFileForCreate(
      fileName: fileName,
      writer: (file) async {
        await file.writeAsBytes(data.buffer.asUint8List());
        fileX = file;
      },
    );

    filePath = fileInfo.identifier;

Afterwards I want to get the chosen path. I am trying with fileInfo.identifier, but this gives only a content uri, not the path (like content://com.android.externalstorage.documents/document/primary%3ADownload%2Fmy_file_name.png, the user chose.

Is there a way to return the chosen directory or convert the content uri into it?

writeFileWithIdentifier method doesn't work properly on Android 10

Today I came across a problem like this.

  1. I have an existing file of a certain size on the device
  2. I try to overwrite it using the identifier (using writeFile() method) with content that is smaller than it was before

Current behavior:
The file is not truncated to a smaller size. New content is written to the beginning of the file, and continues by the content that was previously in the file.

Correct behavior:
The file should be completely overwritten and truncated if the new content is smaller.

I began to figure it out to find out what was the reason of this issue.
And I found the reason.

In Kotlin implementation file there are such lines of code (writeFileWithIdentifier method):

withContext(Dispatchers.IO) {
  contentResolver.openOutputStream(fileUri, "w").use { output ->
    require(output != null)
    file.inputStream().use { input ->
      input.copyTo(output)
    }
  }
}

Method openOutputStream with mode "w" works successfully to rewrite the stream on android version less than 10.
But in Android 10 it doesn't truncate the file.
There should be a mode "wt" specified, instead of a mode "w".

I tried to replace "w" by "wt" locally, and it works.

There is an issue in Android issue tracker:
https://issuetracker.google.com/issues/135714729?pli=1

Also, there is a similar issue in another repository with the same reason, as an example:
itinance/react-native-fs#837

This problem is very critical for me, since it does not allow overwriting files normally, could you think about how to fix the code in the part of specifying the stream opening mode? I would be very grateful.
Thank you so much!

Won't build for Android with Flutter 3.0

When attempting to build for Android with Flutter 3.0 you get the following error:

e: /Applications/flutter/.pub-cache/git/file_picker_writable-e1fe4af1792d8f5d92dedd1311a46a1ffc3dfa4c/android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritableImpl.kt: (31, 1): Class 'FilePickerWritableImpl' is not abstract and does not implement abstract member public abstract fun onNewIntent(p0: Intent): Boolean defined in io.flutter.plugin.common.PluginRegistry.NewIntentListener
e: /Applications/flutter/.pub-cache/git/file_picker_writable-e1fe4af1792d8f5d92dedd1311a46a1ffc3dfa4c/android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritableImpl.kt: (485, 3): 'onNewIntent' overrides nothing

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':file_picker_writable:compileDebugKotlin'.
> Compilation error. See log for more details

The issue is that the intent parameter of NewIntentListener#onNewIntent has been given a @NonNull annotation, which effectively changes the interface. Simply changing onNewIntent(intent: Intent?) in FilePickerWritableImpl.kt to onNewIntent(intent: Intent) will fix the problem for newer Flutter, but I don't know if the parameter could ever have been null in the past, so I'm not sure what to do about older Flutter versions.

Remove android.permission.READ_EXTERNAL_STORAGE

I think for ACTION_OPEN_DOCUMENT, we don't need the broad permission to read external storage. I think it's using a notion that user is implicitly giving surgical permission when picking the file.

I tried the following diff, and I was able to pick a file for reading.

modified   android/src/main/AndroidManifest.xml
@@ -1,4 +1,3 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="codeux.design.filepicker.file_picker_writable">
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 </manifest>

I tested with:

  • file_picker_writable's null-safety branch (at 0144b6f0b8adab3d51fa1c1de418d2e59c9f1fc1)
  • Flutter 2.0
  • Android 11

Cannot select files, they seemed to be disabled

My iOS app contains your file picker dialog and is used for several file types.

One function is to open GPX and KML map files. However, when I open the file selector I can see the requested files but I am not able to select them, they look disabled; when I tap on them, nothing happens.

Two things are strange:

  1. Another function must open image files. This is done with the same open file widget and works properly.
  2. We tested three different devices (2 different iPhones, 1 iPad). Only on my iPhone 11 Max has the problem. The other devices can open the GPX files without any problems.

So, I believe it depends on my specific device. I was looking for a specific permission which I can set somewhere in the settings but I was not able to solve the problem.

Does anybody has an idea why it is not possible to open the files?

Kind regards!

path_provider <2.0.0 restriction causing problems

Recently the google_fonts package released version 2.0.0, which depends on path_provider 2.0.0.

file_picker_writable specifies path_provider: ">=1.6.0 <2.0.0" so currently it can't be used with google_fonts: ^2.0.0:

% flutter analyze
Because no versions of google_fonts match >2.0.0 <3.0.0 and google_fonts 2.0.0 depends on path_provider ^2.0.0, google_fonts ^2.0.0 requires path_provider ^2.0.0.
And because file_picker_writable >=1.2.0-dev.1 depends on path_provider ^1.6.0, google_fonts ^2.0.0 is incompatible with file_picker_writable >=1.2.0-dev.1.
So, because orgro depends on both file_picker_writable ^1.2.0 and google_fonts ^2.0.0, version solving failed.
Running "flutter pub get" in orgro...                                   
pub get failed (1; So, because orgro depends on both file_picker_writable ^1.2.0 and google_fonts ^2.0.0, version solving
failed.)

(In fact despite long since having updated to 1.2.0, my pubspec.yaml still specified file_picker_writable: ^1.1.1 so initially what flutter pub did was resolve back down to 1.1.1, which apparently didn't prevent path_provider 2.0.0. Of course this downgrade failed due to API changes since then. I was quite confused for a while.)

It would be nice if you could bump the version.

Implement a feature that covers usescases like limiting to "text/plain"

I have the following diffs on a fork:

modified   android/src/main/kotlin/codeux/design/filepicker/file_picker_writable/FilePickerWritableImpl.kt
@@ -53,6 +53,8 @@ class FilePickerWritableImpl(
     val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
       addCategory(Intent.CATEGORY_OPENABLE)
       type = "*/*"
+      val mimetypes = arrayOf<String>("text/plain", "application/octet-stream")
+      putExtra(Intent.EXTRA_MIME_TYPES, mimetypes)
     }
     val activity = requireActivity()
     try {
@@ -79,7 +81,7 @@ class FilePickerWritableImpl(
     val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
       addCategory(Intent.CATEGORY_OPENABLE)
 //      type = "application/x-keepass"
-      type = "*/*"
+      type = "text/plain"
       putExtra(Intent.EXTRA_TITLE, file.name)
     }
     val activity = requireActivity()
modified   ios/Classes/SwiftFilePickerWritablePlugin.swift
@@ -175,7 +175,7 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin {
         }
         _filePickerResult = result
         _filePickerPath = nil
-        let ctrl = UIDocumentPickerViewController(documentTypes: [kUTTypeItem as String], in: UIDocumentPickerMode.open)
+        let ctrl = UIDocumentPickerViewController(documentTypes: [kUTTypePlainText as String], in: UIDocumentPickerMode.open)
         ctrl.delegate = self
         ctrl.modalPresentationStyle = .currentContext
         _viewController.present(ctrl, animated: true, completion: nil)

The Android implementation, for "text/plain", "application/octet-stream" seems to work for users to select files such as ledger.dat, hledger.journal, or ledger.txt. For iOS I think I only got it to work for files like ledger.txt.

My reason for limiting what files can be picked, is especially in apps where user can write to file, I don't want them to inadvertently corrupt a non text file somehow, such as wedding.jpg, or car-deed.pdf.

I am hoping something can be implemented at file_picker_writable, that would cover my usecase, and potentially others'.

I imagine the difficulty is in generalizing this from my usecase above, of something like plain text on iOS and Android, since each platform may cover a notion like MIME types or file extensions with a different approach in the exposed API. And this is just with iOS and Android, I imagine the problem gets more difficult as we discover what the different APIs will be on other platforms. But iOS and Android may be the most difficult cases, since we might imagine desktops expose more aspects of a file in an API, than do mobile OSes.

I completely acknowledge that the author has priorities, that the package is working great, and it is not difficult to maintain my changes on a fork.

Errors opening from intent/open-in/copy-to are swallowed

If an error/exception occurs while trying to open a file sent from another app by intent (Android) or "Open in"/"Copy to" (iOS), the error is logged (e.g. here) but nothing is reported to the Dart layer.

In case the application wants to show an error in this case, it would be good to have an ErrorHandler that one could register (alongside UriHandler, FileInfoHandler) to be notified when an error occurs.

[IOS] skipDestinationStartAccess warning

Warning: startAccessingSecurityScopedResource is false for file:///private/var/mobile/Containers/Shared/AppGroup/A33CEDC8-AC01-4799-B622-684C1CB35B11/File%20Provider%20Storage/data.xlsx (destination); skipDestinationStartAccess=true

Why am I getting the warning? And why are you abbreviating this filename? Can you remove the abbreviation in this filename and release a new version? I removed it manually but I don't want it to break in any pubscpec process.

  Future<T> _createFileInNewTempDirectory<T>(
      String baseName, Future<T> Function(File tempFile) callback) async {

-     if (baseName.length > 30) {
-       baseName = baseName.substring(0, 30);
-     }

    final tempDirBase = await getTemporaryDirectory();

    final tempDir = await tempDirBase.createTemp('file_picker_writable');
    await tempDir.create(recursive: true);
    final tempFile = File(path.join(
      tempDir.path,
      baseName,
    ));
    try {
      return await callback(tempFile);
    } finally {
      _unawaited(tempDir
          .delete(recursive: true)
          .catchError((dynamic error, StackTrace stackTrace) {
        _logger.warning('Error while deleting temp dir.', error, stackTrace);
      }));
    }
  }
}

Add macOS support

It'd be great to be able to use this for persistently picking files on macOS as well.

Not working on Android when the activity is forcibly restarted by the Android system

Hi!

There is a some kind of bug, that is relevant for me only on real device.

What I have:

  1. Real device with stock Android 10.
  2. Application using simple function FilePickerWritable().openFile():
final fileInfo = await FilePickerWritable().openFile((fileInfo, file) async {
      return fileInfo;
});

What happens further:

  1. The file open dialog opens normally
  2. I find my file
  3. I click on it
  4. The dialog freezes for a while, and then simply disappears / closes. Returning nothing! My debugger cannot catch the moment when the value is returned, it seems to be detached from the process and no longer stops at a breakpoint.

There are no errors in the log, but there are some messages:

D/FilePickerWritable(30527): Got method call: openFilePicker
I/Choreographer(30527): Skipped 76 frames!  The application may be doing too much work on its main thread.
W/ActivityThread(30527): handleWindowVisibility: no activity for token android.os.BinderProxy@b858bba
D/FilePickerWritable(30527): onNewIntent(null)
D/PathProviderPlugin(30527): Use TaskQueues.
D/FilePickerWritable(30527): We have no active result, so activity result was not for us.

The last one is most interesting.
It is hardcoded in your FilePickerWritableImpl.kt:

val result = filePickerResult ?: return false.also {
  plugin.logDebug("We have no active result, so activity result was not for us.")
}

We come here because we really have filePickerResult = null.
But in openFilePicker() method it is aclually set to not null result, I checked this:

fun openFilePicker(result: MethodChannel.Result) {
  ...
  this.filePickerResult = result
  ...
}

It is strange... It looks like setting this value and then checking this value happen on different threads... Or some other reason, I don't know.

Another strange thing is that on emulator it is always working correctly!

I thought there might be some problem in my application.
But I tried a simple application that just opens a file open dialog and then shows the returned file identifier in the dialog.
It is relevant also for this simplest app.
To recreate the simplest application, just place the main.dart file from the archive into the lib folder of the created template application (and add a dependency on file_picker_writable).

Please take a look at this issue!
Try to reproduce this. Perhaps this is not reproduced on every device, but I have a stock Android on my phone, without third-party launchers, etc. That is, the problem may not be that rare.
As a last resort, please try to think theoretically what could be the problem and try to fix it.
Thanks in advance!

Files opened by "Copy to" get filenames mangled

See #3 for a discussion of "Copy-to" files.

Problem

When you open a file by "Copy to", a copy of the file is placed in /private/var/mobile/Containers/Data/Application/$SANDBOX_UUID/Documents/Inbox/. Say the file is foobar.txt.

If you don't do anything with that file, it will just sit there forever.

Then when you "Copy to" foobar.txt again, the new copy will be renamed to foobar-2.txt to avoid overwriting the old copy.

(Why would you "Copy to" the same file twice? For instance opening files from Google Drive only allows doing "Copy to", not "Open in", so if you want to open files from Google Drive in a Flutter app using file_picker_writable then you will encounter this issue.)

Proposal

The only workaround I can see is to delete the copied file when you're done with it. You can delete the file just fine: File.fromUri(Uri.parse(fileInfo.uri)).delete() will succeed. However it's not clear which files are safe to delete this way.

So I would propose a flag: isCopy or isLocalFile or something, to indicate that the file was copied rather than being accessed in-place.

Other solutions I don't like

Just delete all of Documents/Inbox

This seems like overkill. What about use cases where some such files should be retained?

Just delete if the file URI is within the app's sandbox

This is surprisingly cumbersome to do correctly: Using e.g. path_provider you can obtain the app's Documents folder, but the URI is slightly different due to symlinks (one starts with /var, the other /private/var; the former is symlinked to the latter).

final docsDir = await getApplicationDocumentsDirectory();
final realDocsDir = docsDir.resolveSymbolicLinksSync();
final filePath = Uri.parse(fileInfo.uri).toFilePath();
if (filePath.startsWith(realDocsDir)) {
  // File is in app's sandbox
}

So we need another Flutter plugin, a roundtrip through a platform channel, and to hit the filesystem, all to determine something that flutter_picker_writable already knows.

Unhandled exception when reading file on Android

I had a crash report in Google Play Console caused by file_picker_writable trying to read a non-existent file (see amake/orgro#10).

The trace was

java.io.FileNotFoundException:
  at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel (DatabaseUtils.java:149)
  at android.content.ContentProviderProxy.openTypedAssetFile (ContentProviderProxy.java:705)
  at android.content.ContentResolver.openTypedAssetFileDescriptor (ContentResolver.java:1694)
  at android.content.ContentResolver.openAssetFileDescriptor (ContentResolver.java:1510)
  at android.content.ContentResolver.openInputStream (ContentResolver.java:1194)
  at codeux.design.filepicker.file_picker_writable.FilePickerWritableImpl$copyContentUriAndReturnFileInfo$2.invokeSuspend (FilePickerWritableImpl.java:154)
  at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (BaseContinuationImpl.java:8)
  at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.java:93)
  at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely (CoroutineScheduler.java)
  at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask (CoroutineScheduler.java:14)
  at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker (CoroutineScheduler.java:28)
  at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run (CoroutineScheduler.java)

Unfortunately the coroutines ruin the trace, so I can't see how exactly execution got to FilePickerWritableImpl#copyContentUriAndReturnFileInfo. But there is some code path that doesn't properly catch exceptions.

I think one problem might be that you use launch to start coroutines in several places, but have the try/catch outside of launch. Uncaught exceptions in launched coroutines aren't forwarded to the parent scope and will crash the app on Android (link).

Incompatible with other intent handling plugins

I'm trying to use this library (v2.0.0) on Android along with flutter_local_notifications (5.0.0+4), and the latter's onSelectNotification is never being called due to this library saying it can handle all incoming intents.

I've looked into the code, and it looks like this is expected behaviour, but it's not always desirable. Can it be made configurable somehow?

Right now, I'm able to bypass the issue by uncommenting this code, which makes the plugin only handle a few specific content schemes:

// if (scheme == null || !CONTENT_PROVIDER_SCHEMES.contains(scheme)) {
// plugin.logDebug("Not handling url $data (no supported scheme $CONTENT_PROVIDER_SCHEMES)")
// return false
// }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.