Native iOS/Android file choosers


#44

Have you got the latest version of Dropbox? I know there were some bugs, which got fixed in later versions. It definitely works with Dropbox on my iOS 11 device.

As for Google Drive saving, I had the same, I think it is a bug in Google Drive, all the operation finishes successfully on our end. I tried that on iOS 11 device, so on a fairly recent system. I could not get it to install on my iOS 10 device. When I do the same on Android, then it correctly uploads the file to the drive.

As for reading, where are you reading from?

No worries with the news, it’s much better to hear about them now than after the release to the master branch :slight_smile:


#45

Its the same for iCloud, Dropbox, Google Drive.

All apps up to date - GoogleDrive last updated today and Dropbox yesterday.


#46

Can you try uninstalling Dropbox and installing it again? I definitely remember someone saying that Dropbox had to be uninstalled completely before installing again. As for the crash on reading, I will send you a code diff to try shortly.


#47

will do - FYI, writing to GoogleDrive was working ok the other day before these latest changes…


#48

Nice idea - cured the issue. Can now write to Dropbox


#49

Phew, one less to worry about. We will get to the bottom of this!


#50

Ok, we have sorted it out (thanks @leehu!). I have pushed in commit 7297f62 a fix to the crash and I have also updated the docs on how to use the returned URL on iOS:

    //==============================================================================
    /** Returns the last document that was chosen by one of the browseFor methods.

        Use this method if you are using the FileChooser on a mobile platform which
        may return a URL to a remote document. If a local file is chosen then you can
        convert this file to a JUCE File class via the URL::getLocalFile method.

        Note: on iOS you must use the returned URL object directly (you are also
        allowed to copy- or move-construct another URL from the returned URL), rather
        than just storing the path as a String and then creating a new URL from that
        String. This is because the returned URL contains internally a security
        bookmark that is required to access the files pointed by it. Then, once you stop
        dealing with the file pointed by the URL, you should dispose that URL object,
        so that the security bookmark can be released by the system (only a limited
        number of such URLs is allowed).

        @see getResult, URL::getLocalFile
    */
    URL getURLResult() const;

File browser on android
#51

Hi Lukasz, done some testing on Android.

Saving to Googledrive works fine. One point is that if I launch the save browser with “*.xml” and I create “myfile”, it would be nice if the filechooser returned “myfile.xml” as the result rather than myfile - I believe the desktop versions do this so for consistency Android should to.

