Building an Ionic Geolocation Tracker using Google Maps

Building an Ionic Geolocation Tracker using Google Maps

This Ionic Geolocation Tracker tutorial explains how to build an Ionic Geolocation tracker using Google Maps. How to Build an Ionic Geolocation Tracker with Google Maps and Track Drawing. Building an Ionic Geolocation Tracker using Google Maps

Within your Ionic App you can easily track the position of your users with the Ionic Geolocation plugin. It is also easy to add a Google Map to your app, so why not combine these 2 features into a useful app?

In this tutorial we will build an Ionic Geolocation Tracker which can track the route of users, display the path the user has walked inside a Google map and finally also save those information to display previous runs. It’s gonna be fun!

Starting our Geolocation Tracker

We start by creating a blank new Ionic app and install the Geolocation plugin and also the SQLite storage so Ionic Storage uses a real database inside our final app. Right now we already add a variable to the Geolocation plugin but we need another fix for iOS later on as well:

ionic start devdacticLocationTracker blank
cd devdacticLocationTracker
ionic cordova plugin add cordova-plugin-geolocation --variable GEOLOCATION_USAGE_DESCRIPTION="To track your walks"
ionic cordova plugin add cordova-sqlite-storage
npm install --save @ionic-native/geolocation

As we want to use Google Maps we also need to load the SDK and therefore we can simply add this directly to our src/index.html right before the cordova import:

<script src="http://maps.google.com/maps/api/js?key=YOUR_API_KEY_HERE"></script>

Also, you need to insert your own key here (simply replace YOUR_API_KEY_HERE) but you can easily generate an API key by going to this page.

Now we also need to load our plugins, therefore make sure to import and connect them inside your app/app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
 
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
 
import { Geolocation } from '@ionic-native/geolocation';
import { IonicStorageModule } from '@ionic/storage';
 
@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicStorageModule.forRoot(),
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    Geolocation
  ]
})
export class AppModule {}

Finally, I already mentioned that we need some fix for iOS. If you build your app you will get a warning that you haven’t added the according information to your *.plist why you want to use the Geolocation, but you can simply add this little snippet to your config.xml which will always add the NSLocationWhenInUseUsageDescription when you build the iOS app:

<edit-config file="*-Info.plist" mode="merge" target="NSLocationWhenInUseUsageDescription">
    <string>This App wants to track your location</string>
</edit-config>
Basic View and Google Map Positions

We’ll split the next part into 2 sections, and we start with the basic view of our Google Map. Our view mainly consists of a button to start and stop our tracking, the actual Google map and also a list of previous routes which can be loaded once again into our map just lake you are used to it from your running apps!

Go ahead and start with the view by changing the pages/home/home.html:

<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      GPS Tracking
    </ion-title>
  </ion-navbar>
</ion-header>
 
<ion-content padding>
 
  <button ion-button full icon-left (click)="startTracking()" *ngIf="!isTracking">
      <ion-icon name="locate"></ion-icon>
      Start Tracking
    </button>
  <button ion-button full color="danger" icon-left (click)="stopTracking()" *ngIf="isTracking">
      <ion-icon name="hand"></ion-icon>
      Stop Tracking
  </button>
 
  <div #map id="map"></div>
 
  <ion-list>
    <ion-list-header>Previous Tracks</ion-list-header>
    <ion-item *ngFor="let route of previousTracks">
      {{ route.finished | date }}, {{ route.path.length }} Waypoints
      <button ion-button clear item-end (click)="showHistoryRoute(route.path)">View Route</button>
    </ion-item>
  </ion-list>
</ion-content>

Nothing fancy yet, the map is really just one element and we’ll do all the logic from inside the class but first we have to add some styling to make the map look good inside our app.

Therefore, add this bit of styling inside your pages/home/home.scss:

page-home {
    #map {
        width: 100%;
        height: 300px;
      }
}

We want to load the map on start of the app and also focus it on our current position so we will notice when the track drawing begins.

First of all we need a reference to our map inside the view using @ViewChild and then we can create a new map using new google.maps.Map with additional options. In this case we disable the different view options displayed on the map and pick the Roadmap style for the map.

We’ll also keep track of this new map variable as we need it later to draw our tracks!

After the map initialisation we also try to get the current position of the user with the geolocation plugin and if we get back the coordinates we can focus our map on this spot! We could also display a marker here but that’s left for the reader.

Go ahead and change your pages/home/home.ts to:

import { Component, ViewChild, ElementRef } from '@angular/core';
import { NavController, Platform } from 'ionic-angular';
import { Geolocation } from '@ionic-native/geolocation';
import { Subscription } from 'rxjs/Subscription';
import { filter } from 'rxjs/operators';
import { Storage } from '@ionic/storage';
 
declare var google;
 
