In this article, I will explain how to download assets dynamically in a Flutter app so that they can be accessed later in offline mode.
Why would I ever need to do anything like that?
Let’s imagine I want to build an app about making cakes. I will include recipes, pictures, and videos, it will be super cool and full of amazing content, so I will upload all my those assets into S3 and get a nice and lean build.
Now, I decide I want my app to work offline. No biggie, I will include all my assets as part of my build. Suddenly my app goes from 5 or 10 megabytes to 50 or even 500 megabytes (yes, I like videos). Not ideal, but hey, that’s life, who am I to question my product owner common sense?
The app is doing awesome, let’s make it even better. What if… instead of just cakes, we add some other themes, like cocktails, vegetarian dishes, etc. Suddenly our app goes from 500 megabytes to 3000. Now we have a problem.
How do we fix this mess?
Even with today's devices, I don’t think any user would be happy to download such a big app, so probably I would do the whole offline mode an optional feature. But what we could also do is download this content on-demand. This way, a user could download our app and then the content for cocktails but forget about cakes (think about Google maps offline mode).
What are we going to build?
Since building our cakes application would take quite some time I’m going to simplify it a little bit:
- Instead of cakes, cocktails and vegetarian, my themes will be candy and cocktails.
- Instead of videos, I will just use a bunch of images as resources.
How does it work?
The app will be set to “candy” theme by default and will show a bunch of images that will be part of the app inside the /assets folder.
The user now can change the theme by clicking the floating button. This will download the assets from a storage repository and save them in the device. We obviously need a network connection for that, but once they have been downloaded and saved, the app will load those assets from the device so this will work offline.
I’m in, let’s do it!
Surprisingly, we can accomplish all that with just over 100 lines of code, but we will need some extra libraries to do it.
Step by step
These are the things we need to do:
- Display our local (/assets) images.
- Define our available themes and use “candy” as default.
- When changing the theme to “cocktails”, check if we have to download our assets. If so, do it and save them in the internal storage.
- Load our images, but this time use the files downloaded instead of the ones under /assets.
Help! I need some libraries!
Let’s have a look at the
pubspec.yaml file and see what we’ve got.
The first library we need is http, which is the standard used to make http requests in dart.
Now we can request our file, but we don’t want to do a request per resource. Instead, we can bundle all our resources in a zip file and use archive to extract all the resources and save them individually in the device storage.
Finally, we will use path_provider to retrieve the path for the internal storage for the application and save our data there. We will later use it again to retrieve the resources (images) we need.
Where are the assets?
We have two different sets of assets:
- for the “candy” theme, we will save the images in the
/assets/imagesfolder and later used with
- for the “cocktails” theme, we will create a zip file and upload it to Firebase storage, which conveniently provides an url to download them. After this is downloaded and the contents extracted, the images will be used with
Understood, show me the code
Let’s see the whole thing and then review every part.
State and basic UI
I created an enum
AppTheme so define the two different states: candy and cocktails. As I need to save the current theme I will use a
StatefulWidget that will be rebuilt every time the user changes the theme.
In this first section, we set the initial state to “candy” and load the images as a
List<String> to later iterate over them.
The UI is very simple, just a list of images and a floating button. Once the button is pressed we check the current theme and download assets if we need to, and then update the state with the new theme and load the list of images names. The list of images is already pre-set, but I can imagine this could also come in the zip file for every set of resources or as a JSON independently.
List.builder will iterate over the images and display them. The method
_getImage() will use the proper widget to decide how to load the image depending on the current theme.
Download and extract assets
Basically, I find out the path for the application storage directory with
getApplicationDocumentsDirectory and store the value, as I will use it later. If the assets have not already been download, I do it and extract the contents as individual files. This is a very basic example, but we could refine this by adding different folders and even deleting the zip file after the contents have already been extracted.
To download the file we just use a regular http request call and store the data in the application directory as a file.
Load the images
Loading the images is quite simple. If the assets are part of the build we can use a regular
Image.asset widget. In case they were downloaded and saved we can use
It seems like a lot has been done but basically, we are downloading a zip file, extracting the contents and saving them in the storage as regular files.
This is a very simple case where I simplified a lot the problem. We could add a more sophisticated cache system or some extra ui elements to let the user know that we are downloading new assets, but this serves to illustrate the problem and works nicely. You can check the complete example in https://github.com/davidanaya/flutter-themed-app.
Thanks for reading!