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.
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).
First, create an instance of
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://email@example.com');
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.
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
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 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
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