Introduction
The other day I needed to download a PDF into a device and show it. I thought about an Apache Cordova project. To accomplish this, I made a little example app with Apache Cordova and AngularJS. This app can download a PDF from a URL, store it and show it to the user.
Source Code
The source code accompanying this article can be found on Github.
Plugins
The app requires plugins to work. The following list show the plugins needed and his utility.
InAppBrowser
- It allows opening new navigator’s window and even opening the system navigator opening. File
- Access to files paths FileTransfer
- Download Device
- It allows you to see information of the platform in which the app is running.
NOTE: You may need the Whitelist plugin.
Plugins work on Windows Phone and Windows 8, however, I haven't tried to run it on Windows Phone and Windows 8 in this example.
Platform Singularities
In this development came into play some peculiarities of each platform. Therefore, creating one source code for every platform is complicated.
Android
- It CAN’T open a new navigator’s window into the same app to show PDFs
- It CAN open system navigator. Also the system ALLOWS CHOOSING the app that will open the file.
- It has SOME SHARED DIRECTORIES
iOS
- It CAN open a new navigator’s window into the same app to show PDFs
- It CAN open system navigator. However the system DOESN'T ALLOW CHOOSING the app that will open the file and show the file into the navigator system.
- It has ONLY PRIVATE DIRECTORIES.
Solution
Android: PDF file will be downloaded and stored into a shared directory and it will be opened by the system. You need to store it in a shared directory so that other apps can get access to it.
iOS: PDF file will be opened into a new navigator’s windows into the example app. You need to open a window into the app because on iOS you can’t share files with others apps.
In a nutshell, only two parameters should be dependents on the platform: file path and how to open a file.
In the File Plugin documentation, you can find tables that indicate which paths are read/write or which paths are privates. Click here.
Using the Code
The main functionality is split in two AngularJS services. The first is a download service to download and save a file from an URL. The other one shows a file from an URI, this service can show files from an URL or from a local storage system.
Download Service
For the example, the service was declared like a factory and the first thing to do is to decide where to store files. For that, we use the "deviceready
" event to know when the plugins will be available.
This service use data directory for iOS and the external application storage directory for Android.
angular.module("pdfViewer.download").factory('downloadService', ['$document',
function ($document) {
var _saveDirectory = "";
var _fileTransfer;
$document[0].addEventListener("deviceready", function () {
_fileTransfer = new FileTransfer();
if (device.platform === "iOS") {
_saveDirectory = cordova.file.dataDirectory;
}
else if (device.platform === "Android") {
_saveDirectory = cordova.file.externalApplicationStorageDirectory;
}
else {
_saveDirectory = cordova.file.dataDirectory;
}
}, false);
return {};
}]);
Now, the service needs to expose a method that implements the download function. The download function creates a directory called "PDF" into the storage directory, then download the file by URL parameter.
Download function returns the file path in a promise.
angular.module("pdfViewer.download").factory('downloadService', ['$document', '$q',
function ($document, $q) {
var _saveDirectory = "";
var _fileTransfer;
$document[0].addEventListener("deviceready", function () {
_fileTransfer = new FileTransfer();
if (device.platform === "iOS") {
_saveDirectory = cordova.file.dataDirectory;
}
else if (device.platform === "Android") {
_saveDirectory = cordova.file.externalApplicationStorageDirectory;
}
else {
_saveDirectory = cordova.file.dataDirectory;
}
}, false);
return {
download: function (url, fileName) {
return $q(function (resolve, reject) {
window.resolveLocalFileSystemURL
(_saveDirectory , function (dir) {
dir.getDirectory("PDF",
{ create: true }, function (finalDir) {
if (!_fileTransfer) {
reject('error.noTransfer');
}
if (!_saveDirectory) {
reject('error.noDirectory');
}
var fileURL = _saveDirectory + fileName;
var uri = encodeURI(url);
_fileTransfer.download(
uri,
fileURL,
function (entry) {
resolve(entry.toURL());
},
function (error) {
reject(error);
},
true
);
});
});
});
}
}
}]);
Viewer Service
Same as before, the service was declared like a factory and the first thing to do is specify the platform dependant parameter. Then, the service exposes the open function.
angular.module("pdfViewer.viewer").factory('viewerService',
['$document', '$window', function ($document, $window) {
var _type = "";
var _open;
$document[0].addEventListener("deviceready", function () {
_open = $window.open;
if (device.platform === "iOS") {
_type = "_blank";
}
else if (device.platform === "Android") {
_type = "_system";
}
else {
_type = "_system";
}
}, false);
return {
showPDF: function (fileToShow) {
_open(encodeURI(fileToShow),_type,'location=no')
}
}
}]);
In the source code, you can see an example controller and view for downloading and showing a PDF with this service.
Points of Interest
I need download, store and show a PDF. However, you can use these services for download, store and show other file types (like docx).
I made this example using Visual Studio 2015 and his Apache Cordova Tools. I'm working to improve the code.