@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  @ViewChild('map') mapElement: ElementRef;
  map: any;
  currentMapTrack = null;
 
  isTracking = false;
  trackedRoute = [];
  previousTracks = [];
 
  positionSubscription: Subscription;
 
  constructor(public navCtrl: NavController, private plt: Platform, private geolocation: Geolocation, private storage: Storage) { }
 
  ionViewDidLoad() {
    this.plt.ready().then(() => {
      this.loadHistoricRoutes();
 
      let mapOptions = {
        zoom: 13,
        mapTypeId: google.maps.MapTypeId.ROADMAP,
        mapTypeControl: false,
        streetViewControl: false,
        fullscreenControl: false
      }
      this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
 
      this.geolocation.getCurrentPosition().then(pos => {
        let latLng = new google.maps.LatLng(pos.coords.latitude, pos.coords.longitude);
        this.map.setCenter(latLng);
        this.map.setZoom(16);
      }).catch((error) => {
        console.log('Error getting location', error);
      });
    });
  }
 
  loadHistoricRoutes() {
    this.storage.get('routes').then(data => {
      if (data) {
        this.previousTracks = data;
      }
    });
  }
}

You might have noticed that we also added declare var google at the top which prevents TypeScript errors later inside our code.

Right now, our app is also loading historic routes from the storage using loadHistoricRoutes but of course that’s still empty. Later this will be an array of objects, so let’s work on adding data to that array!

Tracking Geolocation and Drawing on Google Map

We now need to do 2 things:

  • Watch the current GPS position of the user
  • Draw a path on our map

After that we also need to add the tracked route to the storage, but that’s the easiest part of our app.

Inside the startTracking function we subscribe to the position of the user which means we automatically get new coordinates from the Observable!

We can then use those values to first of all add a new entry to our trackedRoute array (to have a reference to all the coordinates of the walk) and then call the redrawPath if we want to immediately update our map.

Inside that function we also check a variable currentMapTrack which always holds a reference to the current path on our map!

To actually draw we can use a Polyline which can have a few options and most important the path which expects an array of objects like [{ lat: '', lng: '' }]

This function will then automatically connect the different coordinates and draw the line, and finally after we have constructed this line we call setMap on this line which draws it onto our map!

Now go ahead and add these 2 functions below the previous functions inside pages/home/home.ts:

startTracking() {
    this.isTracking = true;
    this.trackedRoute = [];
 
    this.positionSubscription = this.geolocation.watchPosition()
      .pipe(
        filter((p) => p.coords !== undefined) //Filter Out Errors
      )
      .subscribe(data => {
        setTimeout(() => {
          this.trackedRoute.push({ lat: data.coords.latitude, lng: data.coords.longitude });
          this.redrawPath(this.trackedRoute);
        }, 0);
      });
 
  }
 
  redrawPath(path) {
    if (this.currentMapTrack) {
      this.currentMapTrack.setMap(null);
    }
 
    if (path.length > 1) {
      this.currentMapTrack = new google.maps.Polyline({
        path: path,
        geodesic: true,
        strokeColor: '#ff00ff',
        strokeOpacity: 1.0,
        strokeWeight: 3
      });
      this.currentMapTrack.setMap(this.map);
    }
  }

The last missing function is when we stop the tracking. Then we need to store the information of the previous walk and also clear our map again.

Along with the tracked route we also store the current date to have some sort of information on the actual element, of course a running app would allow to review and share your path and then perhaps add more information like the time it took, distance covered and so on.

For now though this is enough, and the last function of this tutorial is the loading of historic routes – which is actually only a call to our redraw function with the information stored in the object!

Therefore, wrapn this up by adding the last functions to your pages/home/home.ts:

stopTracking() {
  let newRoute = { finished: new Date().getTime(), path: this.trackedRoute };
  this.previousTracks.push(newRoute);
  this.storage.set('routes', this.previousTracks);
 
  this.isTracking = false;
  this.positionSubscription.unsubscribe();
  this.currentMapTrack.setMap(null);
}
 
showHistoryRoute(route) {
  this.redrawPath(route);
}

That’s it, now make sure to run your app on a real device or at least a simulator where you can change the location to test out the functionality like I did within the initial Gif at the top of this tutorial!

You can also find a video version of this article below.

How to build a React Web App with Ionic?

How to build a React Web App with Ionic?

With the latest version of the Ionic framework, which lets you write mobile apps with web technologies, you can use React to build your mobile apps. In this React tutorial, you'll learn how to build a React web app with Ionic.

With the latest version of the Ionic framework, which lets you write mobile apps with web technologies, you can use React to build your mobile apps. Versions previous to 4 only has components for Angular, but now React support is complete. It has all the components for building apps to your liking.

Ionic 4 and later is a mobile app framework and a component library. You can build mobile apps, progressive web apps, and normal web apps. The component library can be used on its own. Of course, the hardware support remains in place for you to use if you wish. Right now, the React version of Ionic can only be used to build web apps. Only the Angular version can be built into a mobile app.

The full reference for the React version of Ionic is at https://ionicframework.com/docs.

In this article, we will build a React web app with Ionic that does currency conversion. The home page will display the list of the latest exchange rates and another page will have a form to convert the currencies of your choice. Tp get the data we use the Foreign exchange rates API, located at https://exchangeratesapi.io/ to get the exchange rates, and we use the Open Exchange Rates API, located at https://openexchangerates.org/, to get the list of currencies.

To start, we will start by installing the Ionic CLI. We run:

npm install -g [email protected]

to install the latest version to your computer. Next we run:

ionic start currency-converter --type=react

then select the Sidenav option to create an Ionic project with a left side bar.

Next we install our own packages. We need Axios to make HTTP requests and MobX for state management. Run npm i axios mobx mobx-react in our project folder to install them.

Now we are ready to create some pages. In the pages folder, create ConvertCurrencyPage.jsx and add:

