Building a mobile chat app with Nest.js and Ionic 4

Building a mobile chat app with Nest.js and Ionic 4

In this first part of this tutorial series we will learn how to build a fullstack mobile application using cutting edge technologies like Ionic 4, Nest.js and Pusher Chatkit. Ionic will handle our front end, we will build the backend of the app using Nest.js and use Pusher Chatkit to handle messaging.

Building a mobile chat app with Nest.js and Ionic 4 - Part 1: Build the backendIntroduction

In this first part of this tutorial series we will learn how to build a fullstack mobile application using cutting edge technologies like Ionic 4, Nest.js and Pusher Chatkit. Ionic will handle our front end, we will build the backend of the app using Nest.js and use Pusher Chatkit to handle messaging.

The application that you’ll be building is a group chat application that will allow users to register, login and then chat with a group of other users.

The aim of this tutorial is to show you how you can use Pusher’s Chatkit to easily add chat features in your mobile applications built with Ionic 4, Angular and Nest.js.

You can find the source code for the first part from this GitHub repository.

Since we are not going to create the mobile UI in this part, we’ll be using cURL to interact with our application. This is a GIF image that shows how to send to POST request to the /register endpoint to register a user in then send a POST request to the /login endpoint to get a JWT token:


Prerequisites

You need to have a basic understanding of TypeScript and Node.js to follow this tutorial. TypeScript is a superset of JavaScript that adds static types to the language.

Both Nest.js and Ionic 4 (based on Angular) requires TypeScript so you need to be familiar with it.

You also need to have a recent version of Node.js (v8.11.2) and NPM (v5.6.0) installed on your machine, if they are not installed on your system, you simply need to head to the official website and grab the binaries for your system or refer to your operating system instructions for installing Node.js via the official package manager of your system.


Why use Chatkit?

Chatkit is a Pusher hosted API that allows developers to build apps with chat features without re-inventing the wheel. The available features include:

  • Group chat
  • One-to-one chat
  • Private chat
  • Typing indicators
  • “Who’s online” presence
  • Read receipts
  • Photo, video, and audio messages

The set of features covers the most needed chat features in most apps, which means you can focus on building the features that are specific to your app and let Pusher take care of the commonly needed chat features including managing chat state and data, scaling and infrastructure.


Configuring Chatkit

Setting up Chatkit is quite easy, first you need to have an account with Pusher, next, simply head to the dashboard. Under the CHATKIT box, click on the CREATE button to create your own Chatkit instance:

Enter a name for your instance and click on CREATE:

After creating your Chatkit instance, head to the Credentials tab and take note of your Instance Locator and Secret Key. You will need them later to connect your application to your Chatkit instance.

You also need to manually create a room where we’ll add new users once they sign up in our application but to interact with the instance you first need to create a user:

Add the required information and create you first Chatkit user:

You’ll be taken to this interface:

Under User Actions, click on Create and join a room. Give a name to your room and click on CREATE ROOM:

Take note of the room ID because we’ll need it later in this tutorial series.

Note: Please note that except for this first user that we manually created, all the other users will be created from the SDK and added to the room when new users register via our application interface.

Chatkit handles all the chat data and features but we need a server to create users and add authentication. For this matter, we’ll use Nest.js for setting up an authentication server.


Installing the Nest.js CLI

Before creating a Nest.js project we first need to install Nest.js CLI which makes it easy to create and manage Nest.js projects. The CLI helps you from the first step of creating a project to the final step of building a production version of your final app. It’s based on the @angular-devkit package and provides its own schematics for Nest.js development which is @nestjs/schematics.

You can install Nest.js CLI from npm via the following command:

    $ npm install -g @nestjs/cli

Note: Please note that you may need to use sudo on Debian based system or macOS or an elevated administrator command prompt on Windows to install Node.js globally on your system depending on your npm configuration. As the time of this writing, Nest.js CLI v5.6.3 will be installed.

You can also create a Nest.js project by pulling the nestjs/cli[:version] Docker image or cloning the https://github.com/nestjs/nest-cli.git repository and installing dependencies. For more information, you can see the official repository.


Creating a new Nest.js project

After installing Nest.js CLI, you can now run the following command to easily create a new project:

    $ mkdir chatkit-nestjs-ionic
$ cd chatkit-nestjs-ionic
$ nest new server

The CLI will ask you for a bunch of information like the description and author and which package manager to use for installing packages, either npm or Yarn, enter the required information then hit Enter to start generating your project files and installing dependencies:

Wait a little to finish the installation process:

Then you can navigate inside your project’s folder and run a local development server:

    $ cd server
$ npm run start

As you can see from the screenshot, this command allows you to start a development server on the port configured inside the src/main.ts file.

Your server is now running, you can simply open your browser and navigate to localhost:3000. You should see the Hello world! message.

Since we use Chatkit for adding all chat features, we will not need to implement any feature in the server except for JWT authentication and user management.


Setting up TypeORM and creating a database

For storing and registering users we need a database.

Nest.js supports TypeORM which is considered the most mature Object Relational Mapper (ORM) available in TypeScript. It’s available from the @nestjs/typeorm package.

Let’s start by installing the required dependencies:

    $ npm install --save @nestjs/typeorm typeorm sqlite3

For the sake of simplicity, we’ll use an SQLite database, but TypeORM supports all major databases like MySQL, PostgreSQL, MSSQL, Oracle, and MongoDB.

Note: Since an ORM abstracts away any direct operation with the underlying database system, you can later switch to use a fully fledged system like MySQL for production without changing anything in your code. But for now, let’s keep it simple and use SQLite.

After finishing up with installing the dependencies, you need to import the TypeOrmModule into the root ApplicationModule module. Open the src/app.module.ts file and add the following changes:

    // server/src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [
   TypeOrmModule.forRoot({
      type: 'sqlite',
      database: 'my.db',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
   }),
],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

We import TypeOrmModule and we call the forRoot() method which takes the same configuration object as the standard createConnection() method of TypeORM.

In the configuration object, we specify:

  • The sqlite string for type so we can use SQLite as the database,
  • The my.db string for the database file (SQLite uses files to store the database),
  • The entities array which refers to all files that end with .entity.ts or .entity.js extensions. These files are created by developers and contain the ORM entities.
  • The synchronize option which takes true or false and allows you to automatically sync your database tables with the entities each time you run the app. In development, you can set it to true but it’s not preferable in production.
Note: Now, you can inject the Connection and EntityManager services anywhere you want to access them.

Next, let’s create a User entity which corresponds to a user in the database. Create a src/models/user.entity.ts file and add the following class:

    // server/src/models/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;

  @Column()
  name: string;

  @Column()
  email: string;

  @Column()
  password: string;
}

You need to import the User entity and add it in the imports array of the module using the forFeature method:

    // server/src/app.module.ts
import { User } from './models/user.entity';
...

