Sharing Files on Android or iOS from or with your Qt App - Part 3

Welcome to Part 3 of my Blog series about Sharing Files to and from mobile Qt Apps.

Part 1 was about sharing Files or Content from your Qt App with native Android or iOS Apps, Part 2 explained HowTo share Files with your Qt App from other native Android Apps – this part now does the same from other native iOS Apps.

01_blog_overview

Some preliminary notes

  • Try it out: all sources are available at ⦁ GitHub
  • Android Permissions not checked in Example App: enable WRITE_EXTERNAL_STORAGE manually
  • Current Android release is for Target SDK 23 ! (Android 7 requires ⦁ FileProvider – wait for Blog Part 4)
  • All Use-Cases are implemented to be used x-platform – currently Android and iOS

If you‘re looking for an Android-only solution please also take a look at AndroidNative project.

Prepare iOS Info.plist

Similar to Android intent-filter in AndroidManifest.xml we must add some informations to Info.plist. If you haven‘t already done copy the Info.plist from your build directory into your project:

02_info_plist_in_project

Then add this into your .pro:

ios {
    ...
    QMAKE_INFO_PLIST = ios/Info.plist
    ...
}

Open the Info.plist and insert these lines:

<key>CFBundleDocumentTypes
  <array>
    <dict>
        <key>CFBundleTypeName</key>
        <string>Generic File</string>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>LSHandlerRank</key>
        <string>Alternate</string>
         <key>LSItemContentTypes</key>
           <array>
              <string>public.data</string>
           </array>
    </dict>
  </array>

See all the details from Apple Documentation „Registering File Types“.
Here‘s a short overview how the Types are organized:

03_uniform_type_identifiers