import {
  IonButtons,
  IonContent,
  IonHeader,
  IonItem,
  IonList,
  IonMenuButton,
  IonPage,
  IonTitle,
  IonToolbar,
  IonInput,
  IonLabel,
  IonSelect,
  IonSelectOption,
  IonButton
} from "@ionic/react";import React from "react";
import { observer } from "mobx-react";
import { getExchangeRates } from "../requests";const ConvertCurrencyPage = ({ currenciesStore }) => {
  const [fromCurrencies, setFromCurrencies] = React.useState({});
  const [toCurrencies, setToCurrencies] = React.useState({});
  const [values, setValues] = React.useState({ amount: 0 } as any);
  const [submitting, setSubmitting] = React.useState(false);
  const [toAmount, setToAmount] = React.useState(0);const convertCurrency = async () => {
    setSubmitting(true);
    if (values.amount <= 0 || !values.from || !values.to) {
      return;
    }
    const { data } = await getExchangeRates(values.from);
    const rate = data.rates[values.to];
    setToAmount(values.amount * rate);
  };React.useEffect(() => {
    const fromCurrencies = {};
    for (let key in currenciesStore.currencies) {
      if (key != values.to) {
        fromCurrencies[key] = currenciesStore.currencies[key];
      }
    }
    setFromCurrencies(fromCurrencies);const toCurrencies = {};
    for (let key in currenciesStore.currencies) {
      if (key != values.from) {
        toCurrencies[key] = currenciesStore.currencies[key];
      }
    }
    setToCurrencies(toCurrencies);
  }, [currenciesStore.currencies, values.from, values.to]);return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonButtons slot="start">
            <IonMenuButton />
          </IonButtons>
          <IonTitle>Convert Currency</IonTitle>
        </IonToolbar>
      </IonHeader><IonContent>
        <IonList lines="none">
          <IonItem>
            <IonInput
              type="number"
              value={values.amount}
              color={!values.amount && submitting ? "danger" : undefined}
              min="0"
              onIonChange={ev =>
                setValues({ ...values, amount: (ev.target as any).value })
              }
            ></IonInput>
          </IonItem><IonItem>
            <IonLabel>Currency to Convert From</IonLabel>
            <IonSelect
              placeholder="Select One"
              color={!values.from && submitting ? "danger" : undefined}
              onIonChange={ev =>
                setValues({ ...values, from: (ev.target as any).value })
              }
            >
              {Object.keys(fromCurrencies).map(key => {
                return (
                  <IonSelectOption value={key} key={key}>
                    {(fromCurrencies as any)[key]}
                  </IonSelectOption>
                );
              })}
            </IonSelect>
          </IonItem><IonItem>
            <IonLabel>Currency to Convert To</IonLabel>
            <IonSelect
              placeholder="Select One"
              color={!values.to && submitting ? "danger" : undefined}
              onIonChange={ev =>
                setValues({ ...values, to: (ev.target as any).value })
              }
            >
              {Object.keys(toCurrencies).map(key => {
                return (
                  <IonSelectOption value={key} key={key}>
                    {(toCurrencies as any)[key]}
                  </IonSelectOption>
                );
              })}
            </IonSelect>
          </IonItem><IonItem>
            <IonButton size="default" fill="solid" onClick={convertCurrency}>
              Convert
            </IonButton>
          </IonItem>{toAmount ? (
            <IonItem>
              {values.amount} {values.from} is {toAmount} {values.to}.
            </IonItem>
          ) : (
            undefined
          )}
        </IonList>
      </IonContent>
    </IonPage>
  );
};export default observer(ConvertCurrencyPage);

to add a form to convert currency from one to another. In this form, we filter by choices by excluding from the currency being converted to from the first drop down and exclude the currency being converted from in the second drop down. Also we have an ion-input for the amount being converted. We get the values of the currency from the currenciesStore , which is the MobX store that gets the list of currencies from. In the IonSelect components we set the onIonChange props to handler functions which set the drop down’s values. We also set the placeholder for all the inputs and selects. In the IonInput component, we do the same with the onIonChange handler. We show the right value by using the variables set in the values object during change events.

When the user clicks Convert, we run the convertCurrency function. We check if the values are set correctly before running the rest of the code. If that succeeds, then we run the getExchangeRates function imported from requests.js , then we set the final toAmount by multuplying the rate with the amount.

The useEffect callback is used for excluding the currency to convert from from the currency to convert to list and vice versa. The array in the second argument of useEffect specifies which values to watch for.

The observer function in the last line is for designating the component to watch the latest values from the MobX stores.

Next in Home.tsx , we replace the existing code with:

