Open files in external apps from Firemonkey

It’s rather a common scenario when a Firemonkey mobile app needs to open a file. It might be a PDF document, an image or video. And while opening a file directly inside of Firemonkey app is totally a legit strategy, sometimes it may be not so easy to implement. TImage provides great support for various graphics formats, but what about PDF? Implementing PDF support for multiple platforms may be quite a challenge. Luckily, both iOS and Android provides a shortcut, we can ask OS to open a document in a third-party application of user’s choice.

And beyond that, mobile OS usually provides more options than just “open a file”. We can also share or print a file, which may be useful for users.

And while both Android and iOS support that workflow, each platform has its own caveats and difficulties and SDK’s take different approaches to this task. This factor is a good reason to make cross-platform wrapper which offers a common platform-agnostic interface for opening files (and URLs) in external applications.

Firemonkey External File Viewer

The completed solution is called “firemonkey-external-file-viewer” and published on GitHub – https://github.com/Code-Partners/firemonkey-external-file-viewer

It exposes a common cross-platform interface and encapsulates all platform-specific details. Under the hood, external file viewers call native APIs.

The only thing you need to do in order to integrate it into your project is to clone or download git repository and include Source\ folder and its subfolders into the project (or Library path in RAD Studio).

How to use

First, create an instance of TExternalFileViewer using Factory() method:

class function Factory(AOwner: TComponent; AForm: TForm): TExternalFileViewer;

AForm argument impacts only iOS implementation and defines a parent control for showing a popup window (more details about that below).

An object of TExternalFileViewer class has 2 methods:

  • procedure OpenFile(Path: string);
  • procedure OpenURL(URL: string);

Names of both methods are self-explanatory. Note, that in Path argument of OpenFile method you can supply paths to both shared files and files which are bundled inside of your app (how to access embedded files on Android and iOS).

Git repository contains a sample Delphi project which demonstrates the most common use cases:

  // open embedded PDF file
  self.FViewer.OpenFile(System.IOUtils.TPath.GetDocumentsPath() + PathDelim + 'svn-book.pdf');
  
  // open remote PDF by URL
  self.FViewer.OpenURL('https://github.com/progit/progit2/releases/download/2.1.45/progit.pdf');
  
  // open remote image by URL
  self.FViewer.OpenURL('https://git-scm.com/images/logo@2x.png');

This class can be used in C++ Builder projects as well, integration is the same as for Delphi projects.

The library supports Android and iOS. On other platforms, it will compile fine, but Factory method will return a dummy instance which does nothing. This approach allows testing your Firemonkey on other platforms without being forced to wrap TExternalFileViewer with conditional compiler directives in order to evade compile-time or runtime errors.

If you are interested to know about platform-specific implementation details, please see below.

Android implementation details

The biggest concern with Android implementation is that external apps can’t access embedded files, which means that we need to copy them to a shared directory first. Due to differences across all of the Android vendors, it’s quite a tricky question of which directory will work for all of them. The current implementation uses Environment.getExternalStorageDirectory() + /Documents directory. Please note that this directory does not get cleared automatically by Android. By default, Android headers in Firemonkey do not contain a header for android.os.Environment class from Android SDK, so it was generated with Java2OP and added to the project.

Another thing to note is that Android associates apps not with file extensions but with MIME types. In order to find a MIME type for any given file extension, I used the approach described in https://stackoverflow.com/a/31691791/2899073 which leverages android/webkit/MimeTypeMap class.

With this being done TExternalFileViewer is able to query Android to open embedded or shared files with various file extensions. When OpenFile function is called, it shows the native dialog with applications which are registered to open given file type.

iOS implementation details

iOS has the more convenient workflow for opening bundled files, we don’t need to copy them to the public folder, iOS will handle access rights implicitly. Although, iOS approach is more bound to iOS SDK – we need to communicate with UIDocumentInteractionController class, provide it with UIDocumentInteractionControllerDelegate delegate (event listener).

Firemonkey SDK contains headers for both of these types. We need to instantiate TUIDocumentInteractionController, set a delegate with setDelegate method and show a popup window by calling presentPreviewAnimated method.

Then, iOS calls documentInteractionControllerViewControllerForPreview of delegate. This method is meant to return an instance of UIViewController which will be used as a parent control for the popup window.

We need to retrieve an UIViewController of the current Firemonkey form:
Controller := WindowHandleToPlatform(self.FViewer.FForm.Handle).Wnd.rootViewController

1 thought on “Open files in external apps from Firemonkey”

  1. Pingback: Delphi Blogs of the Month #60

Leave a Comment

Save up to 35% off Delphi, C++Builder and RAD Studio for anotherShopClose Countdown
Scroll to Top