@Module({
imports: [
...
TypeOrmModule.forFeature([User]),

Next, let’s create a UserService that encapsulates all database operations that we need to perform against the User model.

Head back to your terminal and run the following command to generate a service:

    $ nest g s user

This command will create the src/user/user.service.ts file that contains the actual service code and the src/user/user.service.spec.ts file that contains the unit tests for the service. And also update the src/app.module.ts file by including UserService in the providers array.

Next, let’s add the create and findByEmail methods in the src/user/user.service.ts file which will be used respectively to persist a user and find a user by its email in the database.

    // server/src/user/user.service.ts
import { Injectable } from '@nestjs/common';
import { User } from '../models/user.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';

@Injectable()
export class UserService {
    constructor(
        @InjectRepository(User)
        private userRepository: Repository<User>,
    ) { }

    async  findByEmail(email: string): Promise<User> {
        return await this.userRepository.findOne({
            where: {
                email: email,
            }
        });
    }

    async  create(user: User): Promise<User> {
        return await this.userRepository.save(user);
    }
}

First we import User, Repository and InjectRepository, next, inject the User repository via the service’s constructor and finally we define our methods.

The findByEmail method simply calls the findOne method of the injected repository to search for a user by the passed email in the database.

The create method calls the save method of the injected repository to save a user in the database.


Adding JWT authentication

Authentication is important for most web applications. You can follow different ways and approaches to implement user authentication. In this tutorial, we’ll implement authentication with JSON Web Tokens (JWTs).

First, you need to install the JWT utilities module for Nest.js using :

    $ npm install --save @nestjs/jwt

Next, open the /src/app.module.ts file and include the module in the imports array:

    // server/src/app.module.ts
import { JwtModule } from '@nestjs/jwt';
// [...]

JwtModule.register({
    secretOrPrivateKey:  'secret123'
})

We also provided a private secret key that will be used to sign the JWT payload.

To interact with Chatkit, you also need valid JWT tokens that will be obtained by the client by using a token provider and will be sent with every request that the client makes to Chatkit.

Chatkit provides a test token provider that can be used to quickly start testing the chat features but it should be only used for testing. For production, you need to create your own token provider which can be done in two ways:

  • Either, by using the provided server SDKs.
  • Or without the help of the server SDKs using a JWT library or your own custom JWT implementation. See this link for more information.

In this tutorial, we’ll use the Node.js SDK for Chatkit to add a token provider in our Nest.js project so head back to your terminal and run the following command from the root of your project to install it:

    $ npm install @pusher/chatkit-server --save

Next, let’s create the AuthService class that will encapsulate the code for implementing JWT authentication in our application.

Using Nest.js CLI run the following command to generate a service:

    $ nest g s auth

This command will add the /src/auth/auth.service.ts file that contains the service and the /src/auth/auth.service.spec.ts file that contains the tests for the service and will update the main app module contained in the /src/app.module.ts file to include the generated service.

If you open the main module file at this stage, you can see that the JwtauthService was imported and included in the providers array:

    // server/src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthService } from './auth/auth.service';
// [...]

@Module({
  imports: [/* [...] */],
  controllers: [AppController],
  providers: [AppService, UserService,AuthService],
})
export class AppModule {}

Now, after creating the service, you need to import the Chatkit server SDK, JwtService , UserService , the User entity and the AuthenticationResponse. Open the src/auth/auth.service.ts file and add the following import:

    // server/src/auth/auth.service.ts
import Chatkit from '@pusher/chatkit-server';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../user/user.service';
import { User } from '../models/user.entity';
import Chatkit, { AuthenticationResponse } from '@pusher/chatkit-server';

Next, you need to add the following code:

    // server/src/auth/auth.service.ts
@Injectable()
export class AuthService {
chatkit: Chatkit;
constructor(
private readonly userService: UserService,
private readonly jwtService: JwtService
) {
this.chatkit = new Chatkit({
instanceLocator: YOUR_INSTANCE_LOCATOR,
key: YOUR_SECRET_KEY
})
}

We add a member variable to the service that holds the Chatkit instance. Next we inject UserService and JwtService via the constructor and inside it, we create the Chatkit instance.

Replace YOUR_INSTANCE_LOCATOR and YOUR_SECRET_KEY with the credentials from the dashboard. When a user connects to Chatkit, a request will be sent to a /token endpoint (that will be created later in this tutorial) to authenticate the user. Your server has to send a response that contains a token using the Chatkit.authenticate method if the request is valid.

Now, you need to define and implement the following methods:

  • getToken: It’s used to create and return a valid JWT token. This method will simply use the authenticate method of the Chatkit instance to generate a valid token.
  • validateUser: It’s used to validate the user. This method will use the findByEmail method of UserService to check if the user exists in the database.
  • createUser: It’s used to create a user in the local database and then in the Chatkit instance.

Let’s start with the createUser method which takes a parameter of the User type:

    // server/src/auth/auth.service.ts
private async createUser(userData: User): Promise<User>{
return this.userService.create(userData).then(user =>{
const userId = ${user.name}${user.id};
const roomId = "YOUR_ROOM_ID";
const avatarURL = "https://image.flaticon.com/icons/png/128/149/149071.png";

      return this.chatkit.createUser({id: userId, 
         name: user.name,
         avatarURL: avatarURL
      }).then(()=&gt;{

        return this.chatkit.addUsersToRoom({ roomId: roomId,
          userIds: [userId]}).then(()=&gt;{
            return user;
        });

      })

    });
}

Replace YOUR_ROOM_ID with the room id from the dashboard.

This method calls the create method of UserService to persist the user in the database then when the Promise successfully resolves with a user object that has a unique identifier in the database we use the id and name to create a corresponding user in the Chatkit instance by calling the createUser method of the instance and finally we add the user to the room by calling the addUsersToRoom method.

The createUser method of the Chatkit instance requires a unique user identifier and a user name. We construct the user id by concatenating the name with the database id of the user. This way we make sure the Chatkit user id is unique. We also provide a user avatar for testing using the https://image.flaticon.com/icons/png/128/149/149071.png URL.

Note: In a production application, you need to provide your users with a way to upload their avatars then associate them with the Chatkit user. You also need to hash passwords before storing them in the database using a tool like bcrypt.

Let’s now define the getToken method. It takes a user ID and returns an AuthenticationResponse:

    // server/src/auth/auth.service.ts
public getToken(userId: string): AuthenticationResponse {
return this.chatkit.authenticate({ userId: userId });
}

The getToken method is simply a wrapper around the authenticate method of the Chatkit instance which returns a valid JWT token that can be used by the client to access Chatkit APIs. The authenticate method takes a userId that we specify when we create the user in the Chatkit instance (a concatenation of the word name and the database identifier of the user).

Another method that we need to define is the validateUser method which takes a parameter of the User type:

    // server/src/auth/auth.service.ts
private async validateUser(userData: User): Promise<User> {
return await this.userService.findByEmail(userData.email);
}

This method calls the findByEmail method of UserService which checks if the user with the email exists in the database. If it exists the user object is returned otherwise a null object is returned.

After defining these methods, we’ll use them to define two public methods in the same service which are:

  • register for registering users,
  • login for login users.

This is the implementation of the two methods:

    // server/src/auth/auth.service.ts
public async login(user: User): Promise<any | {status: number}>{
return this.validateUser(user).then((userInfo)=>{
if(!userInfo){
return { status: 404 };
}
let userId = ${userInfo.name}${userInfo.id};
const accessToken = this.jwtService.sign(userId);
return {
expires_in: 3600,
access_token: accessToken,
user_id: userId,
status: 200
};

    });
}

public async register(user: User): Promise&lt;any&gt;{
    return this.createUser(user)
}

In the login method, we first use the validateUser method to make sure the user exists in the database then we call the sign method of JwtService to create an access token from the user id and name payload. Finally, we return an object containing the expires_in, access_token, user_id and status properties.

In the register method, we simply call the previously-defined createUser method to create a user in the database and then in the remote Chatkit instance.


Creating endpoints

After implementing the login and register methods, it’s time to create the corresponding endpoints in our application that handle user authentication. We also need to create a /token endpoint that will be used by the Chatkit client SDK to request JWT tokens from our server.

Open the existing src/app.controller.ts file and update it accordingly:

    // server/src/app.controller.ts
import { Post, Body,Request, Controller} from '@nestjs/common';
import { AuthService } from './auth/auth.service';
import { User } from './models/user.entity';

@Controller()
export class AppController {
  constructor(private readonly authService: AuthService) {}

  @Post('token')
  async token(@Request() req): Promise&lt;any&gt; {
    return this.authService.getToken(req.query.user_id).body;
  }

  @Post('login')
  async login(@Body() userData: User): Promise&lt;any&gt; {
    return this.authService.login(userData);
  }  

  @Post('register')
  async register(@Body() userData: User): Promise&lt;any&gt; {
    return this.authService.register(userData);
  }    
}

We start by importing the Post, Request and Body decorators, and also AuthService and the User entity. Next, we inject AuthService as an authService instance via the controller’s constructor.

Finally, we instruct Nest.js to create the three /token, /login and /register routes that accept a POST request by decorating their methods with the @Post decorator (the route is passed as a parameter).

For the login and register methods, we use the @Body() decorator to instruct Nest.js to inject the body of the received request in the endpoint handler as userData.

For the token method we need the full request so we use the @Request decorator instead.

Note: We could also create a controller for handling authentication using nest g controller auth but since our Nest.js app has only one task which is to handle JWT auth we can simply use the existing application controller.
Testing our auth endpoints

After creating the authentication endpoints, let’s use cURL to test them before we create our front-end mobile application in the next tutorial.

First, run the following command from the root of your project to start the Nest.js development server:

    $ npm start

Next, make sure you have cURL installed on your system and run the following command from your terminal:

    curl -X POST -H 'content-type: application/json'  -d  '{ "email": "[email protected]", "name": "ahmed", "password": "pass001" }' localhost:3000/register

This will create a user in your SQLite database and a Chatkit user that you can see from the Console/INSTANCE INSPECTOR tab in your Chatkit dashboard. The endpoint returns the created Chatkit user with the id, name, created_at and updated_at fields.

You can also test the /login endpoint using:

    curl -X POST -H 'content-type: application/json'  -d  '{ "email": "[email protected]", "password": "pass001"}' localhost:3000/login

This should return a response object with an access token and a user id.


Enabling CORS

Since we’ll be using Ionic for creating the mobile app that will interact with this server and we’ll do most Ionic development on the browser we need to setup CORS (Cross Origin Resource Sharing). Otherwise, the browsers will block the requests to the server due to the same origin policy.

You can easily enable CORS in Nest.js by opening the src/main.ts file and calling the app.enableCors method:

    // server/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors();
  await app.listen(3000);
}
bootstrap();

Conclusion

In this tutorial, we’ve seen how to create a server for JWT authentication using Nest.js and Chatkit’s SDK for Node.js.

In the next tutorial, we’ll continue developing our mobile application that uses this server for authentication and Chatkit for implementing the chat features.

You can find the source code for the first part from this GitHub repository.


Building a mobile app with Nest.js, Ionic 4 and Chatkit - Part 2: Build the frontend

In the previous tutorial we have created the server side of our chat application that uses TypeScript, Nest.js and Chatkit. Now, in the second tutorial, we are going to create the frontend mobile application using Ionic 4 and Pusher Chatkit.

Chatkit provides developers with client and server side SDKs for working with the API. In the previous tutorial, we used the Node.js SDK. In this tutorial, we’ll be using the JavaScript SDK.

You can find the source code for this part in this GitHub repository.


Prerequisites

To follow along with this tutorial you need to have these prerequisites:

  • Working knowledge of TypeScript,
  • Working knowledge of Angular (we’ll be using Ionic with Angular),
  • Recent versions of Node.js (v8.11.2) and npm (v5.6.0) installed on your system.

You can check the installed versions of Node.js and npm using the following commands from your terminal:

$ node --version
$ npm --version

What we’ll be building

In this part, we’ll build a chat mobile application with several chat features like:

  • User registration and login with email and password,
  • Listing users in a room with their online status,
  • Group chatting.
Introducing Ionic 4

Ionic 4 is a mobile UI library built on top of modern web technologies like web components and CSS variables. It’s the latest version of the most popular UI framework (now just a library) for building hybrid mobile applications with JavaScript and the web.

Ionic 4 aims to become just a UI mobile library that can be used by developers with any preferred client side library or framework like for example Angular, Vue, or React. You can also use it with plain JavaScript to build mobile applications.

Being framework-agnostic doesn’t mean that support for Angular is dropped. In fact, the Ionic team is also working on the Ionic-Angular v4 package that could be seen as the next version of Ionic 3 (which could be only used with Angular).


Installing the Ionic CLI v4

Now if you have Node.js and npm installed, you can go ahead and install Ionic CLI v4 by running the following command from your terminal:

$ npm install -g ionic

Note: Please note that depending on how you configured npm in your system you may need to open an Admin command prompt on Windows or run the command with sudo in macOS and Linux if you want to install packages globally. As the time of this writing, Ionic v4.5.0 is installed.
Creating an Ionic/Angular v4 project

You can create Ionic projects using the Ionic CLI and you can also specify which starter or base template you want to use for your project:

  • The blank starter: it provides a base blank project with one page.
  • The tabs starter: it provides a base project with tabs.
  • The side menu starter: it provides a base project with a side menu.

Now head back to your terminal and run the ionic start command to generate your project based on the blank template:

$ cd chatkit-nestjs-ionic
$ ionic start frontend blank --type=angular

Note: You also need to specify the type of framework to use with the --type=angular option, which is new in Ionic CLI v4 (For now the CLI supports only Angular).

The Ionic CLI will prompt you if you want to integrate Cordova.

  • Yes if you need to target native iOS and Android or,
  • No if you only need the web version of your project. For example, to develop a Progressive Web App.

You can enter No for now since we’ll be using the browser for testing.

You can also enter No for Install the free Ionic Appflow SDK and connect your app? If you don’t want to install Ionic Appflow SDK.

Wait for your project to be generated and the dependencies to get installed then run the following command to serve your project locally:

$ cd frontend
$ ionic serve

Your application will be running from the localhost:8100 address.


Installing the Chatkit client side SDK

Let’s now start implementing Chatkit by installing the JavaScript client side SDK using the following command:

$ cd frontend
$ npm install @pusher/chatkit-client --save

We’ll be importing this library in our project in the next section.


Setting up HttpClient, Forms and Ionic Storage

We’ll be using Angular HttpClient for sending requests to Chatkit and to our Nest.js server so we need to set it up in the project. Open the src/app/app.module.ts file, import HttpClientModule and FormsModule then add them to the imports array:

// frontend/src/app/app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';

@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,
HttpClientModule,FormsModule
],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {}

We’ll use the Ionic storage module for working with the browser’s local storage so we first need to install it via npm:

$ npm install  --save  @ionic/storage

Next, add IonicStorageModule.forRoot() in the imports array:

// frontend/src/app/app.module.ts
// [...]
import { IonicStorageModule } from '@ionic/storage';

@NgModule({
// [...]
imports: [/* [...] */,IonicStorageModule.forRoot()],
providers: [
// [...]
],
bootstrap: [AppComponent]
})
export class AppModule {}

Note: At this point, you can start your development server with ionic serve to make sure you don’t have any problems starting your app.
Creating the authentication service

After installing the client SDK, we’ll create an Angular service that handles authentication in the mobile application.

First, in you terminal, create a User interface using the following command:

$ ionic g interface user

This will create a src/app/user.ts file. Open it and update it as follows:

// frontend/src/app/user.ts
export interface User {
id: number;
name: string;
email: string;
password: string;
}

Again, in your terminal, run the following command to generate a service:

$ ionic g service auth

This will create the src/app/auth.service.ts and src/app/auth.service.spec.ts files. Open the src/app/auth.service.ts file and start by adding the following imports:

// frontend/src/app/auth.service.ts
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { Storage } from '@ionic/storage';
import { User } from './user';

Next create the AUTH_SERVER variable:

// frontend/src/app/auth.service.ts
AUTH_SERVER: string = 'http://localhost:3000';

AUTH_SERVER holds the address of our authentication (Nest.js) server and authState.

Next, inject HttpClient and Storage via the service constructor:

// frontend/src/app/auth.service.ts
@Injectable({
providedIn: 'root'
})
export class AuthService {

constructor(private  httpClient:  HttpClient, private  storage:  Storage) {}

}

Next, add three methods to the src/app/auth.service.ts file for registration, and login:

// frontend/src/app/auth.service.ts
register(userInfo: User): Observable<User>{
return this.httpClient.post<User>(${this.AUTH_SERVER}/register,userInfo);
}

login(userInfo: User): Observable<any>{
return this.httpClient.post(${this.AUTH_SERVER}/login,userInfo).pipe(
tap( async (res: { status: number, access_token, expires_in, user_id })=>{
if(res.status !== 404){
await this.storage.set("ACCESS_TOKEN", res.access_token);
await this.storage.set("EXPIRES_IN", res.expires_in);
await this.storage.set("USER_ID", res.user_id);
}
})
);
}

At this point, here is how the complete src/app/auth.service.ts file looks like:

// frontend/src/app/auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { Storage } from '@ionic/storage';
import { User } from './user';
@Injectable({
providedIn: 'root'
})
export class AuthService {
AUTH_SERVER: string = 'http://localhost:3000';
constructor(private httpClient: HttpClient, private storage: Storage) { }
register(userInfo: User): Observable<User> {
return this.httpClient.post<User>(${this.AUTH_SERVER}/register, userInfo);
}
login(userInfo: User): Observable<any> {
return this.httpClient.post(${this.AUTH_SERVER}/login, userInfo).pipe(
tap(async (res: { status: number, access_token, expires_in, user_id }) => {
if (res.status !== 404) {
await this.storage.set("ACCESS_TOKEN", res.access_token);
await this.storage.set("EXPIRES_IN", res.expires_in);
await this.storage.set("USER_ID", res.user_id);
}
})
);
}
}

That’s it, we’ve finished with the authentication service. In the next section, we’ll see how you can use this service for adding authentication to your application.


Creating the registration and login pages

After creating the authentication service we can now create the register and login pages that will allow the users to either register or login. In your terminal, run the following command:

$ ionic g page login

This will generate a src/app/login folder with the following files:

  • src/app/login/login.module.ts
  • src/app/login/login.page.scss
  • src/app/login/login.page.html
  • src/app/login/login.page.spec.ts
  • src/app/login/login.page.ts

And will update the src/app/app-routing.module.ts file that holds the routing information by adding the following route:

// frontend/src/app/app-routing.module.ts
{ path: 'login', loadChildren: './login/login.module#LoginPageModule' }

That means we can access the login page from the /login path. You also need to generate a page for registering users using:

$ ionic g page register

This will generate a src/app/register folder with similar files to the login page and will add the following route:

// frontend/src/app/app-routing.module.ts
{ path: 'register', loadChildren: './register/register.module#RegisterPageModule' }

That means we can access this page from the /register path.

Implementing the register page

Let’s start adding the code for registering users. Open the src/app/register/register.page.ts file then import and inject AuthService and Router:

// frontend/src/app/register/register.page.ts
import { Component, OnInit } from '@angular/core';
import { Router } from "@angular/router";
import { AuthService } from '../auth.service';

@Component({
selector: 'app-register',
templateUrl: './register.page.html',
styleUrls: ['./register.page.scss'],
})
export class RegisterPage implements OnInit {
constructor(private authService: AuthService, private router: Router) { }
ngOnInit() {
}
}

Next, add the following method:

// frontend/src/app/register/register.page.ts
import { Component, OnInit } from '@angular/core';
import { Router } from "@angular/router";
import { AuthService } from '../auth.service';

@Component({
selector: 'app-register',
templateUrl: './register.page.html',
styleUrls: ['./register.page.scss'],
})
export class RegisterPage implements OnInit {
constructor(private authService: AuthService, private router: Router) { }
ngOnInit() {
}
register(form) {
this.authService.register(form.value).subscribe((res) => {
this.router.navigateByUrl('login');
});
}
}

We simply call the register() method of the authentication service and we pass the form value then we subscribe to the returned observable. After registration is successfully done we navigate to the login page.

Next open the src/app/register/register.page.html and add a form inside <ion-content> to get the user’s information:

// frontend/src/app/register/register.page.html
<ion-header>
<ion-toolbar color="primary">
<ion-title>Chatkit Demo</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<form #f="ngForm" (ngSubmit)="register(f)">
<ion-grid>
<ion-row justify-content-center>
<ion-col align-self-center size-md="6" size-lg="5" size-xs="12">
<div text-center>
<h3>Register</h3>
</div>
<div padding>
<ion-item>
<ion-input name="name" type="text" placeholder="Name" ngModel required></ion-input>
</ion-item>
<ion-item>
<ion-input name="email" type="email" placeholder="[email protected]" ngModel required></ion-input>
</ion-item>
<ion-item>
<ion-input name="password" type="password" placeholder="Password" ngModel required></ion-input>
</ion-item>
<ion-item>
<ion-input name="confirm" type="password" placeholder="Password again" ngModel required></ion-input>
</ion-item>
</div>
<div padding>
<ion-button size="large" type="submit" [disabled]="f.invalid" expand="block">Register</ion-button>
</div>
</ion-col>
</ion-row>
</ion-grid>
</form>
</ion-content>

In the form, we bind the register method we created before with the ngSubmit event so the method gets called when the user submits the form.

If you run your application and go to the http://localhost:8100/register address, you should see this page:

Implementing the login page

Next, let’s implement the login page. Open the src/app/login/login.page.ts file then import AuthService and Router:

// frontend/src/app/login/login.page.ts
import { Router } from "@angular/router";
import { AuthService } from '../auth.service';

Next inject them via the service constructor:

// frontend/src/app/login/login.page.ts
constructor(private authService: AuthService, private router: Router) { }

Next, add a showError variable:

// frontend/src/app/login/login.page.ts
export class LoginPage implements OnInit {
showError: boolean = false;

Finally, add the login method:

// frontend/src/app/login/login.page.ts
login(form){
this.authService.login(form.value).subscribe((res)=>{
if(res.status == 200){
this.showError = false;
this.router.navigateByUrl(home/${res.user_id});
}
else{
this.showError = true;
}
});
}

In this method, we call the login() method of the authentication server and we pass the form data (email and password) using the .value member variable of the form instance. Next, we subscribe to the observable and check the status of the returned response. If the request is successful i.e res.status == 200 we navigate to the home page of the application and we pass user_id as a parameter to the home/ URL. Otherwise, we simply set showError to true.

At this point, this is how the complete src/app/login/login.page.ts looks like:

// frontend/src/app/login/login.page.ts
import { Component, OnInit } from '@angular/core';
import { Router } from "@angular/router";
import { AuthService } from '../auth.service';

@Component({
selector: 'app-login',
templateUrl: './login.page.html',
styleUrls: ['./login.page.scss'],
})
export class LoginPage implements OnInit {
showError: boolean = false;
constructor(private authService: AuthService, private router: Router) { }
ngOnInit() {
}
login(form) {
this.authService.login(form.value).subscribe((res) => {
if (res.status == 200) {
this.showError = false;
this.router.navigateByUrl(home/${res.user_id});
}
else {
this.showError = true;
}
});
}
}

Let’s now add the form to get the user’s email and password in the login page. Open the src/app/login/login.page.html file and add a form inside <ion-content>:

// frontend/src/app/login/login.page.html
<ion-header>
<ion-toolbar color="primary">
<ion-title>Chatkit Demo</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<form #f="ngForm" (ngSubmit)="login(f)">
<ion-grid>
<ion-row justify-content-center>
<div *ngIf="showError">Error! Please try again</div>
</ion-row>
<ion-row justify-content-center>
<ion-col align-self-center size-md="6" size-lg="5" size-xs="12">
<div text-center>
<h3>Login</h3>
</div>
<div padding>
<ion-item>
<ion-input name="email" type="email" placeholder="[email protected]" ngModel required></ion-input>
</ion-item>
<ion-item>
<ion-input name="password" type="password" placeholder="Password" ngModel required></ion-input>
</ion-item>
</div>
<div padding>
<ion-button size="large" type="submit" [disabled]="f.invalid" expand="block">Login</ion-button>
</div>
</ion-col>
</ion-row>
</ion-grid>
</form>
</ion-content>

If you visit the http://localhost:8100/login address, you should see this page:

That’s it for the authentication part, next we’ll be working with Chatkit to add chat features to our application.


Creating the chat service and connecting to Chatkit

After implementing user authentication in our application, we’ll create an Angular service that will encapsulate all the code required to connect with Chatkit and call any chat features so head back to your terminal and run this command:

$ ionic g service chat

The command will create the src/app/chat.service.ts file that contains the actual code for the service and the src/app/chat.service.spec.ts file that contains the specifications or test units.

Now open the src/app/chat.service.ts file, and let’s add the code to link our application to Chatkit.

Start by importing ChatManager, TokenProvider and BehaviorSubject:

// frontend/src/app/chat.service.ts
import { Observable, BehaviorSubject } from 'rxjs';
import { ChatManager, TokenProvider } from '@pusher/chatkit-client';

Next, add the following variables to the service:

// frontend/src/app/chat.service.ts
AUTH_URL = 'http://localhost:3000/token';
INSTANCE_LOCATOR = 'YOUR_INSTANCE_LOCATOR';
GENERAL_ROOM_ID = 'YOUR_ROOM_ID';
GENERAL_ROOM_INDEX = 0;

chatManager: ChatManager;
currentUser;
messages = [];

usersSubject = new BehaviorSubject([]);
messagesSubject = new BehaviorSubject([]);

Make sure you create a room in your dashboard then replace YOUR_INSTANCE_LOCATOR and YOUR_ROOM_ID with your own values. Next, add the following method that allows you to connect to your Chatkit instance and subscribe to your room:

// frontend/src/app/chat.service.ts
async connectToChatkit(userId: string){
this.chatManager = new ChatManager({
instanceLocator: this.INSTANCE_LOCATOR,
userId: userId,
tokenProvider: new TokenProvider({ url: this.AUTH_URL})
})

this.currentUser = await this.chatManager.connect();

await this.currentUser.subscribeToRoom({
  roomId: this.GENERAL_ROOM_ID,
  hooks: {},
  messageLimit: 20
});

}

We create an instance of [ChatManager](https://docs.pusher.com/chatkit/reference/javascript#chat-manager) and we assign it to chatManager variable. We pass in an instance locator, a user ID and a token provider.

We then use its [connect](https://docs.pusher.com/chatkit/reference/javascript#connect) method to connect to Chatkit and retrieve a current user object that we’ll use to interact with our Chatkit instance, rooms, messages, and users. The connect method returns a promise that resolves with a Current User object.

Finally, we use the subscribeToRoom method to subscribe to our room. This will allow us to be notified when new messages or users are added to the room. We pass in the room ID, a hooks object and a message limit number.

In the hooks object of the subscribeToRoom method, we need to provide subscription hooks that will be called when a new message or a user is added to the room or an event like user typing is triggered:

// frontend/src/app/chat.service.ts
hooks: {
onMessage: message => {
this.messages.push(message);
this.messagesSubject.next(this.messages);
}
},

In our case, we are using the onMessage hook that gets called for new messages.

Next after calling the subscribeToRoom method add the following code:

// frontend/src/app/chat.service.ts
const users = this.currentUser.rooms[this.GENERAL_ROOM_INDEX].users;
this.usersSubject.next(users);

This will allow us to get the list of users in the room.

Next, add the following methods which return the behavior subjects from the service:

// frontend/src/app/chat.service.ts
getUsers(){
return this.usersSubject;
}

getMessages(){
return this.messagesSubject;
}

Next, add the sendMessage that is used to send a message to the room:

// frontend/src/app/chat.service.ts
sendMessage(message){
return this.currentUser.sendMessage({
text: message.text,
roomId: message.roomId || this.GENERAL_ROOM_ID
})
}

Finally, we add a couple of other needed methods for checking the status of the user and return the current user:

// frontend/src/app/chat.service.ts
isUserOnline(user): boolean {
return user.presence.state == 'online';
}

getCurrentUser(){
return this.currentUser;
}

This is how the complete src/app/chat.service.ts file looks like:

// frontend/src/app/chat.service.ts
import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { ChatManager, TokenProvider } from '@pusher/chatkit-client';

@Injectable({
providedIn: 'root'
})
export class ChatService {
AUTH_URL = 'http://localhost:3000/token';
INSTANCE_LOCATOR = 'YOUR_INSTANCE_LOCATOR';
GENERAL_ROOM_ID = 'YOUR_ROOM_ID';
GENERAL_ROOM_INDEX = 0;
chatManager: ChatManager;
currentUser;
messages = [];

usersSubject = new BehaviorSubject([]);
messagesSubject = new BehaviorSubject([]);
constructor() { }
async connectToChatkit(userId: string) {
this.chatManager = new ChatManager({
instanceLocator: this.INSTANCE_LOCATOR,
userId: userId,
tokenProvider: new TokenProvider({ url: this.AUTH_URL })
})
this.currentUser = await this.chatManager.connect();
await this.currentUser.subscribeToRoom({
roomId: this.GENERAL_ROOM_ID,
hooks: {
onMessage: message => {
this.messages.push(message);
this.messagesSubject.next(this.messages);
}
},
messageLimit: 20
});

const users = this.currentUser.rooms[this.GENERAL_ROOM_INDEX].users;
this.usersSubject.next(users);

}

getUsers() {
return this.usersSubject;
}
getMessages() {
return this.messagesSubject;
}
sendMessage(message) {
return this.currentUser.sendMessage({
text: message.text,
roomId: message.roomId || this.GENERAL_ROOM_ID
})
}
isUserOnline(user): boolean {
return user.presence.state == 'online';
}
getCurrentUser() {
return this.currentUser;
}
}

That’s it for our chat service, next we’ll use this service to implement group chat in our mobile app.


Displaying room users on the home page

When a user registers in our application a Chatkit user is created behind the scenes. Open the src/app/home/home.page.ts file and start by adding the following imports:

// frontend/src/app/home/home.page.ts
import { OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { ChatService } from '../chat.service';
import { User } from '../user';

Next, add the following variables to the component:

// frontend/src/app/home/home.page.ts
export class HomePage implements OnInit{
userId: string = '';
userList: any = [];

Next inject ChatService and ActivatedRoute which is used to get route parameters:

// frontend/src/app/home/home.page.ts
constructor(private chatService: ChatService, private route: ActivatedRoute){}

Finally when the component is initialized we connect to our Chatkit instance and we retrieve the room users:

// frontend/src/app/home/home.page.ts
ngOnInit(){
this.userId = this.route.snapshot.params.id;
this.chatService.connectToChatkit(this.userId);
this.chatService.getUsers().subscribe((users)=>{
this.userList = users;
});
}

On the ngOnInit life-cycle event of the component we first retrieve the user id from the route path then we call the connectToChatkit method of ChatServiceto connect the Chatkit.

Note: HomePage needs to implement OnInit i.e export class HomePage implements OnInit.

Finally, we subscribe to the getUsers method of ChatService to get the room’s users and add them to userList.

One more method that we need in our component is:

// frontend/src/app/home/home.page.ts
isOnline(user){
return this.chatService.isUserOnline(user);
}

This will allow us to check if the user is online.

This is the full content of the the src/app/home/home.page.ts:

// frontend/src/app/home/home.page.ts
import { Component } from '@angular/core';
import { OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { ChatService } from '../chat.service';
import { User } from '../user';

@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
userId: string = '';
userList: any = [];
constructor(private chatService: ChatService, private route: ActivatedRoute) { }
ngOnInit() {
this.userId = this.route.snapshot.params.id;
this.chatService.connectToChatkit(this.userId);
this.chatService.getUsers().subscribe((users) => {
this.userList = users;
});
}
isOnline(user) {
return this.chatService.isUserOnline(user);
}
}

Since we pass the user ID from the login page to the home page, we need to change the path of the home component to accept a parameter. Open the src/app/home/home.module.ts and change the path as follows:

// frontend/src/app/home/home.module.ts
RouterModule.forChild([
{
path: ':id',
component: HomePage
}
])

Now we need to display the list of users in the room and a button to start chatting. Open the src/app/home/home.page.html file, remove what’s inside <ion-content> and add:

// frontend/src/app/home/home.page.html
<div text-center>
<ion-button color="light" outline size="large" [routerLink]="'/chat'">
<ion-icon name="chatbubbles"></ion-icon>
Start chatting
</ion-button>
</div>

This will add a button that takes the user to a chat page (that will be creating next). Next inside <ion-content>, add an Ionic spinner to create a loading animation:

// frontend/src/app/home/home.page.html
<h4>Users</h4>
<ion-spinner name="dots" *ngIf="userList.length === 0"></ion-spinner>

Now, add the list to display users:

// frontend/src/app/home/home.page.html
<ion-list>
<ion-item class="user-item" *ngFor="let user of userList">
<div class="user-avatar">
<img [src]="user.avatarURL" alt="">
</div>
<ion-label class="user-name">
{{ user.name }}
</ion-label>
<div class="user-presence">
<ion-icon [class.user-online]="isOnline(user)" name="radio-button-on"></ion-icon>
</div>
</ion-item>
</ion-list>

We simply loop over userList and display each user’s name, avatar and online status.

Also change the title of the page and the color of the toolbar:

// frontend/src/app/home/home.page.html
<ion-header>
<ion-toolbar color="primary">
<ion-title>
Chatkit Demo
</ion-title>
</ion-toolbar>
</ion-header>

This is how the complete src/app/home/home.page.html file looks like:

// frontend/src/app/home/home.page.html
<ion-header>
<ion-toolbar color="primary">
<ion-title>
Chatkit Demo
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<div text-center>
<ion-button color="light" outline size="large" [routerLink]="'/chat'">
<ion-icon name="chatbubbles"></ion-icon>
Start chatting
</ion-button>
</div>
<h4>Users</h4>
<ion-spinner name="dots" *ngIf="userList.length === 0"></ion-spinner>
<ion-list>
<ion-item class="user-item" *ngFor="let user of userList">
<div class="user-avatar">
<img [src]="user.avatarURL" alt="">
</div>
<ion-label class="user-name">
{{ user.name }}
</ion-label>
<div class="user-presence">
<ion-icon [class.user-online]="isOnline(user)" name="radio-button-on"></ion-icon>
</div>
</ion-item>
</ion-list>
</ion-content>

Finally let’s add some styling. Open the src/app/home/home.page.scss file and add the following styles:

// frontend/src/app/home/home.page.scss
.user-item {
display: flex;
.user-avatar{
flex : 1;
img{
width: 30px;
height: 30px;
}
}
.user-name{
flex: 2;
}
.user-presence{
flex: 1;
}
.user-online{
color: #32eb32;
}
}

We use CSS Flex layout to style each list item.


Creating the chat page

When the user clicks on START CHATTING he will be taking to a chat page that we’re going to create in this section. Head back to your terminal and run the following command:

$ ng generate page chat

Open the src/app/chat/chat.page.ts file and first add the following imports:

// frontend/src/app/chat/chat.page.ts
import { Router } from '@angular/router';
import { ChatService } from '../chat.service';
import { User } from '../user';

Next inject Router and ChatService:

// frontend/src/app/chat/chat.page.ts
constructor(private router: Router, private chatService: ChatService) { }

Next add the following variables to the component:

// frontend/src/app/chat/chat.page.ts
messageList: any[] = [ ];
chatMessage: string ="";

Next on the ngOnInit life-cycle event get the messages and assign them to messageList:

// frontend/src/app/chat/chat.page.ts
ngOnInit() {
this.chatService.getMessages().subscribe(messages =>{
this.messageList = messages;
});
}

Finally, add the method to send a message to the room members:

// frontend/src/app/chat/chat.page.ts
sendMessage(){
this.chatService.sendMessage({text:this.chatMessage}).then(()=>{
this.chatMessage = "";
});
}

This is the full content of the src/app/chat/chat.page.ts file:

// frontend/src/app/chat/chat.page.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { ChatService } from '../chat.service';
import { User } from '../user';

@Component({
selector: 'app-chat',
templateUrl: './chat.page.html',
styleUrls: ['./chat.page.scss'],
})
export class ChatPage implements OnInit {
messageList: any[] = [];
chatMessage: string = "";
constructor(private router: Router, private chatService: ChatService) { }

ngOnInit() {
this.chatService.getMessages().subscribe(messages => {
this.messageList = messages;
});
}
sendMessage() {
this.chatService.sendMessage({ text: this.chatMessage }).then(() => {
this.chatMessage = "";
});
}
}

Now, open the src/app/chat/chat.page.html file and let’s create a chat UI. First, add the list for displaying messages inside <ion-content>:

// frontend/src/app/chat/chat.page.html
<ion-content padding>
<div class="container">
<div *ngFor="let msg of messageList" class="message left">
<img class="user-img" [src]="msg.sender.avatarURL" alt="" src="">
<div class="msg-detail">
<div class="msg-info">
<p>
{{msg.sender.name}}
</p>
</div>
<div class="msg-content">
<span class="triangle"></span>
<p class="line-breaker ">{{msg.text}}</p>
</div>
</div>
</div>
</div>
</ion-content>

We loop through messageList using the *ngFor directive and display each message text, and sender information like name and avatar.

Next, add a textarea to enter the user’s message and a button to send it below <ion-content>:

// frontend/src/app/chat/chat.page.html
<ion-footer no-border>
<div class="input-wrap">
<textarea #messageInput
placeholder="Enter your message!"
[(ngModel)]="chatMessage"
(keyup.enter)="sendMessage()">
</textarea>
<button ion-button clear icon-only item-right (click)="sendMessage()">
<ion-icon name="ios-send" ios="ios-send" md="md-send"></ion-icon>
</button>
</div>
</ion-footer>

We bind the sendMessage method to both the text-area enter and the button click events which allows users to send a message by either pressing Enter or clicking on the button.

Also change the text of the title and the color of the toolbar:

// frontend/src/app/chat/chat.page.html
<ion-header>
<ion-toolbar color="primary">
<ion-title>Chat Room</ion-title>
</ion-toolbar>
</ion-header>

This is the full content of the src/app/chat/chat.page.html file:

// frontend/src/app/chat/chat.page.html
<ion-header>
<ion-toolbar color="primary">
<ion-title>Chat Room</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<div class="container">
<div *ngFor="let msg of messageList" class="message left">
<img class="user-img" [src]="msg.sender.avatarURL" alt="" src="">
<div class="msg-detail">
<div class="msg-info">
<p>
{{msg.sender.name}}
</p>
</div>
<div class="msg-content">
<span class="triangle"></span>
<p class="line-breaker ">{{msg.text}}</p>
</div>
</div>
</div>
</div>
</ion-content>
<ion-footer no-border>
<div class="input-wrap">
<textarea #messageInput placeholder="Enter your message!" [(ngModel)]="chatMessage" (keyup.enter)="sendMessage()">
</textarea>
<button ion-button clear icon-only item-right (click)="sendMessage()">
<ion-icon name="ios-send" ios="ios-send" md="md-send"></ion-icon>
</button>
</div>
</ion-footer>

Next, open the src/app/chat/chat.page.scss file and add the following styles:

// frontend/src/app/chat/chat.page.scss
.input-wrap {
padding: 5px;
display: flex;
textarea {
flex: 3;
border: 0;
border-bottom: 1px #000;
border-style: solid;
}
button {
flex: 1;
}
}
ion-footer {
box-shadow: 0 0 4px rgba(0, 0, 0, 0.11);
background-color: #fff;
}
ion-content .scroll-content {
background-color: #f5f5f5;
}
.line-breaker {
white-space: pre-line;
}
.container {
.message {
position: relative;
padding: 7px 0;
.msg-content {
color: #343434;
background-color: #ddd;
float: left;
}
.user-img {
position: absolute;
border-radius: 45px;
width: 45px;
height: 45px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.36);
}
.msg-detail {
width: 100%;
padding-left: 60px;
display: inline-block;
p {
margin: 0;
}
.msg-info {
p {
font-size: .8em;
color: #888;
}
}
}
}
}

This is a screenshot of the chat UI:

Note: Please note that you first need to register by visiting localhost:8100/register where you need to enter your name, email and password. After registering you’ll be redirected to the /login page where you need to enter your email and password. If login is successful, you’ll be redirected to the home page where you have the START CHATTING button that you need to click on in order to navigate to the chat page.
Conclusion

In this tutorial, we’ve created a simple group chat app using Nest.js, Ionic 4 and Chatkit. You can also leverage Chatkit to allow private and direct chats between users by creating rooms that have only two members and you can allow users to create their own chat rooms and invite users they want and other features that you can read about from the docs.

You can find the source code for this part in this GitHub repository.


Originally published by Ahmed Bouchefra at https://pusher.com

Learn More

Learn Swift 4: From Beginner to Advanced

Build a Basic App with Spring Boot and JPA using PostgreSQL

Build a Video Chat App with ASP.NET Core, Angular

5 ways to build real-time apps with JavaScript

Full Stack Developers: Everything You Need to Know

Android Studio for beginners

Create and use private rooms in an Angular 7 chat app with Chatkit



Display Weather Information in a NativeScript With Angular Android and iOS Mobile App

Display Weather Information in a NativeScript With Angular Android and iOS Mobile App

In this tutorial, we're going to develop an application for Android and iOS with NativeScript.

In this tutorial, we're going to develop an application for Android and iOS with NativeScript.

I am a huge fan of NativeScript and have been for a long time. While HERE doesn't currently offer an official NativeScript SDK for building Android and iOS applications, it shouldn't stop you from using HERE with NativeScript because most APIs can be accessed over HTTP.

A few months ago, I had written a tutorial around using the HERE Weather API with Angular which many of you found very useful. To make this tutorial mobile friendly, I thought NativeScript would be a great candidate.

In this tutorial, we're going to expand what we saw in the web version and take it to Android and iOS with NativeScript.

To get an idea of what we hope to accomplish, take a look at the following image:

Nothing too fancy is happening in the above picture. We are collecting the 7 day forecast and displaying it in a list with the appropriate weather icons. All of this data, including the icon, is returned as part of the API response.

To proceed with this tutorial, you will need to have NativeScript and Angular CLI installed and properly configured, along with a HERE Developer Portal account.

Create a New NativeScript With Angular Project

Assuming you've got NativeScript ready to go, we can create a new Angular templated project for use with this project. From the command line, execute the following command:

tns create here-weather-project --template tns-template-blank-ng

Since we'll be using HTTP to communicate with REST APIs, we won't need to install any special plugins. As we reach a certain point in the tutorial we will install a JavaScript library for time conversions, but it isn't necessary for our project and can wait.

Request Weather Forecast Information From the HERE Weather API

To be successful with the HERE Weather API, we'll need to make use of the the Angular HttpClient module. To do this, we need to make some changes to the project's src/app/app.module.ts file:

import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
@NgModule({
    bootstrap: [
        AppComponent
    ],
    imports: [
        NativeScriptModule,
        AppRoutingModule,
        HttpClientModule
    ],
    declarations: [
        AppComponent
    ],
    schemas: [
        NO_ERRORS_SCHEMA
    ]
})
export class AppModule { }

In the above code, notice that the HttpClientModule was imported and then added to the imports array of the @NgModule block. With it added, we can start using it in our components and services.

To keep this example simple, we will not be creating any new services or components. We're going to leverage the HomeComponent that should already exist as part of the NativeScript template.

Open the project's src/app/home/home.component.ts file and include the following:

import { Component, OnInit } from "@angular/core";
import { HttpClient, HttpParams } from '@angular/common/http';
import { map } from 'rxjs/operators';
@Component({
    moduleId: module.id,
    selector: "home",
    templateUrl: "./home.component.html"
})
export class HomeComponent implements OnInit {
    private appId: string;
    private appCode: string;
    public weather: any;
    public constructor(private http: HttpClient) { }
    public ngOnInit() { }
    public getWeather(coordinates: any) { }
}

Because we know we'll be using the HttpClient class, we need to import it along with the HttpParams class. We then need to start configuring each of our methods.

In the constructor method, we inject the HttpClient, but we also need to define our HERE application tokens:

public constructor(private http: HttpClient) {
    this.appId = "HERE-APP-ID";
    this.appCode = "HERE-APP-CODE";
}

Make sure to replace the actual tokens with those found in your HERE Developer Portal account. With the tokens defined, let's start creating the HTTP request to the HERE Weather API.

Within the file, make the getWeather method look like the following:

public getWeather(coordinates: any) {
    let params = new HttpParams({
        fromObject: {
            "product": "forecast_7days_simple",
            "latitude": coordinates.latitude,
            "longitude": coordinates.longitude,
            "app_id": this.appId,
            "app_code": this.appCode
        }
    });
    this.http.get("https://weather.cit.api.here.com/weather/1.0/report.json", { params: params })
        .pipe(map(result => (<any>result).dailyForecasts.forecastLocation))
        .subscribe(result => {
            this.weather = result.forecast;
        }, error => {
            console.error(error);
        });
}

The above code is where the magic happens. After being provided a set of latitude and longitude coordinates, we can construct the request with the parameters found in the documentation. These parameters also include our application tokens.

With the parameters properly formatted, we can make the request and store the response in the weathervariable. In our scenario, we only care about the forecastLocation information. There are plenty of other parameters and response data to look at as part of the HERE Weather API.

To execute this function when the component initializes, we can make use of the ngOnInit method:

public ngOnInit() {
    this.getWeather({ latitude: 37.7397, longitude: -121.4252 });
}

In the above example, we are requesting weather information for Tracy, CA. As of now we aren't displaying the information, only obtaining it. To display it, we can visit the project's src/app/home/home.component.html file:

<ActionBar class="action-bar">
    <Label class="action-bar-title" text="TRACY, CA"></Label>
</ActionBar>
<GridLayout class="page">
    <ListView [items]="weather" class="list-group">
        <ng-template let-forecast="item">
            <GridLayout class="list-group-item" rows="auto, auto" columns="50, *">
                <Image [src]="forecast.iconLink" rowSpan="2" row="0" col="0"></Image>
                <Label text="{{ forecast.highTemperature }}C" row="0" col="1" class="p-x-5"></Label>
                <Label text="{{ forecast.utcTime }}" row="1" col="1" class="p-x-5"></Label>
            </GridLayout>
        </ng-template>
    </ListView>
</GridLayout>

The above XML will create a nicely formatted list of data. The data will include the image, the temperature in Celsius, and the time. However, the time won't be formatted and the we may be more interested in Fahrenheit format for the temperature.

This is where we can start formatting our data.

Change the Response Format With Angular Pipes

There are plenty of ways to format our data, but in Angular, it might be best to create a pipe for our needs. We can actually create two pipes, one for the Fahrenheit conversion and one for the time formatting.

To do this, execute the following commands from within your project:

ng g pipe fahrenheit 
ng g pipe moment

The above commands will create two pipes. As you might have noticed, we'll be using Moment.js for our time conversions. This means we need to install it like mentioned earlier in the tutorial. To do this, execute the following from the command line:

npm install moment --save

We're going to start with our Celsius to Fahrenheit conversion. Open the project's src/app/fahrenheit.pipe.ts file and include the following:

import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
    name: 'fahrenheit'
})
export class FahrenheitPipe implements PipeTransform {
    public transform(value: any, args?: any): any {
        return ((value * (9 / 5)) + 32).toFixed(2);
    }
}

We're using a standard formula during our transformation to make this possible. Before we try to use this pipe, let's finish the MomentPipe as well. Open the project's src/app/moment.pipe.ts and include the following:

import { Pipe, PipeTransform } from '@angular/core';
import * as moment from "moment";
@Pipe({
name: 'moment'
})
export class MomentPipe implements PipeTransform {
    public transform(value: any, args?: any): any {
        return moment(value).format("MMMM DD, YYYY");
    }
}

There is one more step that must be taken before we can actually use the FahrenheitPipe and MomentPipepipes. We need to include them as part of our module. However, we aren't going to include them to our project's src/app/app.module.ts file. If any reference to these pipes exist there, go ahead and remove them. Instead, we're going to add them to the project's src/app/home/home.module.ts file like so:

import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptCommonModule } from "nativescript-angular/common";
import { HomeRoutingModule } from "./home-routing.module";
import { HomeComponent } from "./home.component";
import { FahrenheitPipe } from '../fahrenheit.pipe';
import { MomentPipe } from '../moment.pipe';
@NgModule({
    imports: [
        NativeScriptCommonModule,
        HomeRoutingModule
    ],
    declarations: [
        HomeComponent,
        FahrenheitPipe,
        MomentPipe
    ],
    schemas: [
        NO_ERRORS_SCHEMA
    ]
})
export class HomeModule { }

At this point in time we can actually use the two pipes in our application. Go back into the project's src/app/home/home.component.html file so we can make a small adjustment.

Take note of the following:

<GridLayout class="list-group-item" rows="auto, auto" columns="50, *">
    <Image [src]="forecast.iconLink" rowSpan="2" row="0" col="0"></Image>
    <Label text="{{ forecast.highTemperature }}C / {{ forecast.highTemperature | fahrenheit }}F" row="0" col="1" class="p-x-5"></Label>
    <Label text="{{ forecast.utcTime | moment }}" row="1" col="1" class="p-x-5"></Label>
</GridLayout>

In our GridLayout we are now piping the data returned from the API into the FahrenheitPipe and MomentPipepipes.

Again, not entirely necessary, but helpful long term for larger projects.

Conclusion

You just saw how to use the HERE Weather API in your NativeScript with Angular project. The HERE Weather API is much more involved than just a 7 day forecast, but for the simplicity of the example, that is the only data we decided to work with.

This tutorial was a followup to the weather with Angular tutorial I had written prior which was strictly for the web.

React Native Tutorial: SQLite Offline Android/iOS Mobile App

React Native Tutorial: SQLite Offline Android/iOS Mobile App

React Native Tutorial: SQLite Offline Android/iOS Mobile App

Table of Contents:
  • Install React App Creator and Create App
  • Add Navigation Header and required Screen
  • Install and Configure React Native SQLite Storage
  • Show List of Product
  • Show Product Details and Delete Product
  • Add Product
  • Edit Product
  • Run and Test React Native and SQLite Offline Mobile App

The following tools, frameworks, and modules are required for this tutorial:

  • Install React App Creator and Create App
  • Add Navigation Header and required Screen
  • Install and Configure React Native SQLite Storage
  • Show List of Product
  • Show Product Details and Delete Product
  • Add Product
  • Edit Product
  • Run and Test React Native and SQLite Offline Mobile App

Before start to the main steps, make sure that you have installed Node.js and can run npm in the terminal or command line. To check the existing or installed Node.js environment open the terminal/command line then type this command.

node -v
v10.15.1
npm -v
6.8.0
yarn -v
1.10.1

1. Install React App Creator and Create App

The Create React Native App is a tool for creating a React Native App. To install it, type this command in your App projects folder.

sudo npm install -g react-native-cli

Then create a React Native App using this command.

react-native init reactOffline

That command will create a React Native app then install all required modules. The app or project folder will contain these folders and files.

Next, go to the newly created React App folder.

cd reactSqlite

To run on iOS device or simulator run this command.

react-native run-ios

The simulator will open along with the new terminal window. Go to the project folder from the new terminal window then run this command.

react-native start

Right after iOS build success and React Native started, you will see this view in the iOS Simulator.

To run on Android device or simulator, make sure ADB detected the device.

adb devices
List of devices attached
J8AXGF0194047T6&nbsp;&nbsp; &nbsp;device

Next, type this command to run on the Android device or simulator.

react-native run-android

It will open the new terminal windows. Just go to the project folder then type this command.

react-native start

You will see this app in your Android device.

Sometimes, if running React Native app faster than starting React-Native Javascript bundler you see this red-screen of error.

No bundle URL present.

Make sure you’re running a packager server or have included a .jsbundle file in your application bundle.

RCTFatal
__28-[RCTCxxBridge handleError:]_block_invoke
_dispatch_call_block_and_release
_dispatch_client_callout
_dispatch_main_queue_callback_4CF
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
__CFRunLoopRun
CFRunLoopRunSpecific
GSEventRunModal
UIApplicationMain
main
start
0x0

Don’t worry, just start the Metro Bundler in the newly opened terminal window after you go to the project folder. After Metro Bundler started completely, refresh the React Native app on your device or simulator. In iOS Simulator you will see this error after refresh.

Attempting to reload bridge before it's valid: <RCTCxxBridge: 0x7ff34bc00510>. Try restarting the development server if connected.

-[RCTCxxBridge reload]
&nbsp; &nbsp; RCTCxxBridge.mm:986
-[RCTRedBox reloadFromRedBoxWindow:]
-[RCTRedBoxWindow reload]
-[UIApplication sendAction:to:from:forEvent:]
-[UIControl sendAction:to:forEvent:]
-[UIControl _sendActionsForEvents:withEvent:]
-[UIControl touchesEnded:withEvent:]
-[UIWindow _sendTouchesForEvent:]
-[UIWindow sendEvent:]
-[UIApplication sendEvent:]
__dispatchPreprocessedEventFromEventQueue
__handleEventQueueInternal
__handleEventQueueInternal
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
__CFRunLoopDoSources0
__CFRunLoopRun
CFRunLoopRunSpecific
GSEventRunModal
UIApplicationMain
main
start
0x0

Just reload again the React Native app, you will get your React Native app running.

2. Add Navigation Header and required Screen

Above generated React Native App just show blank app with plain text. Now, we will show you how to add the Navigation Header and Home Screen for your app. So, it will look like the Native App. In the terminal or command line, type this command to install React Navigation module and don’t forget to stop the running Metro Bundler before installing the modules.

yarn add react-navigation --save
yarn add react-native-gesture-handler --save
react-native link react-native-gesture-handler

Next, create a folder for components and components files in the root of the app folder.

mkdir components
touch components/ProductScreen.js
touch components/ProductDetailsScreen.js
touch components/ProductAddScreen.js
touch components/ProductEditScreen.js

Open and edit components/ProductScreen.js then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

export default class ProductScreen extends Component {
&nbsp; static navigationOptions = {
&nbsp; &nbsp; title: 'Product List',
&nbsp; };
&nbsp; render() {
&nbsp; &nbsp; return (
&nbsp; &nbsp; &nbsp; <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
&nbsp; &nbsp; &nbsp; &nbsp; <Text>Product List</Text>
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go to Details"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.navigate('ProductDetails')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go to Add Product"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.navigate('AddProduct')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go to Edit Product"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.navigate('EditProduct')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; );
&nbsp; }
}

Open and edit components/ProductDetailsScreen.js then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

export default class ProductDetailsScreen extends Component {
&nbsp; static navigationOptions = {
&nbsp; &nbsp; title: 'Product Details',
&nbsp; };
&nbsp; render() {
&nbsp; &nbsp; return (
&nbsp; &nbsp; &nbsp; <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
&nbsp; &nbsp; &nbsp; &nbsp; <Text>Product Details</Text>
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go to Details... again"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.push('ProductDetails')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go to Home"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.navigate('Product')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go back"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.goBack()}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; );
&nbsp; }
}

Open and edit components/ProductAddScreen.js then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

export default class ProductAddScreen extends Component {
&nbsp; static navigationOptions = {
&nbsp; &nbsp; title: 'Add Product',
&nbsp; };
&nbsp; render() {
&nbsp; &nbsp; return (
&nbsp; &nbsp; &nbsp; <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
&nbsp; &nbsp; &nbsp; &nbsp; <Text>Add Product</Text>
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go to Add Product... again"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.push('AddProduct')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go to Home"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.navigate('Product')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go back"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.goBack()}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; );
&nbsp; }
}

Open and edit components/ProductEditScreen.js then add this React codes.

import React, { Component } from 'react';
import { Button, View, Text } from 'react-native';

export default class ProductEditScreen extends Component {
&nbsp; static navigationOptions = {
&nbsp; &nbsp; title: 'Edit Product',
&nbsp; };
&nbsp; render() {
&nbsp; &nbsp; return (
&nbsp; &nbsp; &nbsp; <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
&nbsp; &nbsp; &nbsp; &nbsp; <Text>Add Product</Text>
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go to Edit Product... again"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.push('EditProduct')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go to Home"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.navigate('Product')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title="Go back"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.props.navigation.goBack()}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; );
&nbsp; }
}

Next, open and edit App.js then add replace all codes with this.

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { createAppContainer, createStackNavigator } from 'react-navigation';
import ProductScreen from './components/ProductScreen';
import ProductDetailsScreen from './components/ProductDetailsScreen';
import ProductAddScreen from './components/ProductAddScreen';
import ProductEditScreen from './components/ProductEditScreen';

const RootStack = createStackNavigator(
&nbsp; {
&nbsp; &nbsp; Product: ProductScreen,
&nbsp; &nbsp; ProductDetails: ProductDetailsScreen,
&nbsp; &nbsp; AddProduct: ProductAddScreen,
&nbsp; &nbsp; EditProduct: ProductEditScreen,
&nbsp; },
&nbsp; {
&nbsp; &nbsp; initialRouteName: 'Product',
&nbsp; &nbsp; navigationOptions: {
&nbsp; &nbsp; &nbsp; headerStyle: {
&nbsp; &nbsp; &nbsp; &nbsp; backgroundColor: '#777777',
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; &nbsp; headerTintColor: '#fff',
&nbsp; &nbsp; &nbsp; headerTitleStyle: {
&nbsp; &nbsp; &nbsp; &nbsp; fontWeight: 'bold',
&nbsp; &nbsp; &nbsp; },
&nbsp; &nbsp; },
&nbsp; },
);

const RootContainer = createAppContainer(RootStack);

export default class App extends React.Component {
&nbsp; render() {
&nbsp; &nbsp; return <RootContainer />;
&nbsp; }
}

const styles = StyleSheet.create({
&nbsp; container: {
&nbsp; &nbsp; flex: 1,
&nbsp; &nbsp; backgroundColor: '#fff',
&nbsp; &nbsp; alignItems: 'center',
&nbsp; &nbsp; justifyContent: 'center',
&nbsp; },
});

After Re-run the React Native app on the iOS/Android Device/Simulator you will see this updated views.

3. Install and Configure React Native SQLite Storage

Before creating an offline CRUD application using local data, we have to install the react-native-sqlite-storage and required UI/UX module.

yarn add react-native-sqlite-storage --save
yarn add react-native-elements --save
react-native link

We will use separate Class for accessing SQLite and do some CRUD (create, read, update, delete) operations. For that, create a new Javascript file on the root of the project folder.

touch Database.js

Open and edit Database.js then add this SQLite import with the configuration.

import SQLite from "react-native-sqlite-storage";
SQLite.DEBUG(true);
SQLite.enablePromise(true);

Add constant variable after that.

const database_name = "Reactoffline.db";
const database_version = "1.0";
const database_displayname = "SQLite React Offline Database";
const database_size = 200000;

Give this file a class name.

export default class Database {

}

Inside the class bracket, add a function for Database initialization that creates Database, tables, etc.

initDB() {
&nbsp; let db;
&nbsp; return new Promise((resolve) => {
&nbsp; &nbsp; console.log("Plugin integrity check ...");
&nbsp; &nbsp; SQLite.echoTest()
&nbsp; &nbsp; &nbsp; .then(() => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log("Integrity check passed ...");
&nbsp; &nbsp; &nbsp; &nbsp; console.log("Opening database ...");
&nbsp; &nbsp; &nbsp; &nbsp; SQLite.openDatabase(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; database_name,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; database_version,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; database_displayname,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; database_size
&nbsp; &nbsp; &nbsp; &nbsp; )
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .then(DB => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; db = DB;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log("Database OPEN");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; db.executeSql('SELECT 1 FROM Product LIMIT 1').then(() => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log("Database is ready ... executing query ...");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }).catch((error) =>{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log("Received error: ", error);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log("Database not yet ready ... populating data");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; db.transaction((tx) => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tx.executeSql('CREATE TABLE IF NOT EXISTS Product (prodId, prodName, prodDesc, prodImage, prodPrice)');
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }).then(() => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log("Table created successfully");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }).catch(error => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log(error);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolve(db);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .catch(error => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log(error);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; .catch(error => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log("echoTest failed - plugin not functional");
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; });
};

Add a function for close Database connection.

closeDatabase(db) {
&nbsp; if (db) {
&nbsp; &nbsp; console.log("Closing DB");
&nbsp; &nbsp; db.close()
&nbsp; &nbsp; &nbsp; .then(status => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log("Database CLOSED");
&nbsp; &nbsp; &nbsp; })
&nbsp; &nbsp; &nbsp; .catch(error => {
&nbsp; &nbsp; &nbsp; &nbsp; this.errorCB(error);
&nbsp; &nbsp; &nbsp; });
&nbsp; } else {
&nbsp; &nbsp; console.log("Database was not OPENED");
&nbsp; }
};

Add a function to get the list of products.

listProduct() {
&nbsp; return new Promise((resolve) => {
&nbsp; &nbsp; const products = [];
&nbsp; &nbsp; this.initDB().then((db) => {
&nbsp; &nbsp; &nbsp; db.transaction((tx) => {
&nbsp; &nbsp; &nbsp; &nbsp; tx.executeSql('SELECT p.prodId, p.prodName, p.prodImage FROM Product p', []).then(([tx,results]) => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log("Query completed");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var len = results.rows.length;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (let i = 0; i < len; i++) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let row = results.rows.item(i);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log(`Prod ID: ${row.prodId}, Prod Name: ${row.prodName}`)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const { prodId, prodName, prodImage } = row;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; products.push({
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prodId,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prodName,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prodImage
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log(products);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolve(products);
&nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; }).then((result) => {
&nbsp; &nbsp; &nbsp; &nbsp; this.closeDatabase(db);
&nbsp; &nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; });
&nbsp; }); &nbsp;
}

Add a function to get Product by ID.

productById(id) {
&nbsp; console.log(id);
&nbsp; return new Promise((resolve) => {
&nbsp; &nbsp; this.initDB().then((db) => {
&nbsp; &nbsp; &nbsp; db.transaction((tx) => {
&nbsp; &nbsp; &nbsp; &nbsp; tx.executeSql('SELECT * FROM Product WHERE prodId = ?', [id]).then(([tx,results]) => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log(results);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(results.rows.length > 0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let row = results.rows.item(0);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolve(row);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; }).then((result) => {
&nbsp; &nbsp; &nbsp; &nbsp; this.closeDatabase(db);
&nbsp; &nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; });
&nbsp; }); &nbsp;
}

Add a function to save a new product to the SQLite database.

addProduct(prod) {
&nbsp; return new Promise((resolve) => {
&nbsp; &nbsp; this.initDB().then((db) => {
&nbsp; &nbsp; &nbsp; db.transaction((tx) => {
&nbsp; &nbsp; &nbsp; &nbsp; tx.executeSql('INSERT INTO Product VALUES (?, ?, ?, ?, ?)', [prod.prodId, prod.prodName, prod.prodDesc, prod.prodImage, prod.prodPrice]).then(([tx, results]) => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolve(results);
&nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; }).then((result) => {
&nbsp; &nbsp; &nbsp; &nbsp; this.closeDatabase(db);
&nbsp; &nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; });
&nbsp; }); &nbsp;
}

Add a function to update a product.

updateProduct(id, prod) {
&nbsp; return new Promise((resolve) => {
&nbsp; &nbsp; this.initDB().then((db) => {
&nbsp; &nbsp; &nbsp; db.transaction((tx) => {
&nbsp; &nbsp; &nbsp; &nbsp; tx.executeSql('UPDATE Product SET prodName = ?, prodDesc = ?, prodImage = ?, prodPrice = ? WHERE prodId = ?', [prod.prodName, prod.prodDesc, prod.prodImage, prod.prodPrice, id]).then(([tx, results]) => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolve(results);
&nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; }).then((result) => {
&nbsp; &nbsp; &nbsp; &nbsp; this.closeDatabase(db);
&nbsp; &nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; });
&nbsp; }); &nbsp;
}

Add a function to delete a product.

deleteProduct(id) {
&nbsp; return new Promise((resolve) => {
&nbsp; &nbsp; this.initDB().then((db) => {
&nbsp; &nbsp; &nbsp; db.transaction((tx) => {
&nbsp; &nbsp; &nbsp; &nbsp; tx.executeSql('DELETE FROM Product WHERE prodId = ?', [id]).then(([tx, results]) => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; console.log(results);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resolve(results);
&nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; }).then((result) => {
&nbsp; &nbsp; &nbsp; &nbsp; this.closeDatabase(db);
&nbsp; &nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; });
&nbsp; }); &nbsp;
}

4. Show List of Product

To show or display the list of product, open and edit components/ProductScreen.js then replace all imports with these imports.

import React, { Component } from 'react';
import { StyleSheet, FlatList, ActivityIndicator, View, Text } from 'react-native';
import { ListItem, Button } from 'react-native-elements';
import Database from '../Database';

Instantiate the Database as a constant variable before the class name.

const db = new Database();

Next, replace navigationOptions with these.

static navigationOptions = ({ navigation }) => {
&nbsp; return {
&nbsp; &nbsp; title: 'Product List',
&nbsp; &nbsp; headerRight: (
&nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; buttonStyle={{ padding: 0, backgroundColor: 'transparent' }}
&nbsp; &nbsp; &nbsp; &nbsp; icon={{ name: 'add-circle', style: { marginRight: 0, fontSize: 28 } }}
&nbsp; &nbsp; &nbsp; &nbsp; onPress={() => {&nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; navigation.navigate('AddProduct', {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onNavigateBack: this.handleOnNavigateBack
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });&nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; }}
&nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; ),
&nbsp; };
};

Add a constructor function.

constructor() {
&nbsp; super();
&nbsp; this.state = {
&nbsp; &nbsp; isLoading: true,
&nbsp; &nbsp; products: [],
&nbsp; &nbsp; notFound: 'Products not found.\nPlease click (+) button to add it.'
&nbsp; };
}

Add a function to initialize the screen.

componentDidMount() {
&nbsp; this._subscribe = this.props.navigation.addListener('didFocus', () => {
&nbsp; &nbsp; this.getProducts();
&nbsp; });
}

Add a function to get the product list from Database class.

getProducts() {
&nbsp; let products = [];
&nbsp; db.listProduct().then((data) => {
&nbsp; &nbsp; products = data;
&nbsp; &nbsp; this.setState({
&nbsp; &nbsp; &nbsp; products,
&nbsp; &nbsp; &nbsp; isLoading: false,
&nbsp; &nbsp; });
&nbsp; }).catch((err) => {
&nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; this.setState = {
&nbsp; &nbsp; &nbsp; isLoading: false
&nbsp; &nbsp; }
&nbsp; })
}

Add a variable to iterate the listed product in the view.

keyExtractor = (item, index) => index.toString()

Add a function to render the List Item.

renderItem = ({ item }) => (
&nbsp; <ListItem
&nbsp; &nbsp; title={item.prodName}
&nbsp; &nbsp; leftAvatar={{
&nbsp; &nbsp; &nbsp; source: item.prodImage && { uri: item.prodImage },
&nbsp; &nbsp; &nbsp; title: item.prodName[0]
&nbsp; &nbsp; }}
&nbsp; &nbsp; onPress={() => {
&nbsp; &nbsp; &nbsp; this.props.navigation.navigate('ProductDetails', {
&nbsp; &nbsp; &nbsp; &nbsp; prodId: `${item.prodId}`,
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; }}
&nbsp; &nbsp; chevron
&nbsp; &nbsp; bottomDivider
&nbsp; />
)

Add a function to render the rest of List view.

render() {
&nbsp; if(this.state.isLoading){
&nbsp; &nbsp; return(
&nbsp; &nbsp; &nbsp; <View style={styles.activity}>
&nbsp; &nbsp; &nbsp; &nbsp; <ActivityIndicator size="large" color="#0000ff"/>
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; )
&nbsp; }
&nbsp; if(this.state.products.length === 0){
&nbsp; &nbsp; return(
&nbsp; &nbsp; &nbsp; <View>
&nbsp; &nbsp; &nbsp; &nbsp; <Text style={styles.message}>{this.state.notFound}</Text>
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; )
&nbsp; }
&nbsp; return (
&nbsp; &nbsp; <FlatList
&nbsp; &nbsp; &nbsp; keyExtractor={this.keyExtractor}
&nbsp; &nbsp; &nbsp; data={this.state.products}
&nbsp; &nbsp; &nbsp; renderItem={this.renderItem}
&nbsp; &nbsp; />
&nbsp; );
}

Finally, add a stylesheet for the whole screen after the class bracket.

const styles = StyleSheet.create({
&nbsp; container: {
&nbsp; &nbsp;flex: 1,
&nbsp; &nbsp;paddingBottom: 22
&nbsp; },
&nbsp; item: {
&nbsp; &nbsp; padding: 10,
&nbsp; &nbsp; fontSize: 18,
&nbsp; &nbsp; height: 44,
&nbsp; },
&nbsp; activity: {
&nbsp; &nbsp; position: 'absolute',
&nbsp; &nbsp; left: 0,
&nbsp; &nbsp; right: 0,
&nbsp; &nbsp; top: 0,
&nbsp; &nbsp; bottom: 0,
&nbsp; &nbsp; alignItems: 'center',
&nbsp; &nbsp; justifyContent: 'center'
&nbsp; },
&nbsp; message: {
&nbsp; &nbsp; padding: 16,
&nbsp; &nbsp; fontSize: 18,
&nbsp; &nbsp; color: 'red'
&nbsp; }
});

5. Show Product Details and Delete Product

From the list of product view, you will see that list item has an action button to show the product details. Next, open and edit components/ProductDetailsScreen.js then replace the imports with these imports.

import React, { Component } from 'react';
import { ScrollView, StyleSheet, Image, ActivityIndicator, View, Text } from 'react-native';
import { Card, Button } from 'react-native-elements';
import Database from '../Database';

Instantiate the Database as a constant variable.

const db = new Database();

Add a function as the constructor.

constructor() {
&nbsp; super();
&nbsp; this.state = {
&nbsp; &nbsp; isLoading: true,
&nbsp; &nbsp; product: {},
&nbsp; &nbsp; id: '',
&nbsp; };
}

Add a function to initialize the screen.

componentDidMount() {
&nbsp; this._subscribe = this.props.navigation.addListener('didFocus', () => {
&nbsp; &nbsp; const { navigation } = this.props;
&nbsp; &nbsp; db.productById(navigation.getParam('prodId')).then((data) => {
&nbsp; &nbsp; &nbsp; console.log(data);
&nbsp; &nbsp; &nbsp; product = data;
&nbsp; &nbsp; &nbsp; this.setState({
&nbsp; &nbsp; &nbsp; &nbsp; product,
&nbsp; &nbsp; &nbsp; &nbsp; isLoading: false,
&nbsp; &nbsp; &nbsp; &nbsp; id: product.prodId
&nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; }).catch((err) => {
&nbsp; &nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; &nbsp; this.setState = {
&nbsp; &nbsp; &nbsp; &nbsp; isLoading: false
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; })
&nbsp; });
}

Add a function to delete a product data.

deleteProduct(id) {
&nbsp; const { navigation } = this.props;
&nbsp; this.setState({
&nbsp; &nbsp; isLoading: true
&nbsp; });
&nbsp; db.deleteProduct(id).then((result) => {
&nbsp; &nbsp; console.log(result);
&nbsp; &nbsp; this.props.navigation.goBack();
&nbsp; }).catch((err) => {
&nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; this.setState = {
&nbsp; &nbsp; &nbsp; isLoading: false
&nbsp; &nbsp; }
&nbsp; })
}

Add a function to render the whole Product Details view.

render() {
&nbsp; if(this.state.isLoading){
&nbsp; &nbsp; return(
&nbsp; &nbsp; &nbsp; <View style={styles.activity}>
&nbsp; &nbsp; &nbsp; &nbsp; <ActivityIndicator size="large" color="#0000ff" />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; )
&nbsp; }
&nbsp; return (
&nbsp; &nbsp; <ScrollView>
&nbsp; &nbsp; &nbsp; <Card style={styles.container}>
&nbsp; &nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <View>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Image
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; style={{width: 150, height: 150}}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; source={{uri: this.state.product.prodImage}}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <View>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Text style={{fontSize: 16}}>Product ID: {this.state.product.prodId}</Text>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <View>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Text style={{fontSize: 16}}>Product Name: {this.state.product.prodName}</Text>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <View>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Text style={{fontSize: 16}}>Product Desc: {this.state.product.prodDesc}</Text>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <View>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Text style={{fontSize: 16}}>Product Price: {this.state.product.prodPrice}</Text>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; &nbsp; <View style={styles.detailButton}>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; large
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; backgroundColor={'#CCCCCC'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; leftIcon={{name: 'edit'}}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title='Edit'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; this.props.navigation.navigate('EditProduct', {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; prodId: `${this.state.id}`,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; });
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }} />
&nbsp; &nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; &nbsp; <View style={styles.detailButton}>
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; large
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; backgroundColor={'#999999'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; color={'#FFFFFF'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; leftIcon={{name: 'delete'}}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title='Delete'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.deleteProduct(this.state.id)} />
&nbsp; &nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; </Card>
&nbsp; &nbsp; </ScrollView>
&nbsp; );
}

Finally, add the stylesheet for this screen after the class bracket.

const styles = StyleSheet.create({
&nbsp; container: {
&nbsp; &nbsp; flex: 1,
&nbsp; &nbsp; padding: 20
&nbsp; },
&nbsp; subContainer: {
&nbsp; &nbsp; flex: 1,
&nbsp; &nbsp; paddingBottom: 20,
&nbsp; &nbsp; borderBottomWidth: 2,
&nbsp; &nbsp; borderBottomColor: '#CCCCCC',
&nbsp; },
&nbsp; activity: {
&nbsp; &nbsp; position: 'absolute',
&nbsp; &nbsp; left: 0,
&nbsp; &nbsp; right: 0,
&nbsp; &nbsp; top: 0,
&nbsp; &nbsp; bottom: 0,
&nbsp; &nbsp; alignItems: 'center',
&nbsp; &nbsp; justifyContent: 'center'
&nbsp; },
&nbsp; detailButton: {
&nbsp; &nbsp; marginTop: 10
&nbsp; }
})

6. Add Product

To add or save a new Product, open and edit the components/ProductAddScreen.js then replace all imports with these imports.

import React, { Component } from 'react';
import { StyleSheet, ScrollView, ActivityIndicator, View, TextInput } from 'react-native';
import { Button } from 'react-native-elements';
import Database from '../Database';

Instantiate the Database as a constant variable.

const db = new Database();

Add a constructor inside the class bracket after the navigationOptions.

constructor() {
&nbsp; super();
&nbsp; this.state = {
&nbsp; &nbsp; prodId: '',
&nbsp; &nbsp; prodName: '',
&nbsp; &nbsp; prodDesc: '',
&nbsp; &nbsp; prodImage: '',
&nbsp; &nbsp; prodPrice: '0',
&nbsp; &nbsp; isLoading: false,
&nbsp; };
}

Add a function to update the input text values.

updateTextInput = (text, field) => {
&nbsp; const state = this.state
&nbsp; state[field] = text;
&nbsp; this.setState(state);
}

Add a function to save a product to the SQLite table.

saveProduct() {
&nbsp; this.setState({
&nbsp; &nbsp; isLoading: true,
&nbsp; });
&nbsp; let data = {
&nbsp; &nbsp; prodId: this.state.prodId,
&nbsp; &nbsp; prodName: this.state.prodName,
&nbsp; &nbsp; prodDesc: this.state.prodDesc,
&nbsp; &nbsp; prodImage: this.state.prodImage,
&nbsp; &nbsp; prodPrice: this.state.prodPrice
&nbsp; }
&nbsp; db.addProduct(data).then((result) => {
&nbsp; &nbsp; console.log(result);
&nbsp; &nbsp; this.setState({
&nbsp; &nbsp; &nbsp; isLoading: false,
&nbsp; &nbsp; });
&nbsp; &nbsp; this.props.navigation.state.params.onNavigateBack;
&nbsp; &nbsp; this.props.navigation.goBack();
&nbsp; }).catch((err) => {
&nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; this.setState({
&nbsp; &nbsp; &nbsp; isLoading: false,
&nbsp; &nbsp; });
&nbsp; })
}

Add a function to render the whole add product view.

render() {
&nbsp; if(this.state.isLoading){
&nbsp; &nbsp; return(
&nbsp; &nbsp; &nbsp; <View style={styles.activity}>
&nbsp; &nbsp; &nbsp; &nbsp; <ActivityIndicator size="large" color="#0000ff"/>
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; )
&nbsp; }
&nbsp; return (
&nbsp; &nbsp; <ScrollView style={styles.container}>
&nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; <TextInput
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; placeholder={'Product ID'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value={this.state.prodId}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onChangeText={(text) => this.updateTextInput(text, 'prodId')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; <TextInput
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; placeholder={'Product Name'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value={this.state.prodName}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onChangeText={(text) => this.updateTextInput(text, 'prodName')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; <TextInput
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; multiline={true}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; numberOfLines={4}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; placeholder={'Product Description'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value={this.state.prodDesc}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onChangeText={(text) => this.updateTextInput(text, 'prodDesc')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; <TextInput
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; placeholder={'Product Image'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value={this.state.prodImage}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onChangeText={(text) => this.updateTextInput(text, 'prodImage')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; <TextInput
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; placeholder={'Product Price'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value={this.state.prodPrice}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; keyboardType='numeric'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onChangeText={(text) => this.updateTextInput(text, 'prodPrice')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; <View style={styles.button}>
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; large
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; leftIcon={{name: 'save'}}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title='Save'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.saveProduct()} />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; </ScrollView>
&nbsp; );
}

Finally, add the style for the whole screen.

const styles = StyleSheet.create({
&nbsp; container: {
&nbsp; &nbsp; flex: 1,
&nbsp; &nbsp; padding: 20
&nbsp; },
&nbsp; subContainer: {
&nbsp; &nbsp; flex: 1,
&nbsp; &nbsp; marginBottom: 20,
&nbsp; &nbsp; padding: 5,
&nbsp; &nbsp; borderBottomWidth: 2,
&nbsp; &nbsp; borderBottomColor: '#CCCCCC',
&nbsp; },
&nbsp; activity: {
&nbsp; &nbsp; position: 'absolute',
&nbsp; &nbsp; left: 0,
&nbsp; &nbsp; right: 0,
&nbsp; &nbsp; top: 0,
&nbsp; &nbsp; bottom: 0,
&nbsp; &nbsp; alignItems: 'center',
&nbsp; &nbsp; justifyContent: 'center'
&nbsp; }
})

7. Edit Product

To edit a product, open and edit components/ProductEditScreen.js then replace all imports with these imports.

import React, { Component } from 'react';
import { StyleSheet, ScrollView, ActivityIndicator, View, TextInput } from 'react-native';
import { Button } from 'react-native-elements';
import Database from '../Database';

Instantiate the Database as a constant variable.

const db = new Database();

Add the constructor after the navigationOptions function.

constructor() {
&nbsp; super();
&nbsp; this.state = {
&nbsp; &nbsp; prodId: '',
&nbsp; &nbsp; prodName: '',
&nbsp; &nbsp; prodDesc: '',
&nbsp; &nbsp; prodImage: '',
&nbsp; &nbsp; prodPrice: '0',
&nbsp; &nbsp; isLoading: true,
&nbsp; };
}

Add a function to initialize the screen that will get product data.

componentDidMount() {
&nbsp; const { navigation } = this.props;
&nbsp; db.productById(navigation.getParam('prodId')).then((data) => {
&nbsp; &nbsp; console.log(data);
&nbsp; &nbsp; const product = data;
&nbsp; &nbsp; this.setState({
&nbsp; &nbsp; &nbsp; prodId: product.prodId,
&nbsp; &nbsp; &nbsp; prodName: product.prodName,
&nbsp; &nbsp; &nbsp; prodDesc: product.prodDesc,
&nbsp; &nbsp; &nbsp; prodImage: product.prodImage,
&nbsp; &nbsp; &nbsp; prodPrice: product.prodPrice,
&nbsp; &nbsp; &nbsp; isLoading: false,
&nbsp; &nbsp; });
&nbsp; }).catch((err) => {
&nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; this.setState = {
&nbsp; &nbsp; &nbsp; isLoading: false
&nbsp; &nbsp; }
&nbsp; })
}

Add a function to update the input text value.

updateTextInput = (text, field) => {
&nbsp; const state = this.state
&nbsp; state[field] = text;
&nbsp; this.setState(state);
}

Add a function to update the product data.

updateProduct() {
&nbsp; this.setState({
&nbsp; &nbsp; isLoading: true,
&nbsp; });
&nbsp; const { navigation } = this.props;
&nbsp; let data = {
&nbsp; &nbsp; prodId: this.state.prodId,
&nbsp; &nbsp; prodName: this.state.prodName,
&nbsp; &nbsp; prodDesc: this.state.prodDesc,
&nbsp; &nbsp; prodImage: this.state.prodImage,
&nbsp; &nbsp; prodPrice: this.state.prodPrice
&nbsp; }
&nbsp; db.updateProduct(data.prodId, data).then((result) => {
&nbsp; &nbsp; console.log(result);
&nbsp; &nbsp; this.setState({
&nbsp; &nbsp; &nbsp; isLoading: false,
&nbsp; &nbsp; });
&nbsp; &nbsp; this.props.navigation.state.params.onNavigateBack;
&nbsp; &nbsp; this.props.navigation.goBack();
&nbsp; }).catch((err) => {
&nbsp; &nbsp; console.log(err);
&nbsp; &nbsp; this.setState({
&nbsp; &nbsp; &nbsp; isLoading: false,
&nbsp; &nbsp; });
&nbsp; })
}

Add a function to render the whole Edit Product screen.

render() {
&nbsp; if(this.state.isLoading){
&nbsp; &nbsp; return(
&nbsp; &nbsp; &nbsp; <View style={styles.activity}>
&nbsp; &nbsp; &nbsp; &nbsp; <ActivityIndicator size="large" color="#0000ff"/>
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; )
&nbsp; }
&nbsp; return (
&nbsp; &nbsp; <ScrollView style={styles.container}>
&nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; <TextInput
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; placeholder={'Product ID'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value={this.state.prodId}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onChangeText={(text) => this.updateTextInput(text, 'prodId')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; <TextInput
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; placeholder={'Product Name'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value={this.state.prodName}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onChangeText={(text) => this.updateTextInput(text, 'prodName')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; <TextInput
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; multiline={true}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; numberOfLines={4}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; placeholder={'Product Description'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value={this.state.prodDesc}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onChangeText={(text) => this.updateTextInput(text, 'prodDesc')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; <TextInput
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; placeholder={'Product Image'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value={this.state.prodImage}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onChangeText={(text) => this.updateTextInput(text, 'prodImage')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; <View style={styles.subContainer}>
&nbsp; &nbsp; &nbsp; &nbsp; <TextInput
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; placeholder={'Product Price'}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; value={this.state.prodPrice}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; keyboardType='numeric'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onChangeText={(text) => this.updateTextInput(text, 'prodPrice')}
&nbsp; &nbsp; &nbsp; &nbsp; />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; &nbsp; <View style={styles.button}>
&nbsp; &nbsp; &nbsp; &nbsp; <Button
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; large
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; leftIcon={{name: 'save'}}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; title='Save'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; onPress={() => this.updateProduct()} />
&nbsp; &nbsp; &nbsp; </View>
&nbsp; &nbsp; </ScrollView>
&nbsp; );
}

Finally, add the stylesheet after the class bracket.

const styles = StyleSheet.create({
&nbsp; container: {
&nbsp; &nbsp; flex: 1,
&nbsp; &nbsp; padding: 20
&nbsp; },
&nbsp; subContainer: {
&nbsp; &nbsp; flex: 1,
&nbsp; &nbsp; marginBottom: 20,
&nbsp; &nbsp; padding: 5,
&nbsp; &nbsp; borderBottomWidth: 2,
&nbsp; &nbsp; borderBottomColor: '#CCCCCC',
&nbsp; },
&nbsp; activity: {
&nbsp; &nbsp; position: 'absolute',
&nbsp; &nbsp; left: 0,
&nbsp; &nbsp; right: 0,
&nbsp; &nbsp; top: 0,
&nbsp; &nbsp; bottom: 0,
&nbsp; &nbsp; alignItems: 'center',
&nbsp; &nbsp; justifyContent: 'center'
&nbsp; }
})

8. Run and Test React Native and SQLite Offline Mobile App

As we show you at the first step, run the React Native and SQLite app using this command.

react-native run-android
react-native run-ios

After the new terminal window open, just go to the project folder then run this command.

react-native start

Now, you will see the whole application in the Android/iOS Device.

That it’s, the React Native and SQLite Offline Mobile App. You can get the full source code from our GitHub.

Build Android/iOS Mobile Apps with Capacitor, React.js and Ionic 4

Build Android/iOS Mobile Apps with Capacitor, React.js and Ionic 4

In this artilce, you'll learn how to build Android/iOS Mobile Apps using Ionic 4, React.js, and Capacitor

In this tutorial, we will combine the Ionic 4 component in React.js React Hooks then build to the Native Android/iOS using Capacitor. If you are new to React.js and React Hooks you can read the documentation here. We are using Capacitor for the build to Android/iOS because Cordova did not support this combination of Ionic 4 and React.js.The Android/iOS mobile apps that we will build is very simple. Just show the list of data that get from the RESTful API using the Axios library. The RESTful API contains the nested array of JSON, so, using React Hooks will be very tricky. The Axios used by React.js and Vue.js to handle the HTTP request to the RESTful API.

So, you will learn how to create the new Ionic 4 application with the type of React, using Ionic 4 components in the React Hooks, populate nested JSON array in React Hooks, and build the native Android/iOS using Capacitor.

The following libraries, frameworks, tools, and modules are required for this tutorial:

Before we move to the main steps, check the Node.js version for the latest and recommended version. Type this command in the terminal or command line.

node -v 
npm -v

Create a New Ionic 4 React Application

Use the latest Ionic 4 version to create an Ionic 4 application with the type of React. Type this command in the terminal to update the Ionic CLI to the latest version.

sudo npm install -g ionic capacitor

Next, create a new Ionic 4 application with the type of React using this command.

ionic start ionic-react tabs --type=react

If you have a problem in installing the modules using [NPM](https://morioh.com/p/9c65c77b47e0 "NPM") you can use Yarn for that. Go to the newly created Ionic 4 React Application then type this command to install the module.

cd ./ionic-react 
yarn install

Next, run the Ionic 4 React for the first time using this command.

ionic serve

Then the browser will open automatically to display this Ionic 4 React Screen.

Fetch Data from RESTful API and Display as List

As mention above, we will use Axios to fetch data from RESTful API. To install the Axios library/module, simply run this command.

npm install --save axios

Fetch data and display as a list will do in Ionic 4 React Tab 1. For that, open and edit src/pages/Tab1.tsx then replace all imports with these.

import {

  IonList,

  IonItem,

  IonContent,

  IonHeader,

  IonTitle,

  IonToolbar,

  IonLabel,

  IonAvatar,

  IonLoading

  } from '@ionic/react';

import React, { useState, useEffect } from 'react';

import './Tab1.css';

import axios from 'axios';

One of the problem while using React Hooks is JSON data that fetched from the RESTful API not easily can display to the Ionic 4 React component. It must return as a type which we will create an interface to handle that. Next, add these lines of interface codes before the React.FunctionComponent.

interface ITeam {
  name: string;
  code: string;
}
 
interface IMatches {
  num: number;
  date: string;
  time: string;
  team1: ITeam;
  team2: ITeam;
}
 
interface IRounds {
  name: string;
  matches: Array<IMatches>;
}

As you see, there are three interfaces that related each other because we will fetch the nested arrays from the RESTful API. Next, add the props parameter to the Tab1 React.FunctionComponent.

const Tab1: React.FunctionComponent = (props) => { 
... 
}

Next, declare the constant variables to handle data that fetched from the RESTful API and handle the loading spinner by adding these lines inside the top of Tab1 React.FunctionComponent.

const [data, setData] = useState<IRounds[]>([]); 
const [showLoading, setShowLoading] = useState(true);

To fetch data from the RESTful API using Axios, we have to do that by calling **useEffect** function that comes with React Hooks. Add these lines of **useEffect** function after the constant variable.

useEffect(() => {
  const fetchData = async () => {
    const result = await axios(
      'https://raw.githubusercontent.com/openfootball/world-cup.json/master/2018/worldcup.json',
    );
    setData(result.data.rounds);
    setShowLoading(false);
  };

  fetchData();
}, []);

Add this function to go to details page included with parameter from this Tab1 page.

const showDetail = (data: any) => {
  let prop: any = props;
  prop.history.push({
    pathname: '/tab1/details/' + JSON.stringify(data)
  })
}

As you see, the parameter data is all selected data from the list that converted to the string. Next, modify the Ionic 4 React component to display the list of data that contains the nested JSON arrays.

return (
  <>
    <IonHeader>
      <IonToolbar>
        <IonTitle>World Cup 2018</IonTitle>
      </IonToolbar>
    </IonHeader>
    <IonContent>
      <IonLoading
        isOpen={showLoading}
        onDidDismiss={() => setShowLoading(false)}
        message={'Loading...'}
      />
      <IonList>
        {data.map((round, idx) => (
          <IonItem key={idx} onClick={() => { showDetail(round) }}>
            <IonAvatar slot="start">
              <img src="assets/imgs/ball.png" alt="ball" />
            </IonAvatar>
            <IonLabel>
              <h2>{round.name}</h2>
              {round.matches.map((im, idx2) => (
                <p key={idx2}>
                  <span>{im.date} {im.time}: {im.team1.name} vs {im.team2.name}</span>
                </p>
              ))}
            </IonLabel>
          </IonItem>
        ))}
      </IonList>
    </IonContent>
  </>
);

Display the Details of Data

We will use existing Details page that linked to the Tab2. For that, we have to modify the Details page and Tabs to move the Details page linked to the Tab1. Open and edit src/App.tsx then modify the Ionic 4 React IonRouterOutlet to be like this.

<IonRouterOutlet>
  <Route path="/:tab(tab1)" component={Tab1} exact={true} />
  <Route path="/:tab(tab1)/details/:data" component={Details} />
  <Route path="/:tab(tab2)" component={Tab2} exact={true} />
  <Route path="/:tab(tab3)" component={Tab3} />
  <Route exact path="/" render={() => <Redirect to="/tab1" />} />
</IonRouterOutlet>

Now, the Details page is part of the Tab1 with the additional parameter of data that send from the Tab1 list of data. Next, make a little title and icon modification of the Tabs by modifying the Tabs codes.

<IonTabBar slot="bottom">
  <IonTabButton tab="schedule" href="/tab1">
    <IonIcon icon={aperture} />
    <IonLabel>Matchdays</IonLabel>
  </IonTabButton>
  <IonTabButton tab="speakers" href="/tab2">
    <IonIcon icon={apps} />
    <IonLabel>Tab Two</IonLabel>
  </IonTabButton>
  <IonTabButton tab="map" href="/tab3">
    <IonIcon icon={send} />
    <IonLabel>Tab Three</IonLabel>
  </IonTabButton>
</IonTabBar>

Next, open and edit the src/pages/Details.tsx then replace all imports with these.

import React, { useState, useEffect } from 'react';
import {
  IonBackButton,
  IonButtons,
  IonHeader,
  IonToolbar,
  IonTitle,
  IonContent,
  IonGrid,
  IonRow,
  IonCol,
  IonLabel,
  IonList,
  IonItem } from '@ionic/react';

Add the props param to the Details React.FunctionComponent.

const Details: React.FunctionComponent = (props) => { 
... 
}

Declare the variables to convert props, match, and params.data as the any types. This method is different than the previous method in the list of data that convert a JSON array to the interface.

let prop: any = props; 
let match: any = prop.match; 
let data: any = JSON.parse(match.params.data);

Next, we have to extract the data from params to the Ionic 4 React components.

return (
  <>
      <IonHeader>
        <IonToolbar>
          <IonButtons slot="start">
            <IonBackButton defaultHref="/tab1" />
          </IonButtons>
          <IonTitle>{data.name}</IonTitle>
        </IonToolbar>
      </IonHeader>
    <IonContent>
      {data.matches.map((m: any, idx: number) => (
        <IonList key={idx} lines="none">
          <IonItem>
            <IonLabel>
              <IonGrid>
                <IonRow>
                  <IonCol><p>{m.date}</p></IonCol>
                </IonRow>
                <IonRow>
                  <IonCol>{m.stadium.name}, {m.city}</IonCol>
                </IonRow>
                <IonRow>
                  <IonCol><h2>{m.group}</h2></IonCol>
                </IonRow>
                <IonRow>
                  <IonCol size="5"><b>{m.team1.name} ({m.score1})</b></IonCol>
                  <IonCol size="2">vs</IonCol>
                  <IonCol size="5"><b>({m.score2}) {m.team2.name}</b></IonCol>
                </IonRow>
                <IonRow>
                  <IonCol>
                    {m.goals1.map((g1: any, gidx1: number) => (
                      <p key={gidx1}>{g1.name} `{g1.minute}</p>
                    ))}
                  </IonCol>
                  <IonCol>&nbsp;</IonCol>
                  <IonCol>
                    {m.goals2.map((g2: any, gidx2: number) => (
                      <p key={gidx2}>{g2.name} `{g2.minute}</p>
                    ))}
                  </IonCol>
                </IonRow>
              </IonGrid>
            </IonLabel>
          </IonItem>
        </IonList>
      ))}
    </IonContent>
  </>
);

Run and Test The Ionic 4 React App to the Device using Capacitor

Before installing the Capacitor, we have to build the Ionic 4 React application first by type this command.

ionic build

Next, add the Capacitor Android platform using this command.

ionic capacitor add android

Next, type this command to build and open the Android Studio.

ionic capacitor run android

Now, you can run the Ionic 4 React application to the Android Device using Android Studio. And here the Ionic 4 React application looks like in the Android device.

Next, to run in the iOS device type this command first.

ionic capacitor add ios

If you get the error as below.

✖ Updating iOS native dependencies with "pod install" (may take several minutes): 
✖ update ios: 
[error] Error running update: [!] Unknown installation options: disable_input_output_paths.

To fix that error, update the Cocoapods first then remove the ios folder before running again Capacitor platform adds.

sudo gem install cocoapods -n /usr/local/bin 
rm -rf ios/ 
ionic capacitor add ios

Next, open the XCode using this command.

ionic capacitor open ios

After some setting for your Apple Account, you can run the iOS application inside your XCode to your iPhone device. Here's what we have in our iPhone device.

That it's, the Build Android/iOS Mobile Apps using Ionic 4 React.js Capacitor. You can find the full working source code from our GitHub.