import {
  IonButtons,
  IonContent,
  IonHeader,
  IonItem,
  IonLabel,
  IonList,
  IonListHeader,
  IonMenuButton,
  IonPage,
  IonTitle,
  IonToolbar,
  IonSelect,
  IonSelectOption
} from "@ionic/react";
import React from "react";
import "./Home.css";
import { getExchangeRates } from "../requests";
import { CurrencyStore } from "../stores";
import { observer } from "mobx-react";const HomePage = ({ currencyStore, currenciesStore }) => {
  const [rates, setRates] = React.useState({});const getRates = async () => {
    const { data } = await getExchangeRates(
      (currencyStore as CurrencyStore).currency
    );
    setRates(data.rates);
  };React.useEffect(() => {
    getRates();
  }, [(currencyStore as CurrencyStore).currency]);
  return (
    <IonPage>
      <IonHeader>
        <IonToolbar>
          <IonButtons slot="start">
            <IonMenuButton />
          </IonButtons>
          <IonTitle>Home</IonTitle>
        </IonToolbar>
      </IonHeader>
      <IonContent>
        <IonList lines="none">
          <IonListHeader>Latest Exchange Rates</IonListHeader>          <IonItem>
            <IonLabel>Currency</IonLabel>
            <IonSelect
              placeholder="Select One"
              onIonChange={ev => {
                (currencyStore as CurrencyStore).setCurrency(
                  ev.target && (ev.target as any).value
                );
              }}
            >
              {Object.keys(currenciesStore.currencies).map(key => {
                return (
                  <IonSelectOption
                    value={key}
                    key={key}
                    selected={
                      (currencyStore as CurrencyStore).currency
                        ? key == (currencyStore as CurrencyStore).currency
                        : key == "AUD"
                    }
                  >
                    {(currenciesStore.currencies as any)[key]}
                  </IonSelectOption>
                );
              })}
            </IonSelect>
          </IonItem>
        </IonList>        <IonList lines="none">
          <IonListHeader>Exchange Rates</IonListHeader>
          {Object.keys(rates).map(key => {
            console.log(rates);
            return (
              <IonItem>
                {key}: {rates[key]}
              </IonItem>
            );
          })}
        </IonList>
      </IonContent>
    </IonPage>
  );
};export default observer(HomePage);

In this file, we display the exchange rates from an API. We get the currencies from the currenciesStore so that users can see exchange rates based on different currencies. The items are display in an IonList provided by Ionic.

The observer function in the last line is for designating the component to watch the latest values from the MobX stores.

Next in App.tsx , replace the following code with:

import React from "react";
import { Redirect, Route } from "react-router-dom";
import { IonApp, IonRouterOutlet, IonSplitPane } from "@ionic/react";
import { IonReactRouter } from "@ionic/react-router";
import { AppPage } from "./declarations";import Menu from "./components/Menu";
import Home from "./pages/Home";
import { home, cash } from "ionicons/icons";/* Core CSS required for Ionic components to work properly */
import "@ionic/react/css/core.css";/* Basic CSS for apps built with Ionic */
import "@ionic/react/css/normalize.css";
import "@ionic/react/css/structure.css";
import "@ionic/react/css/typography.css";/* Optional CSS utils that can be commented out */
import "@ionic/react/css/padding.css";
import "@ionic/react/css/float-elements.css";
import "@ionic/react/css/text-alignment.css";
import "@ionic/react/css/text-transformation.css";
import "@ionic/react/css/flex-utils.css";
import "@ionic/react/css/display.css";/* Theme variables */
import "./theme/variables.css";
import ConvertCurrencyPage from "./pages/ConvertCurrencyPage";
import { CurrencyStore, CurrenciesStore } from "./stores";
import { getCurrenciesList } from "./requests";const currencyStore = new CurrencyStore();
const currenciesStore = new CurrenciesStore();const appPages: AppPage[] = [
  {
    title: "Home",
    url: "/home",
    icon: home
  },
  {
    title: "Convert Currency",
    url: "/convertcurrency",
    icon: cash
  }
];const App: React.FC = () => {
  const [initialized, setInitialized] = React.useState(false);  const getCurrencies = async () => {
    const { data } = await getCurrenciesList();
    currenciesStore.setCurrencies(data);
    setInitialized(true);
  };React.useEffect(() => {
    if (!initialized) {
      getCurrencies();
    }
  });  return (
    <IonApp>
      <IonReactRouter>
        <IonSplitPane contentId="main">
          <Menu appPages={appPages} />
          <IonRouterOutlet id="main">
            <Route
              path="/home"
              render={() => (
                <Home
                  currencyStore={currencyStore}
                  currenciesStore={currenciesStore}
                />
              )}
              exact={true}
            />
            <Route
              path="/convertcurrency"
              render={() => (
                <ConvertCurrencyPage
                  currenciesStore={currenciesStore}
                />
              )}
              exact={true}
            />
            <Route exact path="/" render={() => <Redirect to="/home" />} />
          </IonRouterOutlet>
        </IonSplitPane>
      </IonReactRouter>
    </IonApp>
  );
};export default App;

We modified the routes so that we use the render prop instead of the component prop since we want to pass in our MobX stores into the components. The stores store the currently selected currency for the home page and the list of currencies for both pages.

We get the list of currencies here for both components and set the data in store for both to retrieve by setting it in store.

Next create requests.ts in the src folder and add:

const axios = require('axios');
const APIURL = 'https://api.exchangeratesapi.io';
const OPEN_EXCHANGE_RATES_URL = 'http://openexchangerates.org/api/currencies.json';

export const getExchangeRates = (baseCurrency: string) => axios.get(`${APIURL}/latest?base=${baseCurrency}`)

export const getCurrenciesList = () => axios.get(OPEN_EXCHANGE_RATES_URL)

These are the code for making the HTTP requests to get the currencies and exchange rates.

Next create store.ts to and add:

import { observable, action } from "mobx";class CurrencyStore {
    @observable currency: string = 'AUD';    @action setCurrency(currency: string) {
        this.currency = currency;
    }
}class CurrenciesStore {
    @observable currencies = {};    @action setCurrencies(currencies) {
        this.currencies = currencies;
    }
}export { CurrencyStore, CurrenciesStore };

The CurrencyStore is for storing the selected currency in the home page to show the exchange rates based on the selected currency. currency is the value observed by the home page and setCurrency sets the currency value. Similarly, CurrenciesStore stores a list of currencies retrieved in App.tsx where setCurrencies is called.

Next in tsconfig.json , we replace the existing code with:

{
  "compilerOptions": {
      "experimentalDecorators": true,
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "noImplicitAny": false
  },
  "include": [
    "src"
  ]
}

to noImplicitAny and set it to true .

Finally, in index.html , replace the existing code with:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Currency Converter</title><base href="/" /><meta
      name="viewport"
      content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
    />
    <meta name="format-detection" content="telephone=no" />
    <meta name="msapplication-tap-highlight" content="no" /><link
      rel="shortcut icon"
      type="image/png"
      href="%PUBLIC_URL%/assets/icon/favicon.png"
    /><!-- add to homescreen for ios -->
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-title" content="Ionic App" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
  </head><body>
    <div id="root"></div>
  </body>
</html>

to change the title.

After all the hard work is done, we get:


How to Build an Ionic Chat App with React and Stream

How to Build an Ionic Chat App with React and Stream

In this tutorial, I'll walk you through how to build a lightweight Ionic chat application that is powered by React and Stream Chat.

In this tutorial, I’ll walk you through how to build a lightweight Ionic chat application that is powered by React and Stream Chat.

There are a few requirements for this, primarily the version of Node.js (I prefer to use nvm for Node version management), XCode for iOS if you’re on macOS or Android Studio if you’re on macOS or Windows and want to build against Android, and yarn for dependency management.

Let’s code!

1. Install Ionic

To get started with Ionic, download the Ionic CLI using npm:

$ yarn global add ionic

Once installed, login to Ionic from the command line using your new CLI:

$ ionic login

For now, that’s all that we have to do. We’re going to be using Create React App (next step) to continue our installation.

2. Install Create React App and Dependencies

Similar to how we installed Ionic, let’s go ahead and install Create React App (CRA) globally using npm:

$ yarn global add create-react-app

Next, create a new directory. I’m going to be working in my ~/Code directory, but you’re free to use a directory of your choosing:

$ cd ~/Code

Now, install React using CRA (ionic-chat is the name of the directory that will be generated — this is also optional as you can name it whatever you’d like):

$ npx create-react-app ionic-chat

Move into the ionic-chat directory and we’ll start installing the necessary dependencies.

$ yarn add stream-chat stream-chat-react axios react-router react-router-dom @ionic/react

With our dependencies installed, let’s go ahead and move on to the next step of the setup.

3. Setup the API with Heroku

The API, although small, plays an important role in chat. The API accepts user credentials from the login screen and generates a JWT for use within that. It also adds the user to the channel.

To spin up the API, I’ve included a simple one-click Heroku button. This will generate a new application on Heroku and then create a Stream Chat trial for you to use. After clicking the Heroku button, you will be prompted to add an application name — make this unique. Then click “Deploy” to kick off the Heroku deploy process.

Heroku Dashboard

Once installed, get the environment variables from Heroku (they were generated by the Heroku creation) and drop them in your .env file in your React app. The environment variables can be found under the “Settings” section of your Heroku dashboard as shown in this blog post by Heroku. Note that there is only one environment variable called “STREAM_URL”. The key and secret are delimited by a “:” with the first being the key and the second being the secret.

Heroku — Environment Variables

Alternatively, if you would like to skip Heroku, you can clone this GitHub repo and run the API with the yarn start command — be sure to run yarn install prior to starting and also be sure to fill out your .env with credentials found on the Stream dashboard (you will need to enable a free chat trial).

4. Install the iOS Simulator

If you have XCode installed, you’re pretty much all set. If not, and you want to download XCode, you can do so here. XCode comes bundled with an iOS Simulator by default.

Should you not wish to install XCode, you can optionally install this npm package which will install a standalone iOS simulator for you.

$ yarn global add ios-sim

The full instructions on how to use it are located here: https://www.npmjs.com/package/ios-sim

5. Install Android Studio (Optional)

Running on iOS with macOS seems to be the fastest way to test your code; however, if you’re on Windows or would simply like to use Android, I’ll cover that below.

Head over to the Android Studio download page and select your download of choice. Android Studio is available for iOS, Windows, and macOS. It’s a large file so the download may take a good amount of time.

Once downloaded, follow the installation instructions and open Android Studio. We’re going to download the necessary SDKs and create an Android Virtual Device (AVD).

With Android Studio open, click on “Configure” and then click “SDK Manager”.

Android Studio — Configure

With the SDK Manager open, select “Android 9.0 (Pie)” and then click “Apply”.

Android Studio — Android 9.0 (Pie)

Your download will begin. Once complete, go back to the main screen and click the “Configure” button, followed by “AVD Manager”. On the AVD Manager screen, you will want to click “+ Create Virtual Device”.

Select the “Pixel 3 XL” device, then click “Next”. Select “Pie (28)” for your API level followed by the “Next” button.

Android Studio — OS Download