public.data is similar to MimeType */* we are using to filter Android Intents in our Example App.

After adding the lines to Info.plist our App will appear as a target App if Files or Attachments should be shared from other iOS Apps.

Fortunately iOS doesn‘t provide our own App as target if we want to share Files from our Example App with other iOS Apps.
Remember: On Android we had to create a custom Chooser to filter out our own App as target.

Get the incoming URL

The Files from other iOS Apps will be passed to the Qt App via application:openURL:

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation

see also Apple documentation.

From Part 2 (Android implementation) you know that we have to wait until the Qt App is ready to handle the incoming URL and be able to emit a SIGNAL to QML UI.

We don‘t need this for iOS, because there‘s a Qt method we can use out of the box and grab the passed file url by registering QDesktopServices::setUrlHandler:

QDesktopServices::setUrlHandler("file", this, "handleFileUrlReceived");

This is easy to implement. We already have the IosShareUtils.mm class where we handle sharing Files to other Apps. Adding some lines of code:

IosShareUtils::IosShareUtils(QObject *parent) : PlatformShareUtils(parent)
{
    QDesktopServices::setUrlHandler("file", this, "handleFileUrlReceived");
}

void IosShareUtils::handleFileUrlReceived(const QUrl &url) { QString myUrl = url.toString(); // … remove "file://" from Url // … then check if File exists QFileInfo fileInfo = QFileInfo(myUrl); if(fileInfo.exists()) { emit fileUrlReceived(myUrl); } else { emit shareError(0, tr("File does not exist: %1").arg(myUrl)); } }

That‘s it :)

Thank You @taskfabric

Seems this is the first time I can tell you that there‘s easy stuff to manage Filesharing from Qt,
but I never would have found it out by myself. Didn‘t expected to take a look at QDesktopServices for sharing File URLs on Qt Mobile Apps.

Thanks to Thomas K. Fischer from Taskfabric.com who offered help at Qt Blog Comment
This is the real value of a great Qt Developer Community: Thomas appreciated my work on this sending me some lines of Code and pointing me to QDesktopServices. As a result it was easy for me to implement and publish the updated Sharing Example at Github.

Overview

Here‘s a short Overview about the iOS implementation:

04_handle_url_from_ios_apps

Test it!

Now it‘s a good point to test it.

Open „ekkes Share Example“ and navigate to one of the Tabs.

Open per ex. Apple Keynote App to share a File.

Here‘s the workflow from Apple Keynote App.
Tap on the SHARE Button:

05_keynote_share_01

Select „SEND COPY“:

06_keynote_share_02

„ekkes Share Example“ appears as Target:

07_keynote_share_03

Select ‚ekkes SHARE Example‘ and iOS will open the App. The App was already opened, so the current selected Page will be displayed and include a message about the received File.

08_keynote_share_04

So it works :)

No Qt Support for iOS Share Extensions

Attention: Sharing with a File URL works for all Apps providing Files or Attachments, but to handle Text or embedded Images shared from other Apps needs to be implemented with a "share extension". See the details here.

Share Extensions are easy created using Xcode, but ...
This is quite tricky to use with Qt, as the extension needs to be registered in Xcode and qmake overwrites the Xcode file on each run.

So Share (and other) Extensions cannot be included automatically while building from QtCreator and breaks workflows. See discussions https://bugreports.qt.io/browse/QTBUG-40101.

Hopefully Qbs will support this in the near future, because iOS App Extensions are an essential feature of iOS Apps in the meantime.

Some issues from Sharing Files to other Android Apps

Android: Sharing from Microsoft Word (and other MS Office Apps)

There are differences between Android 6 and Android 7 versions of MS Office Apps.

In both cases the Qt App received a SEND Action and a Content Url

MS Word:

content://com.microsoft.office.word.fileprovider/94161e0b-e48a-4142-9785-d0602d801e95/test.docx

Using QSharePathResolver.java class this File Url could be constructed:

/data/user/10/com.microsoft.office.word/files/tempOffice/Share/ff3251b4-c475-48e2-a7cc-f1df02d69874/test.docx

On Android 6 Devices the File Url could be opened and read without any problems from QFile – on Android 7 Devices I got a „File does not exist“ Error.

Other Apps using SEND Actions and Content Urls are working same on Android 6 or Android 7 Devices, so it‘s something special with MS Office Apps.

Testing some other ways to handle the File Url I found out that on Android 7 Devices for MS Word I can use InputStream for the given FileUrl. So I added an extra check for situations like this:

public class QShareActivity extends QtActivity
{
	// ...
    public static native boolean checkFileExits(String url);
}

bool AndroidShareUtils::checkFileExits(const QString &url) { QString myUrl; if(url.startsWith("file://")) { myUrl= url.right(url.length()-7); } else { myUrl= url; } QFileInfo fileInfo = QFileInfo(myUrl); return fileInfo.exists(); }

JNIEXPORT bool JNICALL Java_org_ekkescorner_examples_sharex_QShareActivity_checkFileExits(JNIEnv *env, jobject obj, jstring url) { const char *urlStr = env->GetStringUTFChars(url, NULL); Q_UNUSED (obj) bool exists = AndroidShareUtils::getInstance()->checkFileExits(urlStr); env->ReleaseStringUTFChars(url, urlStr); return exists; }

Android: Sharing from Google Docs App fails if Qt App is open

In Android Manifest the Launch Mode is declared as

android:launchMode="singleInstance" android:taskAffinity=""

SingleInstance is the only launchMode where always the same instance of our one and only Activity will be opened. This enables workflows where User already logged into an App and navigated to a Page and if shared from another App exactly this Page will be displayed.

This works well and you can test it from „ekkes Share Example“ App: Open the App, select a specific Tab (Page), then open another App and share a File with the Example App. Our Example will be opened exactly on the selected Page.

If the App was open, onNewIntent() from our customized Activity should be called, but Google Docs always calls onCreate() - doesn‘t matter if the Target App is already running or not. Then a white screen comes up, the App hangs and must be closed. The Debug Log reports:

E Qt JAVA : Surface 2 not found!

I haven‘t found a way to workaround this yet - perhaps anyone else has an idea HowTo fix ?

Have Fun

Now it‘s time to download current Version from Github, build and run the Sharing Example App.

Stay tuned for the next part where FileProvider will be added to share Files on Android.


Blog Topics:

Comments