A common requirement for mobile apps is offline capability. If you expect your users to use the app in an area with limited connectivity then having offline capabilities is a must.
For example, the startup I work for built an app for collecting crop data from the fields. Typically the fields are in a pretty area and a good network connection is not always guaranteed. Therefore we need the ability for users to do their work out in the field and then sync their data with the server once they have returned to an area with better network connectivity.
In this article, I’m going to demonstrate how to build a basic image upload app with offline capabilities. We will be using React Native and Redux offline in our demonstration app. We will also be building a NodeJS API for accepting the data from our app and storing it in a database. If you need an introduction to building React Native apps be sure to check out my article below.
What are we building?
In this article we will be building the following:
- A local instance of PostgreSQL for storing data
- NodeJS API with Sequelize for communicating with the database
- A React Native app for capturing and uploading images with offline capabilities
Here is a preview of the end result:
The easiest path for building an app with offline capabilities is to build the app from the beginning with offline in mind. It’s possible to add to an existing app, but it is much easier to include from the start if it’s something you know will be needed.
You can definitely build your own logic and services for handling everything around offline mode, but this can be quite complex to implement. In your app, you would need to keep track of the connection status of the user’s device and handle it accordingly. When the user does something that would cause an update would have to check that status and either send it to the server or store locally until it can be synced at a later time.
In this article, we will be using a library called Redux Offline. This will look very familiar if you’ve ever built an app with Redux before as it works the same. We provide Redux Offline with the information for syncing our data with the server and it handles the syncing based on the current connection status.
Redux Offline makes it simple for developers to handle offline capability. Check it out on Github.
PostgreSQL and Docker
To store the data from our app we will be using a PostgreSQL database. You can have your instance running anywhere but for this example, I will be creating a local instance using Docker.
With Docker installed, run the following command in a terminal to pull the PostgreSQL image from Docker.
docker pull postgres
That will take a minute or two to download the image, but once that is complete run the following command to start a local instance.
docker run --name local-postgres -e POSTGRES_PASSWORD=password -d -p 5432:5432 postgres
This should start a local instance immediately. To confirm you can run the following command to check the current Docker processes.
Next, we will create a new database from within the new container we just started. Run the following commands.
docker exec -it 16856b6f5f bash
root@16856b6f5f:/# psql -U postgres
postgres=# CREATE DATABASE imageuploads;
Now we have our local instance ready and an empty database created within that instance. In the next section, we will use Sequelize to create migrations for setting up our database tables.
We will be creating a simple NodeJS API with Express and Sequelize. Express is a framework used to create REST APIs and Sequelize is an ORM that we will use to communicate with our PostgreSQL database. In the API we will build endpoints for creating new entries for image uploads and also for fetching a list of all of the image uploads currently in the database.
To start we’ll create a new empty NodeJS project. Run the following commands in a terminal to set up an empty package.json and then add in our dependencies
npm init -y
npm install --save express sequelize pg pg-hstore cors body-parser
npm install --save-dev sequelize-cli
Next, we will initialize the Sequelize configuration by running the following command.
npx sequelize-cli init
This will create a few new folders in our project:
We will be creating new models and migrations here shortly, but first, we need to add our local PostgreSQL instance to the development configuration in
Now Sequelize knows where to find our database so we can create a table where we will be storing all of our image uploads. We will use the following command to create a model and migration for the
npx sequelize-cli model:generate --name uploads --attributes id:bigint,file_name:string,image:blob
This will generate a model that we will make a couple of updates to. Update the contents of models/uploads.js to the following code.
For our example, we will actually be storing the images as base64 strings in the database. This is a valid approach to storing images, there is also the option of using something like S3 to handle the file storage. If you’re interested in how to do that be sure to check out my article below.
Build an Image Upload Application With React, NodeJS, PostgreSQL, and S3
Also, be sure to update the migration for adding the
uploads table to the database to the following code.
To execute the migration and add the table to the local database instance run the following command which will run everything in the migrations folder that hasn’t yet been applied to the database.
npx sequelize-cli db:migrate
After this executes successfully we should be able to query our database and see that we have a single table now name uploads with five columns and currently has no rows of data.
Now that we have the database ready to go we are ready to add the API endpoints for creating and fetching uploads. We will create the following endpoints:
Let’s create those now. We will create a new file at routes/uploads.js and add the following code.
Very simple as you can see. We create two endpoints and either write or fetch records in the database. Just one more file to finish off our API. We create a new file called index.js.
And that’s all the code we need for API. We will be calling the API in the next section from our mobile app. You can run the API by running the following command.
React Native and Redux Offline
With everything else in place now we can move on to building our user interface. The first step is to generate a new React Native application using the React Native CLI. Run the following command in a terminal to kick this off.
npx react-native init OfflineImageUpload
This will take a few minutes to create the new app, but once that is complete we can move into the project directory and install the dependencies we’ll need for implementing Redux Offline.
npm install --save redux react-redux @redux-offline/redux-offline@native @react-native-community/async-storage @react-native-community/netinfo react-native-image-pickernpx pod-install
That last command will link everything for the native apps. You can run the app in the iOS simulator with the following command.
npx react-native run-ios
The directory structure we are using for the app will likely look familiar if you’ve ever worked on a Redux application before. We create the following directories in the root of the project:
We will start by creating the Redux actions for our app. So the actions are the functions we use to update the current state of the app’s data store. Create a file in actions/index.js and add the following code.
This is the spot where we set up Redux Offline. We decorate the actions with offline decorators to specify the endpoints to call for the actions and what data to pass. Different actions can trigger based on if calling the API calls or fails. If the network connection is determined to be offline it will wait for the connection to be restored before it sends all the uncommitted actions to the API.
Next, we will need to create a reducer that holds the list of uploads and consumes the results of the actions we created above. Create a new file at reducers/uploads.js and add the following contents.
Typically we’d create a root reducer to combine all of our reducers. We only have a single one, but we’ll still follow the convention as it leaves it open that we can easily add more later. Create a file at reducers/index.js and add this code.
Next, we need a component where we can display the list of images along with a way to upload additional images. Create a file at components/UploadList.js and fill it with these contents.
You can see in this component we are expecting
addUploads to be passed in as props. We will do that in the next step when we create a container to map our actions and reducer to the props of this component.
We are now ready to create a container that will tie our actions and reducer together. Create a file named containers/UploadListContainer.js and add the following code.
Here we have mapped both state (reducers) and dispatch (actions) to props for the
UploadList component we just created.
That’s it for the components we have to create for the app. Now we just tie the rest of it together in our main App.js file.
Here we are simply pulling in the container we just created and displaying it in the UI. We also configure our Redux store with the offline middleware provided by Redux Offline.
That takes care of the code to make our app work. Check out our handiwork below.
Check out the example code on Github:
That wraps things up for this example. If you are interested in building offline-capable mobile apps I hope this has given you some insight into a simple path to do just that. Thanks for reading!