Finally, click “Finish” and your AVD will be provisioned. Once done, you can safely exit out of the AVD screen and you will see your newly created AVD in the AVD manager.

AVD Manager

If you click on the green play button, your AVD will launch!

Android Pie Emulator

Congratulations! You’ve successfully generated an AVD within Android Studio! We’re not going to use it just yet, but the AVD will come in handy when testing later on in this tutorial.

6. Create Files

We have everything set up, now it’s time to add the necessary files to make our code work! We’ll need to create a handful of files, so pay close attention:

  1. In the root of your directory, create ionic.config.json with the following contents:
{
  "name": "Ionic Chat",
  "type": "custom",
  "integrations": {}
}

ionic.config.json

  1. In public/index.html, swap out the current HTML for the following:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0,
    minimum-scale=1.0, maximum-scale=1.0, viewport-fit=cover user-scalable=no"
    />

    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta
      name="apple-mobile-web-app-status-bar-style"
      content="black-translucent"
    />
    <meta name="theme-color" content="#ffffff" />
    <meta name="apple-mobile-web-app-title" content="Ionic Chat" />

    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

    <title>Ionic Chat</title>
  </head>
  <body ontouchstart="">
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

public/index.html

  1. Move into the src/ directory, we’re going to create and modify a few files:

In app.css, swap out all of the existing CSS for this:

@import url("https://fonts.googleapis.com/css?family=Open+Sans");

html,
body {
  background: #ffffff;
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
    env(safe-area-inset-bottom) env(safe-area-inset-left);
  font-family: "Open Sans", sans-serif;
}

.no-scroll .scroll-content {
  overflow: hidden;
}

::placeholder {
  color: #3f3844;
}

.login-root {
  text-align: center;
  margin-top: 25%;
}

.login-card > h4 {
  margin-bottom: 22px;
}

.login-card > input {
  padding: 4px 6px;
  margin-bottom: 20px;
  border: 1px solid #d3d3d3;
  background: hsla(0, 0%, 100%, 0.2);
  border-radius: 4px !important;
  font-size: 16px;
  color: #24282e;
  -webkit-box-shadow: none;
  box-shadow: none;
  outline: 0;
  padding: 0 16px 1px;
  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  height: 50px;
  width: 300px;
}

.login-card button {
  font-size: 16px;
  background-color: #3880ff;
  border-radius: 4px;
  line-height: 1.4em;
  padding: 14px 33px 14px;
  margin-right: 10px;
  border: 0 solid rgba(0, 0, 0, 0);
  color: #ffffff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.12);
  border-radius: 6px;
  text-transform: none;
  outline: none;
}

.str-chat__loading-indicator {
  text-align: center;
  margin-top: 15%;
}

.str-chat-channel {
  background-color: #ffffff !important;
}

.str-chat__header-livestream {
  box-shadow: none !important;
  background: transparent;
}

.str-chat__square-button {
  display: none !important;
}

.str-chat__input {
  box-shadow: none !important;
}

.rta__textarea {
  padding: 4px 6px;
  margin-bottom: 20px;
  border: 1px solid #d3d3d3 !important;
  background: hsla(0, 0%, 100%, 0.2);
  border-radius: 4px !important;
  font-size: 14px !important;
  color: #24282e !important;
  -webkit-box-shadow: none !important;
  -webkit-appearance: none !important;
  box-shadow: none !important;
  outline: none !important;
  padding: 0 16px 1px;
  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  height: 50px;
}

.str-chat__textarea {
  height: 45px !important;
}

.str-chat__input-footer--count {
  margin-top: 4px;
  margin-left: 4px;
}

.footer {
  margin-bottom: 50px;
}

App.css

In App.js, swap out the existing code for this JavaScript (this logic will take care of routing between files):

import React from "react";
import { BrowserRouter as Router, Switch } from "react-router-dom";

import Chat from "./Chat";
import Login from "./Login";

import UnauthedRoute from "./UnauthedRoute";
import AuthedRoute from "./AuthedRoute";

const App = () => (
  <Router>
    <Switch>
      <UnauthedRoute path="/auth/login" component={Login} />
      <AuthedRoute path="/" component={Chat} />
    </Switch>
  </Router>
);

export default App;

App.js

Create a file called AuthedRoute.js and drop the contents below into the file:

import React from "react";
import { Redirect, Route } from "react-router-dom";

const AuthedRoute = ({ component: Component, loading, ...rest }) => {
  const isAuthed = Boolean(localStorage.getItem("token"));
  return (
    <Route
      {...rest}
      render={props =>
        loading ? (
          <p>Loading...</p>
        ) : isAuthed ? (
          <Component history={props.history} {...rest} />
        ) : (
          <Redirect
            to={{
              pathname: "/auth/login",
              state: { next: props.location }
            }}
          />
        )
      }
    />
  );
};

export default AuthedRoute;

AuthedRoute.js

Create a file named Chat.js and use the following code (this is all of the logic that powers chat):

import React, { Component } from "react";
import { IonApp, IonContent } from "@ionic/react";
import {
  Chat,
  Channel,
  ChannelHeader,
  Thread,
  Window,
  MessageList,
  MessageInput
} from "stream-chat-react";
import { StreamChat } from "stream-chat";

import "./App.css";
import "@ionic/core/css/core.css";
import "@ionic/core/css/ionic.bundle.css";
import "stream-chat-react/dist/css/index.css";
import "stream-chat-react/dist/css/index.css";

