Building a Mobile App with Ionic Framework and Rails Part 2
Published: 2023-06-20 17:47:55

---
title: Building a Mobile App with Ionic Framework and Rails Part 2
published: true
date: 2023-04-25 01:07:41 UTC
tags: 
canonical_url: https://carlosmv.hashnode.dev/building-a-mobile-app-with-ionic-framework-and-rails-1
---

In this article, we will create a basic UI for a ToDo API developed with Rails in this [article](https://dev.to/carlosm27/building-a-mobile-app-with-ionic-framework-and-rails-1iob-temp-slug-7699896). There is no need to develop the server if you don't want to. You can clone it from this [repository](https://github.com/carlosm27/todo_api_rails). Or use any other API that has the following fields:

```
{
    id: int,
    task: string,
    completed: boolean,
  }

```

The UI should have the functionality to create a new task, show all the tasks, and update and delete a task.

### Ionic

Ionic is an open-source UI toolkit for building mobile apps using web technologies: HTML, CSS, and Javascript. It offers integration for React, Vue and Angular.

### Requirements

- NodeJS installed.

- React knowledge.

- Android Studio, download it [here](https://developer.android.com/studio).

- Android SDK installed (Instructions [here](https://developer.android.com/about/versions/14/setup-sdk))

- Ngrok installed

### Installation

We need to install the Ionic CLI tool. Ionic apps are created and developed primarily through the Ionic command-line utility. The Ionic CLI is the preferred method of installation, as it offers a wide range of dev tools and help options along the way.

Install the Ionic CLI with npm:

```
 npm install -g @ionic/cli

```

### Starting the app

We will use Typescript for this project and React. We run the following command:

```
ionic start todoAppIonic blank --type=react

```

Then we add Capacitor and select Android.

```
cd todoAppIonic
ionic capacitor add

```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679075165413/40e9ff5b-2213-493d-a309-296689d009a1.png)

After the CLI installed all the dependencies, we run `ionic serve` to start the app on our browser.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679075089376/d08a3fad-aaa1-4e69-a326-c1be0ebea4d4.png)

We go to `localhost:8100`, we should be able to see this page:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679075249270/8c46fb47-c051-4f5b-8178-a9b5e4b16081.png)

### Displaying the list of tasks

```
import React, { useState, useEffect } from "react";
import axios from "axios";
import {
  IonButton,
  IonCardContent,
  IonCheckbox,
  IonContent,
  IonHeader,
  IonInput,
  IonList,
  IonTitle,
  IonToolbar,
  IonLabel, 
  IonItem
} from "@ionic/react";
import { Task } from "../Task";

const API_URL = 'http://localhost:3000/todo_tasks';

const Tasks: React.FC = () => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [newTask, setNewTask] = useState<Task>({
    id: 0,
    task: "",
    completed: false,
  });

  useEffect(() => {
    fetchTasks();
  }, []);

  const fetchTasks = async () => {
    const response = await axios.get<Task[]>(`${API_URL}`);
    setTasks(response.data);
  };
  return (
    <IonContent>
        <IonHeader>
            <IonToolbar>
                <IonTitle>To-Do List</IonTitle>
            </IonToolbar>
        </IonHeader>

      <IonList>
        {tasks.map((task) => (
          <IonItem key={task.id}>
            <IonCardContent>
       {task.task}
      </IonCardContent>

          </IonItem>
        ))}
      </IonList>
    </IonContent>
  );
};

export default Tasks;

```

It imports two modules - "useState" and "useEffect" - from the React library, as well as the "axios" module for sending HTTP requests, and various Ionic components for building a mobile app UI.

The code initializes a `const` named `API_URL` with the url endpoint to fetch data from.

It declares two states (using `useState`) - `tasks` and `newTask`. `tasks` is an array of objects that represent to-do list items. `newTask` is an object with three properties - `id`, `task`, and `completed` - that represents a new to-do list item that the user will soon add to the list.

The `useEffect` hook is used to automatically fetch the current list of to-do items when the component mounts by calling the asynchronous `fetchTasks()` function. `fetchTasks()` fetches the data from the API using `axios` and sets the `tasks` state to the response data.

The code then returns the interface of the To-Do List app using Ionic components, which includes a header, title and a content area containing an Ionic list. The `tasks` state is then mapped over to dynamically render each individual to-do list item as an `IonItem` component which will display the task name in `IonCardContent` component.

Then, the `Tasks` component is exported as the default export of this file to be used further in the app.

Now, we have to go to `src/app.tsx` and add the component to the `App` function.

```
import React from 'react';
import { IonRouterOutlet, setupIonicReact } from '@ionic/react';
import { BrowserRouter, Route } from 'react-router-dom';
import Tasks from './components/Tasks';
import '@ionic/react/css/core.css';

/* Basic CSS for apps built with Ionic */
import '@ionic/react/css/normalize.css';
import '@ionic/react/css/structure.css';
import '@ionic/react/css/typography.css';

/* Optional CSS utils that can be commented out */
import '@ionic/react/css/padding.css';
import '@ionic/react/css/float-elements.css';
import '@ionic/react/css/text-alignment.css';
import '@ionic/react/css/text-transformation.css';
import '@ionic/react/css/flex-utils.css';
import '@ionic/react/css/display.css';

/* Theme variables */
import './theme/variables.css';

setupIonicReact();

const App: React.FC = () => {
  return (
    <BrowserRouter>
      <IonRouterOutlet>

        <Route path="/" component={Tasks} exact={true} />

      </IonRouterOutlet>
    </BrowserRouter>
  );
};
export default App;

```

The `<BrowserRouter>` component contains an `<IonRouterOutlet>` component from the `ionic-react` library, which provides a component for `react-router-dom`.

The `<IonRouterOutlet>` component contains a single `<Route>` component from `react-router-dom`. The `path` prop is set to `/`, which means that when a user navigates to the root URL of the app, the `Tasks` component should be rendered.

The `exact` prop of the `<Route>` component is set to `true`, which means that only exact URL matches should render the `Tasks` component.

Then, in our command line, we execute `ionic serve t`o run the app. And go to `localhost:3100` in our browser.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681764990318/f8ef1227-ee11-4e3c-8c63-459360b369c0.png)

