Using the Ionic Native Diagnostics plugin

April 29, 2017, 8:18 pm Categories:

Categories

Thanks to Ionic Native developers can install plugins to help access different devices features such as the camera hardware, detecting network connectivity and geolocation positioning.

This is all good and well but how do we know whether our users devices (whether iOS and/or Android) will have those features enabled or even present?

Enter the Ionic Native Diagnostics plugin which allows developers to detect whether a certain feature is enabled or even present on the device that the application happens to be running on.

Over the course of this tutorial I want to take you through using a small subset of this plugin's available methods to build a simple demo application for iOS only (sorry - I simply haven't had the time to build/test this on Android) that detects the following:

  • Whether the device is connected to a network
  • Whether Location Services are enabled (and, if they are, subsequently retrieves the user's current location)
  • Whether the device camera is enabled
  • Whether the device Contacts can be accessed

Assuming that all of the above functionality is able to be detected and the plugin determines that those features are enabled the user will then be able to select pictures from the iOS Photo Library, open Google's search home page in an In-App Browser window and add a contact to the device Contacts/Address book application.

All of which will appear as shown over the following screen captures:

So the focus here is simply to demonstrate how the Diagnostics plugin can be used to detect devices features and then work with those as and where necessary.

Ready?

Cool - let's get cracking then!

Laying the foundation

We start off with the Ionic CLI and create a simple project titled ion-diagnostic using the blank template:

ionic start ion-diagnostic blank --v2

Once generated we change into the root directory of the project (using the cd ion-diagnostic command) and install the necessary plugins and their accompanying packages, one after the other, with the following commands:

// Install the Diagnostic plugin
ionic plugin add --save cordova.plugins.diagnostic
npm install --save @ionic-native/diagnostic


// Install the Camera plugin
ionic plugin add --save cordova-plugin-camera
npm install --save @ionic-native/camera


// Install the In-App Browser plugin
ionic plugin add --save cordova-plugin-inappbrowser
npm install --save @ionic-native/in-app-browser


// Install the Geolocation plugin
ionic plugin add --save cordova-plugin-geolocation
npm install --save @ionic-native/geolocation


// Install the Contacts plugin
ionic plugin add --save cordova-plugin-contacts
npm install --save @ionic-native/contacts

Now we need to import the packages for each installed plugin and declare those within the application root module - ion-diagnostic/src/app/app.module.ts - like so:

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 { Camera } from '@ionic-native/camera';
import { Contacts } from '@ionic-native/contacts';
import { Diagnostic } from '@ionic-native/diagnostic';
import { Geolocation } from '@ionic-native/geolocation';
import { InAppBrowser } from '@ionic-native/in-app-browser';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    Camera,
    Contacts,
    Diagnostic,
    Geolocation,
    InAppBrowser,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

Before we can move onto coding the logic and templating for the HomePage component though we have one minor adjustment to make to both the installed node packages and the package.json file.

IonicNativePlugin - Caveat emptor

If you were to try and build/run the application in its current state you might receive the following error in the Terminal:

Module 'node_modules/@ionic-native/core/index' has no exported member 'IonicNativePlugin'

Eww - not nice.

Thankfully resolving this is pretty simple - you just need to upgrade the @ionic-native/core dependency to the latest version.

Simply run the following commands in the Terminal:

npm uninstall --save @ionic-native/core
npm install --save @ionic-native/core@latest

This will update the @ionic-native/core entry in your package.json file to the value shown below (this value will no doubt vary in the not-too distant future!):

"@ionic-native/core": "^3.6.1",

With @ionic-native/core now updated we can build/run the application without experiencing that particular error again.

Phew!

Coding the HomePage logic

Now that we have the basic skeleton of the application built let's start to flesh that out with coding the logic for the HomePage component.

Here we're going to implement the necessary properties and methods to manage the following scenarios in the application:

  • Implementing properties to determine whether DOM elements are conditionally displayed
  • Detecting whether the camera is available
  • Detecting whether Wifi is available
  • Detecting whether Location Services are available
  • Determining whether the application is authorised to use the Contacts/Address Book application
  • Opening google.com in an In-App Browser window
  • Opening and selecting pictures from the iOS Photo Library application
  • Add a contact to the Contacts/Address Book application

As I stated earlier we're only going to make use of a small subset of the available methods for the Diagnostics plugin but these will consist of the following:

  • isCameraPresent
  • isContactsAuthorized
  • isWifiAvailable
  • isLocationAvailable

These will be joined by methods from the remaining plugin packages and components that we'll be importing at the top of the /ion-diagnostic/src/pages/home/home.ts class.

All of which comes together in the following format:

import { Component } from '@angular/core';
import { AlertController, NavController, Platform } from 'ionic-angular';

import { Camera, CameraOptions } from '@ionic-native/camera';
import { Contacts, Contact, ContactAddress, ContactField, ContactName } from '@ionic-native/contacts';
import { Diagnostic } from '@ionic-native/diagnostic';
import { Geolocation } from '@ionic-native/geolocation';
import { InAppBrowser } from '@ionic-native/in-app-browser';



@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {


   public isCameraEnabled 		: boolean 	= false;
   public isWifiEnabled 		: boolean 	= false;
   public isContactsEnabled 	: boolean 	= false;
   public isLocationEnabled 	: boolean 	= false;
   public latitude     			: any;
   public longitude    			: any;
   public isImageTaken      	: any		= '';



   constructor(public navCtrl       : NavController,
               private _ALERT       : AlertController,
   			   private _CONTACTS    : Contacts,
               private _DIAGNOSTIC  : Diagnostic,
               private _GEO         : Geolocation,
               private _CAMERA      : Camera,
               private _IAP         : InAppBrowser,
               private _PLATFORM    : Platform) 
   {
      this._PLATFORM.ready()
      .then(() => 
      {
         this.isCameraAvailable();
         this.isLocationAvailable();
         this.isWifiAvailable();
         this.isContactsAuthorized();
      });
   }




   isCameraAvailable()
   {
      this._DIAGNOSTIC.isCameraPresent()
      .then((isAvailable : any) =>
      {
         this.isCameraEnabled = true;
      })
      .catch((error :any) =>
      {
         console.dir('Camera is:' + error);
      });
   }




   isContactsAuthorized()
   {
      this._DIAGNOSTIC.isContactsAuthorized()
      .then((isAuthorised : any) =>
      {
         this.isContactsEnabled = true;
      })
      .catch((error : any) =>
      {
         console.dir('Contacts is:' + error);
      });
   }




   isWifiAvailable()
   {
      this._DIAGNOSTIC.isWifiAvailable()
      .then((isAvailable : any) =>
      {
         this.isWifiEnabled = true;
      })
      .catch((error : any) =>
      {
         console.dir('Wifi is:' + error);
      });
   }




   isLocationAvailable()
   {
      this._DIAGNOSTIC.isLocationAvailable()
      .then((isAvailable) =>
      {
         
         this._GEO.getCurrentPosition()
         .then((data : any) => 
         {
			 this.isLocationEnabled 	= true;
			 this.latitude 		        = data.coords.latitude;
			 this.longitude		        = data.coords.longitude;

         })
         .catch((error : any) => 
         {
		    console.log('Error getting location', error);
		 });
            

         
      })
      .catch((error : any) =>
      {
         console.dir('Location is:' + error);
      });
   }




   openLink(link)
   {
      let target : string = '_blank',
          opts   : string = 'clearcache=yes,clearsessioncache=yes,toolbar=yes,location=yes';

      this._IAP.create(link, target, opts);
   }




   selectPictureFromPhotoLibrary()
   {
      let options : CameraOptions = {
         quality 			: 100,
         destinationType 	: this._CAMERA.DestinationType.DATA_URL,
         encodingType 		: this._CAMERA.EncodingType.JPEG,
         saveToPhotoAlbum   : true,
         sourceType 		: 0
      }
      
      this._CAMERA.getPicture(options)
      .then((data : any) => 
      {      
         this.isImageTaken = 'data:image/jpeg;base64,' + data;
      })
      .catch((err : any) => 
      {
         console.dir(err);
      });
   }




   saveContact(obj)
   {
      let contact: Contact 		= this._CONTACTS.create();

      
      contact.name 				= new ContactName(null, obj.surname, obj.firstname);
      contact.nickname   		= obj.nickname;
      contact.addresses 		= [new ContactAddress(true, null, null, obj.address, null, null, null, null)];
      contact.phoneNumbers 		= [new ContactField('mobile', obj.mobile)];
      contact.emails    		= [new ContactField('email', obj.email)];
      contact.photos    		= [new ContactField('profile', this.isImageTaken)];
      

      contact.save()
      .then((data : any) => 
      {
         let alert =   this._ALERT.create({
               title: 'Congratulations',
               subTitle : `The contact - ${obj.firstname} ${obj.surname} - was successfully added to your Address book`,
               buttons: ['Cool!']
            });
         alert.present();
      })
      .catch((error: any) => 
      { 
         console.error('Error saving contact.', error);
      });
   }



}

The code should be fairly self-explanatory from the property and method naming conventions but let's take a moment to go over some of the key areas so we recognise both their purpose and importance - starting with the properties defined at the top of the class:

public isCameraEnabled 		: boolean 	= false;
public isWifiEnabled 		: boolean 	= false;
public isContactsEnabled 	: boolean 	= false;
public isLocationEnabled 	: boolean 	= false;
public latitude     		: any;
public longitude    		: any;
public isImageTaken      	: any		= '';

The first 4 boolean properties will be used in the template HTML to conditionally display specific elements on the page if the Diagnostics plugin has detected that particular set of functionality is available/enabled.

The latitude and longitude properties will be used to display geolocation results in the template while the isImageTaken property handles the display of any selected Photo Library image as well as making this available for adding as an image to a Contacts/Address Book record.

The class constructor makes use of Ionic's Platform ready method to ensure the following are able to be run once the platform has detected the device hardware:

this.isCameraAvailable();
this.isLocationAvailable();
this.isWifiAvailable();
this.isContactsAuthorized();

These are simply wrappers around the same Diagnostic plugin methods.

Each of these methods sets the boolean value for the associated property (that was defined towards the top of the class) to true IF that plugin method detects/determines the particular feature it is diagnosing is available/enabled.

For example, if Wifi connectivity was detected then the isWifiEnabled property to true:

isWifiAvailable()
{
   this._DIAGNOSTIC.isWifiAvailable()
   .then((isAvailable : any) =>
   {
      this.isWifiEnabled = true;
   })
   .catch((error : any) =>
   {
      console.dir('Wifi is:' + error);
   });
}

Geolocation functionality is handled within the isLocationAvailable method like so:

isLocationAvailable()
{
   this._DIAGNOSTIC.isLocationAvailable()
   .then((isAvailable) =>
   {         
      this._GEO.getCurrentPosition()
      .then((data : any) => 
      {
	 this.isLocationEnabled 	= true;
	 this.latitude 		        = data.coords.latitude;
	 this.longitude		        = data.coords.longitude;

      })
      .catch((error : any) => 
      {
         console.log('Error getting location', error);
      });      
         
   })
   .catch((error : any) =>
   {
      console.dir('Location is:' + error);
   });
}

Managing the opening of the google.com link through the In-App Browser plugin is handled with the openLink method:

openLink(link)
{
   let target : string = '_blank',
       opts   : string = 'clearcache=yes,clearsessioncache=yes,toolbar=yes,location=yes';

   this._IAP.create(link, target, opts);
}

Selecting images from the device's Photo Library is handled through the selectPictureFromPhotoLibrary method:

selectPictureFromPhotoLibrary()
{
   let options : CameraOptions = {
       quality 			: 100,
       destinationType 	: this._CAMERA.DestinationType.DATA_URL,
       encodingType 	: this._CAMERA.EncodingType.JPEG,
       saveToPhotoAlbum : true,
       sourceType 		: 0
   }
      
   this._CAMERA.getPicture(options)
   .then((data : any) => 
   {      
      this.isImageTaken = 'data:image/jpeg;base64,' + data;
   })
   .catch((err : any) => 
   {
      console.dir(err);
   });
}

Finally the saveContact method handles creating a new contact (using data supplied from the HTML template) and then attempts to save this in the Contacts/Address Book application.

If the contact was able to be added an Alert component is triggered to inform the user that all was successful:

saveContact(obj)
{
   let contact: Contact 	= this._CONTACTS.create();

      
   contact.name 		    = new ContactName(null, obj.surname, obj.firstname);
   contact.nickname   		= obj.nickname;
   contact.addresses 		= [new ContactAddress(true, null, null, obj.address, null, null, null, null)];
   contact.phoneNumbers 	= [new ContactField('mobile', obj.mobile)];
   contact.emails    		= [new ContactField('email', obj.email)];
   contact.photos    		= [new ContactField('profile', this.isImageTaken)];
      

   contact.save()
   .then((data : any) => 
   {
      let alert =   this._ALERT.create({
          title: 'Congratulations',
          subTitle : `The contact - ${obj.firstname} ${obj.surname} - was successfully added to your Address book`,
          buttons: ['Cool!']
      });
      alert.present();
   })
   .catch((error: any) => 
   { 
      console.error('Error saving contact.', error);
   });
}

Implementing the template code

The HTML for the application consists of the following areas:

  • Button for opening the Photo Library application and selecting an image
  • Button for opening Google within an In-App Browser window
  • Geolocation coordinates for the user's current location
  • Details of a contact to be added
  • Button for adding that contact to the application Contacts/Address Book

Angular's NgIf directive is made use of to conditionally display certain parts of the template based on the values assigned to the properties in the class (which we have used the Diagnostics plugin to manage that logic through the returned Promise object for each method).

This template code for the /ion-diagnostic/src/pages/home/home.html looks like the following:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ion Diagnostic
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  

   <button
      ion-button 
      block 
      color="primary"
      *ngIf="isCameraEnabled"
      (click)="selectPictureFromPhotoLibrary()">
      Take a picture
   </button>



   <button
      ion-button 
      block 
      color="primary"
      *ngIf="isWifiEnabled"
      (click)="openLink('http://www.google.com')">
      Open Google in browser window
   </button>



   <ion-item *ngIf="isLocationEnabled">
      <h2>Your location</h2>
      <p>
         Latitude: {{ latitude }}<br>
         Longitude: {{ longitude }}
      </p>
   </ion-item>



   <ion-item *ngIf="isContactsEnabled">
      <section>
         <img *ngIf="isCameraAvailable" [src]="isImageTaken">
         <h2>John Doe</h2>
         <p>
         	Nickname: JD<br>
         	Address: 1 Anywhere Place, Anywhere<br>
         	Mobile: 01234567890<br>
         	Email: john@johndoe.com<br>
         </p>
      </section>
   </ion-item>




   <button
      ion-button 
      block 
      padding-top 
      padding-bottom 
      color="primary"
      (click)="saveContact({ firstname: 'John', surname: 'Doe', nickname: 'JD', address : '1 Anywhere Place, Anywhereville', mobile: '01234567890', email : 'johndoe@johndoe.com' })">
      Store this contact
   </button>


</ion-content>

The HTML should be fairly self-explanatory and straightforward so I'm not going to devote time to going through each block step-by step.

I will however draw your attention to the <img> tag which relies on the value of the isCameraAvailable property to be conditionally displayed as well as using a [src] property binding to display the retrieved Photo Library image as a base64 data URI which is assigned to the isImageTaken property:

<img *ngIf="isCameraAvailable" [src]="isImageTaken">

Lastly, the Store this contact button uses a click event which supplies a JSON object of contact details as a parameter for the saveContact method (broken over multiple lines for readability in the following snippet):

(click)="saveContact({ firstname: 'John', 
                       surname: 'Doe', 
                       nickname: 'JD', 
                       address : '1 Anywhere Place, Anywhere', 
                       mobile: '01234567890', 
                       email : 'johndoe@johndoe.com' })"