class App extends Component {
  constructor(props) {
    super(props);

    const { id, name, email, image } = JSON.parse(localStorage.getItem("user"));

    this.client = new StreamChat(localStorage.getItem("apiKey"));
    this.client.setUser(
      {
        id,
        name,
        email,
        image
      },
      localStorage.getItem("token")
    );

    this.channel = this.client.channel("messaging", "ionic-chat", {
      image: "https://i.imgur.com/gwaMDJZ.png",
      name: "Ionic Chat"
    });
  }

  render() {
    return (
      <IonApp style={{ paddingTop: "2px" }}>
        <IonContent>
          <Chat client={this.client} theme={"messaging light"}>
            <Channel channel={this.channel}>
              <Window>
                <ChannelHeader />
                <MessageList />
                <div className="footer">
                  <MessageInput />
                </div>
              </Window>
              <Thread />
            </Channel>
          </Chat>
        </IonContent>
      </IonApp>
    );
  }
}

export default App;

Chat.js

Next, create a file called Login.js and use the following code (this will add auth to your app):

import React, { Component } from "react";
import axios from "axios";

import "./App.css";

class Login extends Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      name: "",
      email: ""
    };

    this.initStream = this.initStream.bind(this);
  }

  async initStream() {
    await this.setState({
      loading: true
    });

    const auth = await axios.post(process.env.REACT_APP_TOKEN_ENDPOINT, {
      name: this.state.name,
      email: this.state.email
    });

    localStorage.setItem("user", JSON.stringify(auth.data.user));
    localStorage.setItem("token", auth.data.token);
    localStorage.setItem("apiKey", auth.data.apiKey);

    await this.setState({
      loading: false
    });

    this.props.history.push("/");
  }

  handleChange = e => {
    this.setState({
      [e.target.name]: e.target.value
    });
  };

  render() {
    return (
      <div className="login-root">
        <div className="login-card">
          <h4>Ionic Chat</h4>
          <input
            type="text"
            placeholder="Name"
            name="name"
            onChange={e => this.handleChange(e)}
          />
          <br />
          <input
            type="email"
            placeholder="Email"
            name="email"
            onChange={e => this.handleChange(e)}
          />
          <br />
          <button onClick={this.initStream}>Submit</button>
        </div>
      </div>
    );
  }
}

export default Login;

Login.js

Now, create a file called UnauthedRoute.js to accommodate for users who enter without being authenticated:

import React from "react";
import { Redirect, Route } from "react-router-dom";

const UnauthedRoute = ({ component: Component, loading, ...rest }) => {
  const isAuthed = Boolean(localStorage.getItem("token"));
  return (
    <Route
      {...rest}
      render={props =>
        loading ? (
          <p>Loading...</p>
        ) : !isAuthed ? (
          <Component history={props.history} {...rest} />
        ) : (
          <Redirect
            to={{
              pathname: "/"
            }}
          />
        )
      }
    />
  );
};

export default UnauthedRoute;

UnauthedRoute.js

Create a file called withSession.js:

import React from "react";
import { withRouter } from "react-router";

export default (Component, unAuthed = false) => {
  const WithSession = ({ user = {}, streamToken, ...props }) =>
    user.id || unAuthed ? (
      <Component
        userId={user.id}
        user={user}
        session={window.streamSession}
        {...props}
      />
    ) : (
      <Component {...props} />
    );

  return withRouter(WithSession);
};

withSession.js

  1. Install the Ionic build scripts in your package.json file:
"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "ionic:build": "react-scripts build",
    "ionic:serve": "react-scripts start"
}

package.json

Capacitor is an open-source framework provided by Ionic that helps you build progressive native web apps, mobile and desktops apps. It’s optimized for Ionic apps; however, it can be used with just about any framework.

We’ll be using Capacitor to lift and prepare our builds for iOS and Android. First things first though, let’s get Capacitor installed!

$ ionic capacitor add ios

Then, start the React app with the following command from your root directory:

$ yarn start

Open on iOS:

$ ionic capacitor open ios

Capacitor — iOS

Or, open on Android:

$ ionic capacitor open android

Capacitor — Android

Because I’m running macOS, I’m going to be using the iOS simulator. After running ionic capacitor open ios, XCode will launch. You will want to wait about a minute for it to index the project and then you can press the run button.

XCode

Your iOS simulator should boot up with the application installed and you should see a login screen similar to this:

iOS — Login

Go ahead and login with your name and email address. Don’t worry, your information is only stored in local storage and is not persisted to a third party platform of any kind. Once the chat window is loaded, you’ll be able to chat away!

iOS — Chat

What’s Next?

I would encourage you to continue developing against the codebase that you’ve created. If you have run into any issues, you can always clone the repo from GitHub for a fresh start.

In terms of deploying the application to a standalone device such as iOS or Android, Ionic has a great set of tutorials on how to do so. Both tutorials for iOS and Android publication can be found in the Ionic docs.

Want to know more about Stream Chat? Have a look at our interactive API tour that will walk you through the various steps of creating chat from scratch with Stream. We also have amazing API docs and a beautiful UI Kit that will allow you to build any type of chat.

Happy coding! Thank you for reading !

How to use the Cordova Camera Plugin to capture the image in Ionic App