### Create a New task

```
const handleAddTask = async () => {
    const response = await axios.post<Task>(`${API_URL}`, newTask);
    setTasks([...tasks, response.data]);
    setNewTask({ id: 0, task: "", completed: false });
  };

```

This is a function called `handleAddTask()` that is called when the user adds a new task to the list. It first sends a `POST` request to the API by calling [`axios.post`](http://axios.post)`()`. The `post()` method sends an HTTP `POST` request to the specified endpoint while the `newTask` object is passed as the request body.

The response from the API is then set as a new task in the `tasks` state object. This is done by spreading the `tasks` array and appending the newly added task obtained from [`response.data`](http://response.data).

Lastly, the `newTask` state object is resetted to its initial values so that the input form is empty again. The `id` property is set to `0`, `task` is set to an empty string and `completed` is set to `false`.

```
return (
    <IonContent>
       ...
        </IonHeader>
        <IonLabel position="floating">New Task</IonLabel>
        <IonInput
        aria-label="New Task"
        value={newTask.task}
        onIonChange={(e) =>
          setNewTask({ ...newTask, task: e.detail.value!.toString() })
        }
      />
      <IonButton type="submit" expand="block" onClick={handleAddTask}>Add task</IonButton>
      <IonList>
        ...
      </IonList>
    </IonContent>
  );
};

```

This is a part of a React functional component that returns JSX to render a view.

It includes:

- `<IonContent>` component: This is a part of the Ionic UI toolkit that creates a scrollable content container.

- `<IonHeader>` component: This is another part of the Ionic UI toolkit responsible for creating a toolbar in the view.

- `<IonLabel>` component: This creates a floated label for the input field below it. The label content is "New Task".

- `<IonInput>` component: This creates an input field that gets user input. Its `value` is set to `newTask.task` which is a property of a `newTask` object in the component's state object. When the user types into the input, the `onIonChange` prop fires an event listener which calls the `setNewTask` function with a new state object, and re-sets the `task` property to the newly-typed value.

- `<IonButton>` component: This creates a button labeled "Add task" which has an event listener `onClick` that calls the `handleAddTask` function when the button is clicked.

- `<IonList>` component: This is another component from the Ionic UI toolkit responsible for creating a list view in the component.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681765688108/6f99e03b-2aba-4ce7-8666-7bf805ca5725.png)

### Updating a task

```
const handleTaskCheckboxChange = async (taskId: number,task: string, checked: boolean) => {

    const response = await axios.put<Task>(`${API_URL}/${taskId}`, {
        task: task,
        completed: checked,
      })
    const updatedTask = tasks.map((task) =>
      task.id === taskId ? response.data : task
    );
    setTasks(updatedTask);
  };

```

This function has three parameters: `taskId`, `task`, and `checked`. The `taskId` is a number representing the id of the task item being updated, `task` is a string representing the name of the task, and `checked` is a boolean value determining whether the task is completed or not.

This function uses the Axios library to make a PUT request to update the task item in the database.

It sends an object with two fields - `task` and `completed`, which are updated with the `task` and `checked` parameter values.

After the PUT request is completed, the list of tasks (`tasks` array) is updated to reflect the new state of `completed` for the task item that was updated. The `tasks` array is updated using the `map()` function. The updatedTask constant is created by mapping over `tasks` and returning a new array with the updated task.

Finally, the `setTasks()` function is called to update the state of `tasks`, which triggers a re-render of the component with the updated task item.

```
<IonContent> 
    ...   
    <IonList>
        {tasks.map((task) => (
          <IonItem key={task.id}>
            <IonCardContent>
              {task.task}
            </IonCardContent>
              <IonCheckbox
               aria-label="task"
                checked={task.completed}
                value={task.task}
                onIonChange={(event) => handleTaskCheckboxChange(task.id, task.task, event.detail.checked)}
              />
              <IonButton color="danger" onClick={() => handleDeleteTask(task.id)}>
                Delete
              </IonButton>
          </IonItem>
        ))}
      </IonList>
</IonContent>

```

Within the `IonList`, it maps over an array of `tasks` objects and renders an `IonItem` for each of them.

For each `task`, it renders the `task.task` as content within the `IonCardContent`.

It also renders an `IonCheckbox` component for each `task`, taking three props: `checked`, `value`, and `onIonChange`. `checked` prop sets the initial value of the checkbox to the `completed` value of the `task` object. The `value` prop is set to the `task.task` and the `onIonChange` prop is set the function called `handleTaskCheckboxChange` that will be executed when the checkbox value changes. The function is passed the [`task.id`](http://task.id), `task.task`, and the current `checked` value of the checkbox.

Additionally, there is an `IonButton` component for each `task` that has a click event listener calling `handleDeleteTask` function with the task id passed as an argument that will delete a given task when clicked.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681765745902/8eb970ad-f17d-4f60-ad4e-3e1d83fb1458.png)

### Delete a task

```
const handleDeleteTask = async (id: number) => {
    await axios.delete(`${API_URL}/${id}`);
    const newTasks = tasks.filter((task) => task.id !== id);
    setTasks(newTasks);
  };

```

This function makes an HTTP DELETE request to the API with a given task ID to delete the task. Once the request is complete, the function filters out the deleted task from the `tasks` array using the `filter` method and sets the new tasks array using the `setTasks` function.

The `await` keyword is used before the Axios call to wait for the response from the API before continuing with the next line of code.

```
<IonContent>    
    <IonList>
        {tasks.map((task) => (
          <IonItem key={task.id}>
                ...
              <IonButton color="danger" onClick={() => handleDeleteTask(task.id)}>
                Delete
              </IonButton>
          </IonItem>
        ))}
      </IonList>
    </IonContent>

```

The `Delete` button is defined as an `IonButton`. When the button is clicked, it calls the `handleDeleteTask` function with the respective task ID as the parameter, which is defined elsewhere in the code.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681765793274/c6455fea-78cd-4a7b-a798-5b24fc77e002.png)

### Complete Code

```
import React, { useState, useEffect } from "react";
import axios from "axios";
import {
  IonButton,
  IonCardContent,
  IonCheckbox,
  IonContent,
  IonHeader,
  IonInput,
  IonList,
  IonTitle,
  IonToolbar,
  IonLabel, 
  IonItem
} from "@ionic/react";
import { Task } from "../Task";

const API_URL = 'http://localhost:3000/todo_tasks';

const Tasks: React.FC = () => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [newTask, setNewTask] = useState<Task>({
    id: 0,
    task: "",
    completed: false,
  });

  useEffect(() => {
    fetchTasks();
  }, []);

  const fetchTasks = async () => {
    const response = await axios.get<Task[]>(`${API_URL}`);
    setTasks(response.data);
  };

  const handleAddTask = async () => {
    const response = await axios.post<Task>(`${API_URL}`, newTask);
    setTasks([...tasks, response.data]);
    setNewTask({ id: 0, task: "", completed: false });
  };

  const handleTaskCheckboxChange = async (taskId: number,task: string, checked: boolean) => {

    const response = await axios.put<Task>(`${API_URL}/${taskId}`, {
        task: task,
        completed: checked,
      })
    const updatedTask = tasks.map((task) =>
      task.id === taskId ? response.data : task
    );
    setTasks(updatedTask);
  };

  const handleDeleteTask = async (id: number) => {
    await axios.delete(`${API_URL}/${id}`);
    const newTasks = tasks.filter((task) => task.id !== id);
    setTasks(newTasks);
  };

  return (
    <IonContent>
        <IonHeader>
            <IonToolbar>
                <IonTitle>To-Do List</IonTitle>
            </IonToolbar>
        </IonHeader>
        <IonLabel position="floating">New Task</IonLabel>
        <IonInput
        aria-label="New Task"
        value={newTask.task}
        onIonChange={(e) =>
          setNewTask({ ...newTask, task: e.detail.value!.toString() })
        }
      />
      <IonButton type="submit" expand="block" onClick={handleAddTask}>Add task</IonButton>
      <IonList>
        {tasks.map((task) => (
          <IonItem key={task.id}>
            <IonCardContent>
       {task.task}
      </IonCardContent>
              <IonCheckbox
               aria-label="task"
                checked={task.completed}
                value={task.task}
                onIonChange={(event) => handleTaskCheckboxChange(task.id, task.task, event.detail.checked)}
              />
              <IonButton color="danger" onClick={() => handleDeleteTask(task.id)}>
                Delete
              </IonButton>
          </IonItem>
        ))}
      </IonList>
    </IonContent>
  );
};

export default Tasks;

```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681764135798/7ec728de-dcbe-464c-a63d-4e13533efc9f.png)

