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
Pingback: Delphi Blogs of the Month #60