How to use the Cordova Camera Plugin to capture the image in Ionic App

In this Ionic 4 Cordova Camera Plugin tutorial, we'll learn how to use the Cordova camera plugin to capture the image in an Ionic 4 app. Learn how to take pictures using Ionic Native & Cordova Camera plugin. Ionic Native support is fantastic to access the native devices.

Ionic 4 Camera tutorial is going to be today’s main topic, In this tutorial, we will learn how to take pictures using Ionic Native & Cordova Camera plugin.

Creating a Hybrid app doesn’t take a lot of time; however, in most of the cases, we get stuck in building any feature which is directly related to hardware accessibility. To access Network, Sensors, GPS, File System, Storage and Camera in Native devices sometimes becomes a bit tedious.

In this tutorial, we are going to create a basic camera app using Ionic/Angular. This allows a user to click a picture via a native camera device and returns the images in Base64 URL format.

To access native device camera, we will use Ionic Native, Its a wrapper for Cordova plugins that offers easy and better integration with Angular.

Table of Contents

  • Installing Cordova
  • Setting Up Ionic 4 Camera App
  • Adding Platform in Ionic
  • Install Ionic Cordova and Native Camera Plugin
  • Importing Camera Plugin in App Module
  • Using Camera Plugin in Ionic 4 Component
  • Test Camera App in Browser
  • Conclusion
Installing Cordova

You must have the Node and NPM installed in your device, to get started with Ionic 4 Camera tutorial.

Run the following command to install Cordova to access the native device.

npm install -g cordova

If throws an error, then use the below command.

sudo npm install -g cordova

Password:******

We will test our Ionic camera app on a real device. You must have Java SDK installed to build the camera app on Android. If you are creating a camera app for iOS, then you must have an Xcode app installed on macOS.

Setting Up Ionic 4 Camera App

Make sure you must have the latest version of Ionic CLI installed on your system.

npm install -g [email protected]

Once the Ionic CLI is updated, Run the following command to create a brand new blank Ionic/Angular app.

ionic start ionic-cordova-camera-app blank --type=angular

Get into the project folder.

cd ionic-cordova-camera-app
Adding Platform in Ionic

Next, we have to add the target platform. We can add the target platform by running the following commands based on your platform.

ionic platform add ios
ionic platform add android
Install Ionic Cordova and Native Camera Plugin

Next, run the command to install Native camera plugin to access the camera features.

ionic cordova plugin add cordova-plugin-camera
npm install @ionic-native/camera

[Note] Since IOS 10 the camera requires permissions to be placed in your config.xml add

<config-file parent="NSCameraUsageDescription" platform="ios" target="*-Info.plist">
 <string>You can take photos</string>
</config-file>

Add the above code inside of the <platform name='ios> section

Ionic Supports Following Platforms

  • iOS
  • Android
  • Windows
  • Browser
Importing Camera Plugin in App Module

Now, we have installed and configured everything we required in our Ionic app. Next, To use the camera plugin, we need to import the Cordova Camera plugin and also register in the providers array in app.module.ts file.

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 { Camera } from '@ionic-native/camera/ngx';

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

export class AppModule { }
Using Camera Plugin in Ionic 4 Component

Finally, we are going to use camera plugin in an Ionic home component. Navigate to home.page.html file and place the following code inside of it.

<ion-button (click)="captureImage()">
   Click Picture
</ion-button>

<img [src]="clickedImage" />

Navigate to home.page.ts file and add the following code inside of it.

import { Component } from '@angular/core';
import { Camera, CameraOptions } from '@ionic-native/camera/ngx';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})

export class HomePage {
  clickedImage: string;

  options: CameraOptions = {
    quality: 30,
    destinationType: this.camera.DestinationType.DATA_URL,
    encodingType: this.camera.EncodingType.JPEG,
    mediaType: this.camera.MediaType.PICTURE
  }

  constructor(private camera: Camera) { }

  captureImage() {
    this.camera.getPicture(this.options).then((imageData) => {
      // imageData is either a base64 encoded string or a file URI
      // If it's base64 (DATA_URL):
      let base64Image = 'data:image/jpeg;base64,' + imageData;
      this.clickedImage = base64Image;
    }, (err) => {
      console.log(err);
      // Handle error
    });
  }
}

Have a look what we did above using Cordova Camera plugin.

quality: We can set the image quality from 1 to 100, above we set the image size to 30 to control the image size.

destinationType: Choose the format of the return value.

encodingType: Refers to the Image file type (JPEG or PNG).

mediaType: Used to get media type, refers to either Picture or Video.

captureImage(): This method triggers the getPicture() method, It takes the camera options in the parameter and return the data which we are setting in the variable to show the clicked image.

Test Camera App in Browser

To test the camera app in the browser, we have to run the below command.

ionic cordova platform add browser

Next, to test the camera app we need to run the following command.

ionic cordova run browser

Do not run ionic serve otherwise Ionic won’t be able to access the camera.

You can also use the following commands if you are developing the app for iOS or Android.

# Android
ionic cordova run android

# iOS
ionic cordova run ios

Conclusion

Finally, we have completed the Ionic 4 Cordova Camera Plugin Tutorial with Example. In this tutorial, we learned how to use the Cordova camera plugin to capture the image in an Ionic app. Ionic Native support is fantastic to access the native devices. I hope you liked this tutorial and share it with others.