As for reading, I get a sporadic crashes when trying to create the input stream with the following code:

	FileChooser *p_fc = new FileChooser( title, File::getCurrentWorkingDirectory(), filter, true );

	p_fc->launchAsync( FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
					   [ callback ]( const FileChooser& fc ) {
        if ( !fc.getURLResult().isEmpty() ) {
			StringPairArray responseHeaders;
			int statusCode = 0;

			ScopedPointer<InputStream> p_stream( fc.getURLResult().createInputStream( false, nullptr, nullptr, String(),
																					  10000, // timeout in millisecs
																					  &responseHeaders, &statusCode ) );

Which is the same code that works fine on iOS.

Out of action till tomorrow aft now, but will try and give more details then. cheers


#52

Thanks leehu for the report. I will add this to my list.

It would be useful to know where it crashes and what is the problematic URL (i.e. content://<interesting_stuff_here>).


#53

Hi, having problems getting the browser to update.

This is an image of the actual file browser:

and this is what is displayed in the JUCE browser:

Changes outside of the app are not being reflected.

I also don’t appear to have access to any local drives folders apart from the Downloads folder, which I can’t write to. Should I be able to write to other SD locations? thx


#54

Hi leehu,

Saving to Googledrive works fine. One point is that if I launch the save browser with “*.xml” and I create “myfile”, it would be nice if the filechooser returned “myfile.xml” as the result rather than myfile - I believe the desktop versions do this so for consistency Android should to.

I’ve checked the behaviour of file chooser on Mac and it does not append file extension automatically too. I would rather keep it this way than changing it, to avoid introducing a potentially a breaking change for others.

As for reading, I get a sporadic crashes when trying to create the input stream with the following code:

I have tried the following code with a few targets with no issues. Once you reproduce the crash, could you provide more info on where it crashes? Also, what is the problematic URL?

Hi, having problems getting the browser to update.

Can you check your account setting on your phone? For each account you should have syncing options for Google Drive and other apps:

I deleted some stuff on my drive from Mac, then I checked the Drive contents from our file chooser on the phone and it was out of date. Then I checked Google Drive app on the phone and it was also out of date. I went to account settings and I synced contents of Google Drive - then both in the Drive app on the phone and in our file choosers on the phone the content was up to date. Can you check your settings and let me know if syncing solves your issue?

Btw, could you remind me which Android version and device you are using?


#55

will do - FYI, writing to GoogleDrive was working ok the other day before these latest changes…

I’ve tested that both on latest develop on iOS 10.2 and iOS 11.0.3. TLDR; IMHO Google Drive extension for iOS has issues but I am able to write to it. If you want to save files to GD, I suggest preparing file contents locally first and then passing that file as initialFileOrDirectory in FileChooser constructor.

When saving a file on GD, we always receive documentPickerWasCancelled reply and hence the results array is empty. The file is however written to drive - make sure you are logged in to your GD account and try opening GD application on your device first, also try writing to some test folder first, rather than to main Drive directory (which works too). On iOS 11, GD has an additional problem due to missing new required code to support changes introduced in iOS 11. This has been reported for a number of apps: https://www.dropboxforum.com/t5/Mobile/iOS11-UIDocumentPickerViewController-does-nothing-after/td-p/240084
or https://stackoverflow.com/questions/46347574/dismissgrantingaccesstourl-failing-with-didpickdocumenturls-called-with-nil-or (we do get UIDocumentPickerViewController : didPickDocumentURLs called with nil or 0 URLS error in console).

Until GD gets updated, I recommend the workaround in TLDR section. Let me know if the workaround works for you.


#56

Thx @lukasz.k - I’m back from being away for a few days so will go through your posts today and let you know how I get on… thx!


#57

Hi, If I run this code on OSX/Windows I get .xml appended - I’m pretty sure it has to work this way otherwise it wouldn’t be able to process the dialog for overwriting a file of the same name as it wouldn’t know that you were going to append an extension and so you’d have to do all that processing yourself. I’ve just pulled the latest develop so this is running on the latest tip.

	FileChooser *p_fc = new FileChooser( title, File::getCurrentWorkingDirectory(), "*.xml", true );
    p_fc->launchAsync( FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles,
                      [ = ]( const FileChooser& fc ) {
                          auto target = fc.getURLResult();
                          
                          DBG( "target: " << target.toString( false ) );
                      } );

Additionally, as the results from the browser for Android are of the form:
content://com.google.android.apps.docs.storage/document/acc%3D1%3Bdoc%3D98
I can’t append my own “.xml” onto the file as far as I can see - it needs to be done before hand.


#58

Confirmed that the workaround work - thanks!


#59

Yes, manually updating solves the problem - thanks, wasn’t aware you needed to do that. Found an auto-update option too.

Is there any way to get the actual filename of the physical file, e.g. the results being returned are of the form:

content://com.google.android.apps.docs.storage/document/acc%3D1%3Bdoc%3D92

Thx

EDIT: Done some more testing on saving and any saved files do not pass the filter, i.e. are greyed out, e.g. if I save a file called “test.xml” then even though when trying to load a file with filter “*.xml”, anything I’ve copied across from desktop are loadable, but files saved on the tablet are not.

EDIT: Also, should we be able to access internal storage through the browser? thx


#60

Is there any way to get the actual filename of the physical file, e.g. the results being returned are of the form:
content://com.google.android.apps.docs.storage/document/acc%3D1%3Bdoc%3D92

If that’s a local file, have you tried using URL::getLocalFile() ? If that’s a remote resource, can you investigate response headers you get? In particular Content-Disposition may be of interest to you.

EDIT: Also, should we be able to access internal storage through the browser? thx

The native browser supports locations exposed by the OS as well as other apps. Android OS exposes Downloads, Images and Videos locations. To access internal storage, use non native dialog provided by JUCE. Lastly, when saving data using native dialogs, the resulting files show as 0 bytes (not sure yet why), but they will be written correctly - just remember to flush your output stream, as I have now done in demo here


#61

Hi, all files on Android are remote it would seem. The response headers are empty.


#62

Ok, I revisited this. On Mac, OSX appends the extension automatically, it’s not us doing it manually. When we receive a callback from Android OS about a file being chosen we are not allowed to modify the filename by adding an extension. This is because Android gives only access permission for a file which was specified by a user in the chooser - any attempts to tamper with it result in a security exception being thrown. This is a deliberate feature of content providers on Android, so a user needs to specify a filename with an extension I am afraid.


#63

I have added in commit 3b130b1 a new function to URL which you can use to obtain the underlying file name:

    /** Returns the file name. For all but Android's content:// scheme, it will
        simply return the last segment of the URL.
        E.g. for "http://www.xyz.com/foo/bar.txt", this will return "bar.txt".

        For Android's content:// scheme, it will attempt to resolve the filename
        located under the URL.
    */
    String getFileName() const;

the joys of Android :slight_smile: