I. Introduction
II. Getting Started with Ionic Framework 6
III. Building a movie app
IV. Deploying the application
V. Testing and simulating on devices
VI. Conclusion
VII. Additional Resources and Recommendations for Further Learning
Introduction
Why do developers prefer cross-platform frameworks? So they don't have to pull their hair out building and maintaining separate codebases for web and mobile apps. It's hard enough to build one app, let alone two!
Thankfully, the Ionic framework enables cross-platform development using React, Angular, and Vue from a single codebase. Pretty cool, right? This provides a seamless experience for both the developer and the user, as the user doesn't have to learn how to interact with a whole new interface, and the developer only has to maintain one codebase instead of two.
In this article, we will go over how to get started with Ionic, create a simple movie app, deploy and test it. So, if you are a front-end developer looking to expand your skills and build cross-platform apps, read on!
Getting Started with Ionic Framework 6
Before you get started with working with Ionic, make sure you have Node JS
and npm
installed on your computer. If you are using a windows device, install Android studio; if you are using a Macbook, install Xcode.
Install the Ionic CLI globally on your machine by running the following command in your terminal:
npm install -g @ionic/cli
To confirm that the installation was successful, check the Ionic version on your machine with the command below:
ionic --version
Once you have installed Ionic, you are ready to build your first application. In the next section, we will create a starter template for the Ionic application with React.
Building Web Apps with Ionic Framework 6 and React.js
Follow the steps below to get started:
- Create a folder for your application and navigate to the directory using the code below:
mkdir movie
cd movie
- Run the following command to create a starter template for your ionic app:
ionic start myapp
When prompted to select a framework for your template, choose React. Then, in the following prompt, select the option for a blank template to create a new template.
Your folder structure should look like the image below.
- Navigate to the myapp directory with the command below:
cd myapp
- Run the command below to spin up your application:
ionic serve
This command starts a local development server and opens the app in your default browser at http://localhost:8100
.
Your browser page should look like this
The Ionic framework provides a wide range of UI components that you can use to create a high-quality user interface for your app. These components are designed to look and feel like native components on iOS and Android, providing a seamless user experience. In the next section, we will explore the starter template and the UI components.
Exploring the starter template
The starter template's entry point is main.tsx
. This file renders App.tsx
, just like in a basic React JS application.
In the App.tsx
file, a bunch of pre-installed packages are being imported to handle the routing and styling of the application. The application uses both react-router-dom
and @ionic/react-router
for routing.
Components
IonApp:
IonApp
is a component that encapsulates the entire application by providing a foundation for rendering other components.IonRouterOutlet: To define routes, developers can utilize the
<IonRouterOutlet>
component, where each<Route>
component represents a unique route that the application can handle.IonPage: The
Home.tsx
file located in the pages folder is enclosed within theIonPage
component.IonPage
is a framework-provided component used to establish the structure of a page and contain all its constituent components. The starter template above wraps the Home component with theIonPage
component, signifying that the Home component represents the entire page.IonHeader: This component is used to define the header section of a page. It usually contains a title and other navigation elements.
IonToolbar: The toolbar component is used to create the navigation elements on the page. The component can be customized with various properties such as color, mode, and slot.
Read more on Ionic framework components.
Now that you have gone through the template and have a basic understanding of how Ionic applications are structured, let's start building the movie application in the next section.
Building a Movie App
In this section, you will learn how to build a simple movie app. The app will allow users to browse the latest movie releases and save desired movies to a bookmark list. By the end of this section, you will have a strong understanding of Ionic application development and be on your way to building more complex applications.
Setting Up the App Layout
To get started;
- Open the
App.tsx
file and replace the default code with the following:
import { Redirect, Route } from "react-router-dom";
import { IonApp, IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs, setupIonicReact } from "@ionic/react";
import { list, person, videocam } from "ionicons/icons";
import { IonReactRouter } from "@ionic/react-router";
import MovieList from "./pages/MovieList";
import MovieDetails from "./pages/MovieDetails";
import WatchList from "./pages/WatchList";
/* Core CSS required for Ionic components to work properly */
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";
/* Theme variables */
import "./theme/variables.css";
setupIonicReact();
const App: React.FC = () => (
<IonApp>
<IonReactRouter>
<IonTabs>
<IonRouterOutlet>
<Route path="/movies" component={MovieList} exact={true} />
<Redirect exact from="/" to="/movies" />
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="movies" href="/movies">
<IonIcon icon={videocam} />
<IonLabel>Movies</IonLabel>
</IonTabButton>
<IonTabButton tab="watchlist" href="/watchlist">
<IonIcon icon={list} />
<IonLabel>Watch List</IonLabel>
</IonTabButton>
<IonTabButton tab="account" href="/account">
<IonIcon icon={person} />
<IonLabel>Account</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
</IonReactRouter>
</IonApp>
);
export default App;
In an Ionic application, the hierarchy of the JSX
components is critical. The root of the application is wrapped in the <IonApp>
component, which is imported from the ionic/react
package. This component indicates that the project is an Ionic application. The <IonReactRouter>
and <IonReactOutlet>
components are also fundamental components of the Ionic routing. On the other hand, the <IonTabs>
component is not a fundamental part of the hierarchy, but it allows the application to have a bottom tab bar for navigation.
- In the
src
folder, create a pages folder and add aMovieList.jsx
file in the folder. Add the code below to the file:
import React, { useState, useEffect } from "react";
import {
IonApp,
IonContent,
IonHeader,
IonToolbar,
IonTitle,
IonSearchbar,
SearchbarChangeEventDetail,
IonPage,
IonLoading,
} from "@ionic/react";
import axios from "axios";
import { MovieProps } from "../type";
import MovieCard from "../components/MovieCard";
const App = () => {
const [movies, setMovies] = useState<MovieProps[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
`https://api.themoviedb.org/3/movie/popular?api_key=${process.env.REACT_APP_TMDB_KEY}&language=en-US&page=1`
);
setMovies(response.data.results);
};
fetchData();
}, []);
const searchMovies = async (searchText: string) => {
setLoading(true);
const response = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key=${process.env.REACT_APP_TMDB_KEY}&query=${searchText}`
);
const data = await response.json();
setLoading(false);
return data.results;
};
const handleSearch = async (
event: CustomEvent<SearchbarChangeEventDetail>
) => {
const text = event.detail.value || "";
if (text.length === 0) {
return;
}
try {
const results = await searchMovies(text);
setMovies(results);
} catch (error) {
console.log(error);
}
};
return (
<IonPage>
<IonLoading isOpen={loading} message={"Searching..."} duration={2000} />
<IonApp>
<IonHeader>
<IonToolbar>
<div className="toolbar-container">
<IonTitle>Latest Movies</IonTitle>
<IonSearchbar
placeholder="Search movies..."
onIonChange={handleSearch}
className="search-input"
></IonSearchbar>
</div>
</IonToolbar>
</IonHeader>
<IonContent>
<div className="movie-container">
{movies.map((movie) => (
<MovieCard key={movie.id} movie={movie} />
))}
</div>
</IonContent>
</IonApp>
</IonPage>
);
};
export default App;
In the code snippet above, the latest movie list is being fetched from the TMBD website using their API key. To obtain the key, you need to create an account on the TMBD site and request for an API key. Once you have the key, create an .env
file in your src
folder and add your API key to it, as shown in the code below. Replace "YOUR_API_KEY" with the API key gotten from the TMDB website:
REACT_APP_TMBD_KEY=YOUR_API_KEY
In the useEffect
hook in the MovieList
page, the API result is fetched using axios
and saved in the movies
state. The searchMovies
function handles searching the TMDB database for movies using the text input obtained from the IonSearchBar
component, and the handleSearch
function triggers the search when the submit button is clicked.
The IonLoading
component is used to display a loading state while searching for movies. The result gotten after the API is then mapped to the MoviesCard
component, which will be created later in the article.
- Create a
type.ts
file in thesrc
folder and add the snippet below:
export type MovieProps = {
title: string;
poster: string;
plot: string;
id: string;
poster_path:string
release_date:string
backdrop_path:string
};
Create a components folder in the
src
folder and add aMoviesCard.tsx
file.Add the following code to the
MoviesCard
component:
import { IonCard, IonIcon, IonImg, IonLabel } from "@ionic/react";
import { MovieProps } from "../type";
import { trashBin } from "ionicons/icons";
import { useState } from "react";
type props = {
movie: MovieProps;
watch?: boolean;
handleDelete?: (
event: {
preventDefault: () => void;
stopPropagation: () => void;
},
movie: MovieProps
) => void;
};
function MovieCard({ movie, watch, handleDelete }: props) {
const [showSuccessToast, setshowSuccessToast] = useState<boolean>(false);
return (
<IonCard routerLink={`/movies/${movie.id}`}>
<div className="movie-item">
{watch && handleDelete && (
<IonIcon
icon={trashBin}
slot="start"
onClick={(e) => handleDelete(e, movie)}
className="trash"
/>
)}
<IonImg
src= {`https://image.tmdb.org/t/p/w500/${movie.backdrop_path}`}
alt={movie.title}
/>
<IonLabel>
<h2 className="movie-title">{movie.title}</h2>
</IonLabel>
</div>
</IonCard>
);
}
export default MovieCard;
This component is a simple card displaying an image and the movie title. When a user clicks on any part of the card, they will be redirected to the MovieDetails
page. Since the Card
component will also be utilized by the WatchList
page, the trash bin is being conditionally rendered.
Before proceeding to create the next page in the application, consider adding a styles
folder to the application to provide a different look and feel to the MovieList
page.
- In the
src
folder, create aglobal.css
file and add the following code to the file:
/* global styles */
ion-content {
--background: #141414;
--color: #fff;
}
ion-card {
z-index: 1 !important;
}
/* home page styles */
.home-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
.home-container h1 {
font-size: 1.5rem;
margin-top: 20px;
margin-bottom: 30px;
}
/* movie list styles */
.movie-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-gap: 20px;
}
.movie-item {
position: relative;
}
.movie-item ion-img {
width: 100%;
height: 300px;
object-fit: cover;
border-radius: 10px;
}
.movie-item .movie-title,
.bookmark,
.trash {
position: absolute;
background-color: rgba(0, 0, 0, 0.7);
color: #fff;
padding: 10px;
text-align: start;
font-weight: bold;
font-size: 1rem;
}
.movie-item .movie-title {
bottom: 0;
left: 0;
right: 0;
}
.movie-details .bookmark,
.movie-item .trash {
top: 0;
right: 0;
font-size: 1.2rem;
}
/* movie details styles */
.movie-details {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}
.movie-details .text-wrapper {
margin: 0 auto;
}
.title {
margin-left: 20px;
color: #838181;
}
ion-button {
--background: #5c5b5b;
--color: white;
margin-left: 10px;
}
.movie-details h2 {
font-size: 1.5rem;
margin-top: 20px;
margin-bottom: 10px;
}
.movie-details p {
font-size: 1rem;
line-height: 1.5;
margin-bottom: 20px;
max-width: 40ch;
}
.img-wrapper {
position: relative;
}
.movie-details img {
border-radius: 20px;
max-height: 600px;
max-width: 400px;
height: 100%;
width: 100%;
object-fit: cover;
}
.movie-details .rating {
display: flex;
align-items: center;
font-size: 1.2rem;
margin-bottom: 20px;
gap: 5px;
place-items: center;
}
.movie-details .release-date {
color: #5c5b5b;
}
.movie-details .rating ion-icon {
color: #fbc02d;
margin-right: 10px;
}
/* search bar styles */
.search-container {
padding: 20px;
}
.toolbar-container {
display: flex;
justify-content: start;
padding: 10px 40px 10px 0px;
}
.search-input {
width: 400px;
}
ion-toolbar {
display: flex !important;
}
/* error message styles */
.error-message {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.error-message h1 {
font-size: 2rem;
margin-right: 20px;
}
/* loading indicator styles */
.loading-indicator {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.loading-indicator ion-spinner {
--color: #fff;
}
- Import the global styles to your App.tsx file like so:
/* Global styles */
import "./global.css";
The movie list should be displayed in a grid layout like the image below.
- Add a
MovieDetails.tsx
file in your pages folder and add the code below:
import React, { useEffect, useState } from "react";
import { RouteComponentProps, useHistory, useParams } from "react-router-dom";
import {
IonContent,
IonHeader,
IonPage,
IonTitle,
IonToolbar,
IonImg,
IonList,
IonItem,
IonLabel,
IonText,
IonBadge,
IonButton,
IonIcon,
IonLoading,
IonToast,
} from "@ionic/react";
import {
arrowBack,
bookmarkOutline,
star,
starHalf,
starOutline,
} from "ionicons/icons";
import axios from "axios";
import { MovieProps } from "../type";
interface MovieDetails {
id: number;
title: string;
poster_path: string;
backdrop_path: string;
release_date: string;
overview: string;
vote_average: number;
vote_count: number;
genres: { id: number; name: string }[];
}
const MovieDetails: React.FC = () => {
let watchlist: MovieProps[] = localStorage.getItem("watchlist")
? JSON.parse(localStorage.getItem("watchlist")!)
: [];
const { id } = useParams<{ id: string }>();
const [movieDetails, setMovieDetails] = useState<MovieDetails | null>(null);
const [showSuccessToast, setshowSuccessToast] = useState<boolean>(false);
const [message, setMessage] = useState({ present: false, message: "" });
const history = useHistory();
useEffect(() => {
const fetchMovieDetails = async () => {
try {
const response = await axios.get(
`https://api.themoviedb.org/3/movie/${id}?api_key=${process.env.REACT_APP_TMDB_KEY}`
);
setMovieDetails(response.data);
} catch (error) {
console.log(error);
}
};
fetchMovieDetails();
}, [id]);
const handleSaveToWatchList = () => {
const movieId = movieDetails?.id;
const isMovieInWatchlist = watchlist.find(
(movie: any) => movie.id === movieId
);
setshowSuccessToast(true);
if (isMovieInWatchlist) {
setMessage({
message: "This movie has already been bookmarked",
present: true,
});
}
if (!isMovieInWatchlist) {
const newList = [...watchlist, movieDetails];
localStorage.setItem("watchlist", JSON.stringify(newList));
setMessage({
message: "Movie has been successfully bookmarked",
present: false,
});
}
};
if (!movieDetails) {
return (
<IonLoading
isOpen={!movieDetails}
message={"Please wait..."}
duration={3000}
/>
);
}
let rating = movieDetails.vote_average / 2;
const starRating = [];
for (let i = 0; i < 5; i++) {
if (rating >= 1) {
starRating.push(<IonIcon key={i} icon={star} />);
rating--;
} else if (rating >= 0.5) {
starRating.push(<IonIcon key={i} icon={starHalf} />);
rating -= 0.5;
} else {
starRating.push(<IonIcon key={i} icon={starOutline} />);
}
}
return (
<IonPage>
<IonToast
isOpen={showSuccessToast}
message={message.message}
onDidDismiss={() => setshowSuccessToast(false)}
duration={5000}
position="top"
animated={true}
color={message.present ? "medium" : "success"}
></IonToast>
<IonHeader>
<IonToolbar>
<IonButton onClick={() => history.goBack()} slot="start">
<IonIcon icon={arrowBack} />
</IonButton>
<h1 className="title">{movieDetails.title}</h1>
</IonToolbar>
</IonHeader>
<IonContent>
{movieDetails && (
<div className="movie-details">
<div className="img-wrapper">
{" "}
<IonIcon
icon={bookmarkOutline}
slot="start"
color={showSuccessToast ? "success" : "medium"}
onClick={handleSaveToWatchList}
className="bookmark"
/>
<img
src={`https://image.tmdb.org/t/p/w500/${movieDetails.poster_path}`}
alt={`${movieDetails.title} poster`}
/>
</div>
<div className="text-wrapper">
<h2>{movieDetails.title}</h2>
<p className="release-date">
Release date: {movieDetails.release_date}
</p>
<p className="overview">{movieDetails.overview}</p>
<div className="rating">{starRating}</div>
</div>
</div>
)}
</IonContent>
</IonPage>
);
};
export default MovieDetails;
On the MovieDetails
page, the id
obtained from useParams
is used to fetch movie details and stored in the movieDetails
state. Clicking on the bookmark icon calls the handleSaveToWatchList
function. This function checks whether the movie has already been saved in the watch list stored in local storage, and adds the new movie to the list if it hasn't been saved yet. If the operation is successful, the IonToast
component is triggered by setting showSuccessToast
to true.
- Add a
WatchList
page in your pages folder and add the code below:
import React, { useEffect, useState } from "react";
import {
IonButton,
IonContent,
IonHeader,
IonIcon,
IonPage,
IonTitle,
IonToolbar,
} from "@ionic/react";
import MovieCard from "../components/MovieCard";
import { MovieProps } from "../type";
import { arrowBack } from "ionicons/icons";
import { useHistory } from "react-router";
const WatchList: React.FC = () => {
const history = useHistory();
const [list, setList] = useState<MovieProps[]>(() => {
const watchlist: MovieProps[] = localStorage.getItem("watchlist")
? JSON.parse(localStorage.getItem("watchlist")!)
: [];
return watchlist;
});
useEffect(() => {
const watchlist: MovieProps[] = localStorage.getItem("watchlist")
? JSON.parse(localStorage.getItem("watchlist")!)
: [];
setList(watchlist);
}, [localStorage.getItem("watchlist")]);
const handleClear = () => {
localStorage.clear();
setList([]);
};
const handleDelete = (
event: {
preventDefault: () => void;
stopPropagation: () => void;
},
movie: MovieProps
) => {
event.preventDefault();
event.stopPropagation();
const updatedWatchList = list && list.filter((i) => i.id !== movie.id);
localStorage.setItem("watchlist", JSON.stringify(updatedWatchList));
setList && setList(updatedWatchList);
};
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButton onClick={() => history.goBack()} slot="start">
<IonIcon icon={arrowBack} />
</IonButton>
<IonTitle>Watch List</IonTitle>
</IonToolbar>
<IonButton onClick={handleClear}>Clear all</IonButton>
</IonHeader>
<IonContent fullscreen>
<div className="movie-container">
{list &&
list.map((movie) => (
<MovieCard
key={movie.id}
movie={movie}
watch={true}
handleDelete={handleDelete}
/>
))}
</div>
</IonContent>
</IonPage>
);
};
export default WatchList;
The watch list is initially retrieved from local storage and stored in the watchList
state. The list is then mapped and displayed using the movie card component.
- Add the new pages to the routes inside the
App.tsx
file:
<Route path="/movies/:id" component={MovieDetails} exact={true} />
<Route path="/watchlist" component={WatchList} exact={true} />
Great job! You have now created a simple movie web and mobile application using the Ionic framework. However, at the moment, the application can only be viewed on a web browser. In the next section, we will cover how to deploy this application on both iOS and Android platforms.
Deploying the Application
To deploy and use this project as a mobile application for android platforms. Follow the steps below;
- Build the application by running the command below:
ionic build --prod
This will create a production-ready version of the application in the www
directory.
- Deploy to android by running the command below:
ionic capacitor add android
This will add the Android platform to the application.
- Run the following command to build the application:
ionic capacitor build android --prod
To deploy the application for iOS platforms, follow these steps;
- Deploy the application by running the following command:
ionic capacitor add ios
This will add the iOS platform to the application.
- Build the application using the command below:
ionic capacitor build ios --prod
Testing and Simulating on devices
To test the application on devices, you can use Ionic's tools
for live-reload and debugging. Simply run the following command in the terminal for android mobiles:
ionic capacitor run android -l --external
or run the command below for iOS devices:
ionic capacitor run ios -l --external
This command will launch the application on your device and enable live-reload and debugging. You can then make changes to the application's code and see the changes immediately on the device. You can either choose to simulate the application on your physical device or use an emulator.
Here is how the application looks on a mobile device.
Best Practices
Here are some best practices to follow when deploying Ionic applications:
- Optimize the application's performance for each platform by using platform-specific optimizations, such as lazy-loading and tree-shaking.
- Test the application on a wide range of devices and platforms to ensure that it works well for all users.
- Use Ionic's tools for debugging and profiling to identify and fix performance issues.
- Use Ionic's built-in UI components and styling to ensure a consistent look and feel across all platforms.
By following these best practices, you can ensure that your application is performant, user-friendly, and accessible to a wide range of users.
Conclusion
Whew! You sure have come a long way. You started out from knowing little to nothing about Ionic to building a whole application using the framework.
Together we covered the basics of creating a new Ionic project, adding pages and components, integrating with APIs, and styling the application. We even discussed how to test and deploy the application on both Android and iOS devices.
Ionic Framework's flexibility and ease-of-use make it an ideal choice for building cross-platform applications quickly and efficiently. I hope you keep up the momentum and continue to build amazing things with Ionic. Who knows, your next project could be the next big thing.
Additional Resources and Recommendations for Further Learning
Here are some recommended resources for further learning:
Ionic Documentation : The official Ionic documentation is a great place to start. It offers a comprehensive overview of the framework's features and provides detailed instructions on how to use them.
Ionic Academy : This website is dedicated to teaching developers how to use Ionic. It offers a wide range of tutorials, courses, and articles on various topics related to the framework.
GitHub : GitHub offers a vast collection of open-source projects using Ionic. You can search for Ionic projects on GitHub and see how other developers have implemented various features and functionalities.
Top comments (2)
merci beaucoup
Thanks for sharing!