Quite a number of us like to order food. How about we track the delivery of meals right from placing our order to its delivery. In this article, we would be building a food delivery tracking progressive web app (PWA) using Pusher and Vue.js.
Here is what the final demo would look like:
A progressive web app is simply an app that employs various modern technologies to achieve a unique goal. In this article, we will be building a PWA that can engage the user even when they lose connectivity.
To achieve this realtime feature, we will be using Pusher. Pusher is a leader in realtime technologies, and allows us to perform various operations on the web in realtime. Our offline starter project will be an enhanced Vue PWA webpack template. Also, we will be making use of Progressbar.js to implement a smart progress bar functionality on our app and Bulma classes to style our app.
We will be working with vue-cli, which you can install with:
https://gist.github.com/66980a56f136b9be66a5a909a8d4f5a9
This installs the latest version of Vue CLI globally on your machine. Webpack has a PWA template and we will work with this in building our app. Create a Vue project with the pwa template. This is the syntax for creating a project with a template in Vue:
https://gist.github.com/88878279f9703a909589941a879a9b07
For our app, in the command prompt we run:
https://gist.github.com/213c30a4bd173fc88179b79f7e16e61e
Several popup commands requesting some project details will be displayed on our console. For the purpose of this project, skip all of them and use all default values provided. You can customize these if you choose though.
Now let’s test our app, run:
https://gist.github.com/920cb843fba7ef8a592f29e1b81b4c4f
You should get a welcome screen:
Next, let’s install our dependencies with:
https://gist.github.com/be875d378dfc8a4e9aabf7d742356cce
- Bulma: CSS framework based on flexbox
- Progressbar.js: Used to animate SVG paths
- Pusher: Provides realtime service (server-side)
- Pusher-js: Provides realtime service (client-side)
- Dotenv: Used to load environment variables
dotenv
is used to load environmental variables from a .env
file in our root folder. Our pusher keys will be saved in this file. No pusher keys yet? You’ll get them next.
Go on to Pusher and create an account. On the left nav bar, create a new app with any name of your choice. You will be issued an app-id, a pusher key, and a pusher-secret. Select a cluster near you and keep these credentials safe; we will require them later in our app.
For our delivery app, we will configure our pusher server with the pusher credentials obtained on Pusher account creation. In our root folder, we create a file .env
and pass in our credentials:
https://gist.github.com/d6c31d985fca9d47e0d4228bf36b8b49
This file will be utilized by dotenv
, which we installed earlier.
Create a file in the root directory named server.js
, here we shall configure our server. First, we require dotenv
and apply the .config()
method to it. This allows us to use the process.env
object.
https://gist.github.com/143327ad20e99a4dd2ff26252eef0866
As seen above, the ES6 destructuring technique is used to assign the data in our .env
object to the listed constants. Next, we create a new pusher instance and assign our pusher credentials to their respective object properties as seen above.
For our demo we shall use a setInterval()
function to simulate the timing of the pizza delivery process. Let’s configure this timing function:
https://gist.github.com/1b8e89cf1b174568bac41cfa051b7948
We created a stage variable to keep track of the iteration. In our setInterval()
function, we pass it the usual callback using the ES6 arrow function. In this function, we create an object containing the individual processes as values with their properties being numbered keys. The stage value is incremented by one for every iteration. This ensures that at the last stage, the interval is cleared out and exited.
We created a Message ID to refer to the message used from the array, and then converted that to a fraction to serve as the progress bar as well. This serves as our progress. The if block creates a termination case for our function based on the value of messageId
. As long as the function is not terminated, we keep calling the trigger
method on the pusher
instance, passing it arguments of channel, event, and a payload. This payload is an object containing data we would like to send over our pusher channel.
Vue is used to create the client interface as stated earlier. In our src
folder, there is an already configured Vue component — App.vue
. Delete all the content in it and let’s get to creating our own content. Add the following template to represent the UI:
https://gist.github.com/c279be40829032faa074b1f428bbd4dd
Notice the <status>
component and the :progress
binding? Their values are received from the component’s object as shown below:
https://gist.github.com/a5d95244072481e77d1fa06bfb589e3d
First, we require bulma, this provides all the bulma classes used to style our app in the template.
Next, we import pusher-js
which we installed at the beginning. A Status
custom component is also imported which we will create soon. In our Vue data method, we create and return an object whose properties values will be used in the template. The statusText
property displays the status of our order.
The value of connectivityText
is displayed whenever our device is offline and this depends on the value of the connectivityStatus
property to be visible or not. We will set these values soon.
In the created()
lifecycle method, we create a new pusher
instance on the client side. This instance receives the payload from the server once we subscribe to our channel created on the server.
Note: Multiple events can be created per channel.
After a new pusher instance is created with our pusher-key (obtained on registration) as seen above. With this instance, we can subscribe to the channel we used on the server. Next, bind the status
event to channel
, passing it a callback which fetches the payload from the channel. The data fetched is passed as values to the properties we created in our data()
method.
In the components
folder, delete the Hello.vue
component and create a Status.vue
file. You also need to replace Hello.vue
with Status.vue
in the routes list if you opted for routing when creating with Vue CLI.
Add a div with a status
class to the template as shown below:
https://gist.github.com/7e4d4ccc2d7f521df9f5e80b830cf2db
The div tag serves as a mount point for the progress status widget. Next, create the component’s logic that imports the progressbar
plugin:
https://gist.github.com/92e0cffb7b28c7b2aec30238824d37a2
The component has a bar
property which is used to keep track of the widget configuration instance. It is used to configure the awesome circular status bar.
We configure the Progress Bar in our mounted
lifecycle method to ensure that the DOM is ready before manipulating it. This is done by creating an instance of the Circle
constructor on the ProgressBar
object. After configuring our circular bar, the .animate()
object is called on bar
and passed a parameter which is the value of progress
property. The property is received from the parent component via props
. This triggers an animation in the status bar but with a zero initial value so nothing is obvious.
Remember the :progress
property we bound to the value of progress
in App.vue
? It would only be accessible to the Status child component if we specify it in the props
array.
Now to a fun part; in the watch
object, we state a progress function which listens for value changes on the progress
property. Whenever the value changes, the new value is passed as a parameter to the animate
method on this.bar
. This means that whenever a new progress value is received from the server, it propagates through the App parent component, down to the child’s watch
object and updates the status of our order. This update is observed from the progress circular bar.
If you don’t have the app running, run:
https://gist.github.com/1a103eb48c1d69847a6464b14e91c8e5
Then start the server:
https://gist.github.com/a71360ce19b055121b4d27ee02d5e351
Once the server starts running and is emitting events, you should see the client update as shown in the video below:
How do we handle notifications when offline?
We listen to the windows object for an offline
event or an online
event, in which case this.conectivityStatus
is false
or true
respectively. When connectivityStatus
is false
, connectivityText
resolves to:
https://gist.github.com/e9357a463c7b0f2dbe08532984a4d347
In the created()
method of App.vue
, let’s create an EventListener:
https://gist.github.com/35c362fa93ca73af02077410a4475fe4
While the app is running, turn off your network connectivity and you should see a warning message pop-up on the screen as shown in the image below:
In this article, we built a realtime delivery tracking PWA using Pusher and Vue while implementing status bar features with Progressbar.js and styling with Bulma. This app has minimal styling, feel free to add more styles and possibly more cool functionalities.