### Running the app in the Android emulator

Android doesn't allow request from HTTP URLs, we have to use an HTTPS URL. The easiest way I found was using Ngrok. Because Ngrok generates an HTTPS URL to create a tunnel.

```
ngrok http 3000

```

The above command generates an HTTPS URL tunnel for our server. But, this is not enough, we have to allow our Rails server to accept this request. We have to go to `config/enviroments/development.rb` and add the following line inside the `Rails.application.configure` block:

```

Rails.application.configure do
  ...
  config.hosts << /[a-z0-9-.]+\.ngrok-free\.app/

  ..
  end

```

**capacitor.config.ts**

Before running the app in an Android emulator we have to add a server configuration to the `capacitor.config.ts` file.

```
import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.example.app',
  appName: 'todoAppIonic',
  webDir: 'build',
  bundledWebRuntime: false,
  server: {
    "androidScheme": "https",
    "allowNavigation": [
      "ngrok https url"
    ]
  }

};

export default config;

```

To run the app in an Android emulator, we need to have installed the Android SDK.

We run the following command in our command line:

```
ionic capacitor run android

```

Ionic will ask you the device target. This message will not appear if we don't have the Android SDK installed.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681829872438/9d1dbd9c-6243-403d-9173-844f061205db.png)

Then, an Android device will appear on your screen.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681830013077/5d15572e-8d6b-4fa7-b5bb-0922014b8bda.png)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681832704791/66ae672f-4a48-4fb8-b6f9-0e295bd8192c.png)

### Conclusion

This was the first time I build a mobile app, and it was challenging for me. It took me many weeks to build it because I don't have experience with React, so I have to learn both frameworks for this app.

What I dislike about building a mobile app, or an Android mobile app, is all the setup we have to do to build it. Installing Android Studio to download the Android SDK, and make sure we download everything. And I didn't learn how to run the app with Android Studio, which is the reason I use the command `ionic cap run android`. But, all of this is part of the process of learning something new. Probably this is nothing for a seasoned mobile developer.

About Ionic, I can say much, because this is the first framework I try to develop mobile apps, but the documentation was helpful. I wish it has more example apps or tutorials. But if you have more experience developing UI, then the learning curve will be lower. Also, I really that Ionic allows developing the UI using React, VueJs or Angular, I think that will help to ease its adoption for any frontend developer interested in mobile development.

Thank you for taking the time to read this article.

If you have any recommendations about other packages, architectures, how to improve my code, my English, or anything; please leave a comment or contact me through [**Twitter**](https://twitter.com/Carlos_marcv), or [**LinkedIn**](https://www.linkedin.com/in/carlos-marcano-a2135a134/).

The source code is [here](https://github.com/carlosm27/crudAppIonic).

### Resources

- [Ionic Documentation](https://ionicframework.com/docs).

- [Install Android Studio](https://developer.android.com/studio/install).

- [Set up the Android 14 SDK](https://developer.android.com/about/versions/14/setup-sdk).

- [React Router documentation.](https://reactrouter.com/en/main)

- [How To Make a To-do App with Ionic + React](https://medium.com/@benchedms/how-to-make-a-to-do-app-with-ionic-react-d92ece061284).

- [Creating a CRUD To-do App with Ionic 4.](https://www.freecodecamp.org/news/creating-a-crud-to-do-app-using-ionic-4/)

- [IONIC + React App - Fetch API Data with Custom Hook Example](https://www.webune.com/forums/aapyez.html).

- [How to Perform CRUD Operations using React, React Hooks, and Axios](https://www.freecodecamp.org/news/how-to-perform-crud-operations-using-react/).

- [How to Consume REST APIs in React.](https://www.freecodecamp.org/news/how-to-consume-rest-apis-in-react/)

- [Fast API + React App With HarperDB.](https://medium.com/@dennisivy/fast-api-react-crud-app-with-harperdb-5834af537c23)

- [Updating Arrays in State](https://react.dev/learn/updating-arrays-in-state#).