Introduction: Downloading and Opening Files with Capacitor
What's up samurais, welcome back to a new quick win. And today we're going to talk about files and downloading files. It's actually, I think, or it should be a quite simple topic, but there are a few things that you should watch out for. So today, we will implement a simple app using Capacitor to download files—PDF, image, video—and also open them on our device using Capacitor.
The only thing you need for this tutorial is a blank new Ionic application and actually an Ionic Native plug-in. There is also a Capacitor plug-in for opening PDFs but that's somehow not really maintained anymore. So I just used the Cordova plug-in. If you don't know, using Cordova plug-ins with Capacitor simply means calling npm install for the Cordova plug-in and also of course for the Ionic Native wrapper.
Initial Setup: HttpClientModule and FileOpener Provider
Now that you got your application in place, we need one little step before we get into all the coding. And that's adding the HttpClientModule to the array of imports because we're making a simple GET request, actually using the HttpClient to download files. So it's really as easy as that.
And whenever you use Ionic Native plugins or Cordova plugins, you add the wrapper from the package ngx to your providers array, and then we can move on to our home page and homepage.ts, because that's where we're going to work most of our time.
Component Setup: Variables and Capacitor Plugin Imports
Now, first of all, I'll bring in a few variables. So I found this great page, can we open it somehow, called File Examples, which allows, come on, to download some dummy files—video, audio, documents, images—all kinds of files in different formats and different sizes. I don't know who created that page, but that person was a genius because that's just awesome for testing. So from that page, I copied three URLs for a PDF, video, and an image.
We will also monitor the download progress, a quick little example of creating your super simple own download bar. We will also keep track of the files using Capacitor Storage. And finally, also allow download from any URL in case you got a file anywhere and want to test this. So we need a few more things. We need the plugins from Capacitor, from which we will destructure into using Storage and FileSystem, because we will actually write the files to our real app system after download. And we need the File Opener that we installed using Ionic Native.
Now for the constructor, of course, we need our HttpClient, not the module just the HttpClient, and the FileOpener.
Helper Functions: Loading Files and Blob-to-Base64 Conversion
So that's for our dependencies. Now right in the beginning, we can actually call a function loadFiles, because, well, because it's the most boring part. So loading files, as we also did in I think the previous quick win, is a simple call to the Capacitor Storage using a key which you can either define inside your class or you can just define it up front. Or if you want to use it somewhere else, you can also add export to use it somewhere else.
Okay, yeah this dot load files, and then you have to parse the result as the Capacitor Storage is just plain text. So we will store everything JSON stringified and then parse it back once we get it or set it to an empty array in the beginning.
I also added two little helper functions. Let's start with the second one to get the MIME type. This is necessary for the file opener if you want to pass in the right MIME type. I just created it for the three cases that we have and with a really, well, very simple check, but of course, I think there are libraries or code snippets available for more MIME type checking. So just create your own if you want to.
Also, if we want to store files with Capacitor, we need to store a base64 string because Capacitor passes this information through a bridge and then stores it on the native device, and therefore we need a base64 string. But the file that we download will be a blob first. Now there's also a Capacitor community plug-in to directly store a blob that you could use. Actually, maybe I find the URL somewhere. Yeah, there it is. So that would be called Capacitor Blob Writer. I don't want to make this tutorial more complicated. Actually, it looks pretty simple: npm install, a bit of setup for android, and then importing the right file from the Capacitor Blob Writer. So if you plan to store bigger files, I would definitely recommend this because this extra step for the conversion to base64 can take some time and decrease the performance of your application.
Now let's get into something more funny, and that's to download a file based on perhaps a predefined URL. I just wanted to add this so you can either insert a URL inside our home page or you can also directly use one of the predefined URLs.
Let's also quickly add a bit of code to our HTML file now. So I feel like I always get rid of this dummy stuff. I don't know if I'm the person using most of this. Well, anyway. So we get started with a little input which will be tied to our download URL, which means if you want to test this quite fast, just add your own download URL in here. But since we've defined a few of the URLs for PDF, video, and image, I also added like a shortcut row for your testing, with downloadFile passing in the PDF URL, the video URL, or the image URL. And once we get this in place, we're actually a step further. So now we can call our download function. So this will set the URL, otherwise, if you just pass or add something to the input field, that value will be used.
Core Download Logic with HttpClient and FileSystem
Now let's continue because now things are about to get interesting. We're going to make a HTTP GET request to download this.downloadUrl. The important part is now the options that we pass to this request, otherwise it's just a standard request. We set the responseType to blob so we can simply store the data later. And two more things, I want to reportProgress and I want to observe all events.
Those two parts are just for showing the progress. So if you're not interested in the download progress of a file, then just get rid of this. But I thought since we're downloading files which can be a bit bigger from time to time, we should also add this little check. And once we got this in place, we need to add a little check inside the subscribe button. So usually you just get back the data, but because we observe all the events, we need to check if our event type is DownloadProgress or Response. And if it is DownloadProgress, we will just calculate the current progress. It will be a value between 0 and 100. And once we get to the other block, we can reset our download progress, since in that case it means our download has finished.
Now, let's step through the next step. First of all, we need a new file to store our file. At this point, we just have a blob of data, but since we already got the download URL to a file, I would just recommend you use that name or perhaps from your API you got a different name. But if you got this, we can call our helper function and pass in event.body. So usually you get like the result here. Now we use event.body since we have a tiny bit different setup for our HTTP GET request.
Once we got the base64 data string, we can actually write to the file system using the Capacitor file system plugin. We pass in a name, which is like whatever.pdf, then we pass in the data, and then we get a path to the Documents folder, which could be like data whatever, or in this case I think it's like Documents for iOS, and then writeFile will write the file basically to this location inside the documents directory, and we are happy now.
Opening Files with a Cordova Plugin and Saving to Storage
Of course, that's not all. Although we could do this now already, let's say log saved. So that helps to at least see a bit of progress inside our application. Let's try to download the PDF, and then it's stored right here.
To really open this, we need a bit more logic. Especially the opening won't really work inside the browser now because we're using Cordova plug-ins. But for the device, the next steps would be to get the URI path... Actually, isn't that like the URI path? I think... I actually think... that we're fine and we don't need this. This is from a previous version. So within savedFile we should actually have the URI. Let's be a bit adventurous and use this URI...
I'm not sure where this will lead us now. To call our file opener, we also need the MIME type. I will just leave this here in case we need to revisit this code, which might happen. And then we can use the file opener to call either open or showOpenWithDialog. That's completely up to you. open will just use the default application. The dialog will give you options to select any other application from your device.
And finally, since I also wanted to store the files, we will just add the path to my files array and then store this back to storage. I think this code mostly looks like the video capture tutorial that we recently had, so that should be fine.
Device Demo and UI Enhancements
So here we can download on a device. Actually, let me bring this in. I got live reload up as well. On a device, it should actually download the file and immediately open it. So great, that works pretty good. I'm actually amazed this automatically works. And for MP4, yeah, it also works. I'm more than happy. I've seen worse tutorials and demos.
Alright, working great on a device. Just a few more things. First of all, we don't have our progress bar yet. So we're already setting the download progress right here. And therefore I just added like the most easiest version of a div style right in here. Please don't do this in a real application, use your styles file. I just wanted to make this most easy with a width downloadProgress and percent and only display it on download progress.
Besides that, we also don't yet have the list for showing our files. So I use the ion-item-sliding in this case because then we got our item here for opening a file and also on the end we have a little delete file, just as a little more example.
Implementing File Management: Open and Delete
But I'm deleting a file is really, it's really quite boring. Let me give you the code. So for deleteFile, once again, we call FileSystem.deleteFile. I really like working with a file system because it's so easy. You just need to understand which directory to pass in here. So this directory should of course be the same like here. Yeah, which it isn't of course. Good that we talked about this. Did I use data somewhere? Nope, hopefully not. So of course, otherwise the file wouldn't be found.
And the name once again, getting the real name of if you have like foo/bar/my_file.pdf, this substring means start here and create a substring with this path. Yeah, not not this really, I think you know what I mean, right?
Alright, what did we miss? The play, the play, no, open. Open, async openFile, for which we will use our Cordova plugin in fact. So the start is once again the same.
Live Debugging and Code Refactoring
And now I think in... I think we have stored a kind of strange path, let me look into this. So this is the path we stored, which actually looks kind of nice to me. I'm not sure. Let's say, log open our file and then I will also log out the path again because I think we needed to convert it or get the actual URI path. And then once you once you get the path and the MIME type, it's actually just calling the file open again. This time I just used showOpenWithDialog. I'm just really curious to see the path, the different paths in here or the converted passes.
So a little live debugging, remote debugging. If you haven't used live or remote debugging on Mac, you can simply use Safari for iOS, or you can use Chrome for Android. Simply open Safari from the menu, go to Develop, pick your phone, pick the website, and then hope that you see something in the logs.
So with our live reload in place, let's try to open a file. We try to open this file and convert it. It's actually, well, it's basically the same. So I think we can leave out basically everything from that function. Oh no, that was my whole class. Because in the beginning, I created this tutorial and I had the wrong path. So at this point, since we stored the right value, I think we're just fine. Yep, we're just fine. Opening the file directly like this. If you see your file path being like the ones you saw in the debugging tools, like file://whatever, then everything is fine for you. I can also hopefully delete files. Yep, again.
Then let's move back to our function right here, the download function, which we also removed this get URI path. So let's give this one a try again. Yep, that one also works without anything else.
Recap and Final Code Cleanup
And you also see our little download bar at the top of our view. So let's just remove the last path. Checking the download file again. We just need our saved file which now or by now returns the URI as well. So no more URI, we're fine. Because we then store the right path to our file, it's easy within both deleteFile and openFile to directly use this path inside our application.
And I think I think that's all. We covered everything, right? Did we? We can download files, you could insert your own URL, and you can with quite easy tools—or basically the native, or unless no, native in this context is wrong—the standard Angular tools, meaning the HttpClient, you can download files as a blob. You can then use that result, pass it to Capacitor with a little transformation to base64, or using the package that I highlighted in the beginning to directly write blob to the file system. Whatever you prefer. As you can see, for small files it's not really a huge problem.
Conclusion and Further Learning
But if you're downloading like 100 megabytes or anything above, I would really recommend you check out the other plugin. If you got any problems with that, just let me know, we will talk about this. And otherwise, I hope this quick win helped you to implement a cool download and open functionality within your own Ionic Capacitor apps.
If you enjoyed this video, please hit the like button and stay subscribed so you get notified about all the new tutorials, quick wins, and other app development and web development videos on this channel. If you want to learn more about Ionic with in-depth courses, a community of like-minded developers so you can learn and build your apps faster, you should definitely check out the Ionic Academy, which is my code school to help you with everything Ionic with a huge library of courses, material, and a supportive Slack channel so we can get your app out.
I hope you enjoyed this video. I will see you inside the next video. Have a great day and happy coding.