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:

  1. Basic knowledge of React.js
  2. Node.js installed on your system
  3. Ionic CLI installed on your system
  4. A CircleCI account
  5. 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 of Tasks
  • 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.

Create Firebase Project

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.

Select Project Features.

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.

Select Project.

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:

  1. Push our project to a remote repository (GitHub, in this case) connected to our CircleCI account
  2. Add our application as a new project on CircleCI
  3. Add our Firebase token as an environment variable to our CircleCI project
  4. Install firebase-tools locally in the project
  5. Create our pipeline configuration file
  6. 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

Initialize the project folder as a git repository (if it isn’t already) and push to your remote repository.

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.

Add Project

Click Set Up Project.

Start Building - Config sample

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.

Start Building - Add 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.

Start Building - Confirm configuration

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).

Project Settings

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.

Project Settings

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.

Build success

Click into the build to see the behind-the-scenes of the deployment as shown below.

Build Process

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!


Fikayo is a fullstack developer and author with over a decade of experience developing web and mobile solutions. He is currently the Software Lead at Tech Specialist Consulting and develops courses for Packt and Udemy. He has a strong passion for teaching and hopes to become a full-time author.