If we now build and deploy this application to our iOS device (in the following screenshots this is an iPhone 5C) we should see screen captures akin to the following:

Upon initial launch of the application the user will be prompted for permission to access Location Services.

The first time the Camera and Contacts features are requested additional prompts will be displayed requesting permission from the user:

Once the contact has been added this can be accessed in the Contacts/Address Book application like so:

All good stuff!

And it's at this point that we're done!

In Summary

The Ionic Native Diagnostic plugin makes determining whether particular devices features are available and/or enabled/authorised for use easier to accomplish.

As I mentioned previously I've only touched upon a small subset of the methods provided by this plugin and I recommend my readers look through the online documentation to learn more about the quite impressive range of methods that are available (for example, handler methods for listening to state changes for particular device features such as bluetooth or location services).

Throughout this tutorial you've hopefully gained an understanding of how the diagnostics plugin can be used to 'sniff out' the availability of certain device features and provide a foundation from which developers can respond to.

There's certainly a lot more that can be added to here but I'll leave that as an exercise for you the reader to undertake! ;)

I hope you enjoyed this article and please feel free to share your thoughts, findings or suggestions in the comment section below.

If you enjoyed what you've read here then please sign up to my mailing list and, if you haven't done so already, take a look at my e-book: Mastering Ionic for further information about using plugins and components within Ionic.

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top