Continuous deployment for Ionic applications
Fullstack Developer and Tech Author
The Ionic framework continues to remain a top choice when it comes to mobile application development, and over the years it has grown to a full-featured application framework for developing mobile, desktop, and progressive web applications. Part of its popularity is due to it being framework-agnostic. It allows developers to use Angular, React, or Vue for development. In this tutorial, we will be demonstrating how to deploy an Ionic mobile application to the Firebase hosting platform.
Prerequisites
To follow this post, a few things are required:
- Basic knowledge of React.js
- Node.js installed on your system
- Ionic CLI installed on your system
- A CircleCI account
- A Firebase account (with a Gmail account)
Building a simple Ionic application
To begin, let’s scaffold a new Ionic application by running the following command:
ionic start ionic-cd tabs --type=react
This will immediately trigger the Ionic CLI to scaffold a new project for us using the tabs
template inside a folder named ionic-cd
.
Note: If you are prompted to Create a free Ionic account?
. Hit n
to decline. For more information on creating an Ionic account head to this page.
Once this is done, go into the root of the application (cd ionic-cd
) and run the following command to serve the application in your web browser:
ionic serve
Once this command has completes initialization, you will see an application view similar to the one below. The CLI opens a tab in your default browser automatically.
Note: I am using a mobile preview activated in Chrome’s development tools.
Next, let’s begin building the main application. Go into the src/pages
folder of your application and open the file Tab1.tsx
, this is the default home page for the newly scaffolded application. Remove everything in this file and replace it with the following code:
import React, { useState } from "react";
import {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
IonList,
IonItemSliding,
IonItem,
IonLabel,
IonItemOptions,
IonItemOption,
IonFab,
IonFabButton,
IonIcon,
IonModal,
IonButton,
IonCard,
IonCardContent,
IonInput
} from "@ionic/react";
import { add } from "ionicons/icons";
import "./Tab1.css";
interface Task {
id: number;
name: string;
}
const Tab1: React.FC = () => {
const [tasks, setTasks] = useState<Task[]>([]);
const [showModal, setShowModal] = useState(false);
const [taskName = "", setTaskName] = useState<string>();
function addNewTask() {
const new_id = tasks.length + 1;
const newTask = {
id: new_id,
name: taskName
};
tasks.push(newTask);
setTasks(tasks);
setTaskName("");
setShowModal(false);
}
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle color="primary">Task Manager</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonList>
{tasks.length > 0 ? (
tasks.map((item: Task) => {
return (
<IonItemSliding key={item.id}>
<IonItem className="todo-item">
<IonLabel>{item.name}</IonLabel>
</IonItem>
<IonItemOptions side="end">
<IonItemOption onClick={() => {}}>Done</IonItemOption>
</IonItemOptions>
</IonItemSliding>
);
})
) : (
<IonItem>
<IonLabel color="danger">
You have not yet added tasks for today
</IonLabel>
</IonItem>
)}
</IonList>
{/* Modal*/}
<IonModal isOpen={showModal}>
<IonCard>
<IonItem>
<IonLabel color="primary">Add New Task</IonLabel>
</IonItem>
<IonCardContent>
<IonItem>
<IonInput
value={taskName}
placeholder="Enter Task Name..."
onIonChange={(e) => setTaskName(e.detail.value!)}
></IonInput>
</IonItem>
<IonButton
expand="full"
color="success"
onClick={() => addNewTask()}
>
Add Task
</IonButton>
</IonCardContent>
</IonCard>
<IonButton color="success" onClick={() => setShowModal(false)}>
Close Modal
</IonButton>
</IonModal>
{/* FAB */}
<IonFab vertical="bottom" horizontal="end" slot="fixed">
<IonFabButton color="success" onClick={() => setShowModal(true)}>
<IonIcon icon={add} />
</IonFabButton>
</IonFab>
</IonContent>
</IonPage>
);
};
export default Tab1;
What we have built here is a simple task list application where we can add our tasks for the day. Let’s go through the code snippet above.
We start by importing the necessary dependencies including the CSS file for our page. We then define an interface to define our task objects.
...
interface Task {
id: number;
name: string;
}
...
Next, we create our component as a React.js function of the type React.FC
and begin the function by defining the data we want to hold in our state using hooks:
- A
tasks
array ofTask
s - A
showModal
boolean to control the opening and closing of our task creation form - A
taskName
which holds the value of a new task
...
const [tasks, setTasks] = useState<Task[]>([]);
const [showModal, setShowModal] = useState(false);
const [taskName = "", setTaskName] = useState<string>();
...
Following this is the function that we call to add a new task. This function creates a new task by setting its id
based on the length of the array and clears the form after adding the new task to our existing list of tasks.
...
function addNewTask() {
const new_id = tasks.length + 1;
const newTask = {
id: new_id,
name: taskName
};
tasks.push(newTask);
setTasks(tasks);
setTaskName("");
setShowModal(false);
}
...
Next, we render our template to display our list of tasks and a helpful message that reads You have not yet added tasks for today
when the task list is empty. Following the list is a modal component that contains our task form for adding a new task. Below the component is a floating action button that the user clicks to open the modal.
Before we preview this, open Tab1.css
located in the same folder as Tab1.tsx
and replace its contents with the following code:
.todo-item {
--min-height: 70px;
font-size: 1.2em;
}
This bumps up the height of the list items and the font size.
Now go to your browser and load the homepage of your application (Tab1).
As seen on the page, because we haven’t added any tasks yet, we have the message You have not yet added tasks for today displayed. We also have our Add Task button in the bottom right corner with the plus
symbol.
To add a new task, click the bottom-right, green button (with the plus symbol) and type in a task.
Click ADD TASK, then add two or three more. Now we have enough tasks on the page for our application to look functional.
Setting up for deployment to Firebase
Now that we have a working Ionic application, let’s prepare it for deployment to Firebase.
To begin, you need to have the Firebase tools installed. To check whether you have it installed, run the following command:
firebase
This will output a list of Firebase CLI commands/options in your terminal. If it doesn’t, you need to run the following command to install the CLI:
npm install -g firebase-tools
You also need to run this command if the version of your Firebase CLI is less than 8
. To check your version, run the following command:
firebase --version
To set up Firebase hosting for our project, we need to create a Firebase project. Head over to your Firebase console and create a new project.
Click Add Project and enter the name of your project on the first page that pops up.
Click Continue. On the next page (about adding Google Analytics), turn off the Enable Google Analytics for this project
toggle button. This is a demo project and we won’t need analytics.
Now click Create Project. Wait for Firebase to complete the setup for your project, then click Continue to navigate to your project dashboard.
The next step is to set up our Ionic application to be hosted on Firebase using the project we just created. Stay logged in to Firebase on your default browser. Then go to your CLI to run the following command:
firebase login:ci
This command will log you into Firebase by redirecting to your browser where you’re currently logged in. Once the authentication process is complete, you will be logged in to Firebase on your CLI and your Firebase token will be printed on the screen just below the line that reads ✔ Success! Use this token to login on a CI server
. Please keep this token secure as you will be needing it later on in this tutorial.
Next, run the following command at the root of your project to initialize the Firebase set up:
firebase init
The first prompt you will get from this command is ? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices.
. Select Hosting
and hit Enter
to go to the next prompt.
.
The next prompt is about associating your local project with a Firebase project on your Firebase account. From here, you can choose to use an existing project or create a new one. Select Use an existing project
and hit Enter
to move to the next prompt.
This selection will result in the CLI loading your Firebase projects for you to select from in the next prompt. Select the project we just created on our Firebase console, and hit Enter
to confirm your selection.
.
Note: If your project is not visible in the list, stop this process (Ctrl + C
), then rerun the command with the --project
option: firebase init --project <projectId>
replacing projectId
with your newly created Firebase project ID. Select Hosting just as before.
The next prompt asks for the project folder and suggests the public
folder. Ionic keeps its production build in a folder named build
, so enter build
for this prompt and hit Enter
.
The following prompt reads ? Configure as a single-page app (rewrite all urls to /index.html)?
. Type y
, and hit Enter
.
This will complete the setup and you will have two new files: .firebaserc
, which sets the project id for this application and firebase.json
, which contains details about the options we selected during the setup process and some other default settings. We can now proceed to building our deployment pipeline.
Building the CD pipeline
To set up our continuous deployment pipeline, we need to take the following steps:
- Push our project to a remote repository (GitHub, in this case) connected to our CircleCI account
- Add our application as a new project on CircleCI
- Add our Firebase token as an environment variable to our CircleCI project
- Install
firebase-tools
locally in the project - Create our pipeline configuration file
- Push project changes to our repository to initiate deployment
Let’s begin.
First, scaffold a quick package.json
file by running the following command:
npm init -y
Then, push your project to GitHub.
The next step is to set up the repository for our project as a CircleCI project.
On the CircleCI console, select the account to view your Projects
page. If the GitHub repo is not visible here then click Add Projects on the side-menu.
Click Set Up Project.
On the setup page, click Start Building. Before the build starts, you get a prompt to either download and use the provided CircleCI configuration file and have it on a separate branch or set up one manually.
Select Add Manually to proceed. This will prompt another dialog that checks to confirm that you have a configuration file set up to begin building.
Click Start Building to complete the setup. This will immediately trigger the pipeline. The build will fail because we haven’t added our pipeline configuration file.
Our next step is to add our Firebase token as an environment variable in the CircleCI project we just created. On the Pipelines page, with our project selected, click Project Settings (at the top right corner of the webpage).
On the settings page side menu, click Environment Variables
. On the variables setup page, click Add Environment Variable. A dialog box will appear. In the Name
field, enter FIREBASE_TOKEN
and in the Value
field, paste in the Firebase token you got from your CLI earlier. Click Submit
to complete the process. You now have the token variable registered.
Return to the project on your system. Run the following command to install firebase-tools
at the root of the application so you can have it registered in package.json
as a development dependency:
npm install -D firebase-tools
Once that process is complete, it is time to create our deployment configuration file. At the root of your project, create a folder named .circleci
and a file within it named config.yml
. Inside the config.yml
file, enter the following code:
version: 2
jobs:
build:
docker:
- image: cimg/node:12.16
working_directory: ~/repo
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
- v1-dependencies-
- run:
name: Install Dependencies
command: npm install
- run:
name: Build Application
command: npm run build
- save_cache:
key: v1-npm-deps-{{ checksum "package-lock.json" }}
paths:
- ./node_modules
- run:
name: Deploy to Firebase
command: ./node_modules/.bin/firebase deploy --token "$FIREBASE_TOKEN" --only hosting
In the deployment file above, we start by checking out the project from our remote repository. We then install our dependencies. Next, we run the build
script in package.json
to create a production version of our Ionic app in the build
folder, and then cache our dependencies. Finally, we run our firebase-tools
from the local installation to use our Firebase token to deploy our application.
Time to put our money where our mouth is. Let’s commit our changes and push them to our repository to cause our deployment script to be triggered and our application deployed to Firebase hosting.
Click into the build to see the behind-the-scenes of the deployment as shown below.
From the Deploy to Firebase
section. You can see the URL of the deployed application. For this tutorial it is https://my-ionic-app-d2a1d.web.app/
. Load yours in your browser to test your application.
![Live Application]
Conclusion
If you’re still manually copying files over to your server each time your application is updated, then you need to save yourself all that stress by setting up an automated deployment pipeline so your deployments are done anytime you push updates. With this, you only need to worry about building your application, CircleCI takes care of the deployments for you.
Happy Coding!