Ionic is used for hybrid, cross-platform mobile app development utilizing HTML and JavaScript to present the user interface. This provides the developer with reusable code that can be exectued across platforms. It pairs with the PhoneGap/Cordova plugins to enable native access to each platform. The objective of this excercise is to show developers how the Manatee Works Barcode Scanner SDK may be used in a real-life app that is built using the Ionic cross-platform framework.

<TLDR> Here is your download link https://github.com/manateeworks/manateeworks-barcodescanner-ionic2-starter.</TLDR>

Ionic 2, the latest version of Ionic, has embraced TypeScript. It is a strict superset of JavaScript that adds optional static typing and class-based object-oriented programming to the language. Its downside is the need to "compile" it to regular JavaScript.

First Things First

You should first install and configure the neccessary build tools for developing Ionic apps:

 

You may now clone our project to a computer location of your choosing:

git clone https://github.com/manateeworks/manateeworks-barcodescanner-ionic2-starter.git mwIonic2Blog

Pasted image at 2017_03_16 10_31 PM_298794634

This should create a folder mwIonic2Blog on your computer, where the files contained in this project will be copied. Change Directory (cd) to that folder:

cd mwIonic2Blog

The project dependencies are set in the package.json file. Throughout the project lifecycle, dependices may change. Team Manatee works hard to keep projects up to date, but it is quite possible that you may need to bump a version of the dependencies to a more recent one:

package.json_2090296229

Our project depends on several Cordova plugins, including the Manatee Works Cordova plugin. Several other "default" plugins are installed when first creating a clean Ionic 2 app. To install all dependancies, excecute:

npm install

npm-install_820409183

If there are errors, npm should give resonable debugging info. Resolve any issues.

After installing all npm dependencies, proceed to the build the ionic project:

npm run ionic:build

This will build the Ionic app. It is then time to reset the state, so that the platforms will be installed:

ionic state reset

This will add the platforms, and the Cordova plugins:

state-reset_1037001505

 

Platform folders will be created. For each platform there are slight modifcations required.  

iOS requires signing profiles for overall app testing/publishing. To create a signing profile, create one on Apple Developer site. The easiest way to use a signing profile is through XCODE. Navigate to folder "platforms/ios" and open the .xcodeproj.

With a signing profile established, build the Ionic app on the desired platform with the --livereload option active:

ionic run ios -l

This will compile the TypeScript code into normal JavaScript and copy the files to a device for execution.

 

Overview of the solution

You will notice that the application's files may be found in the src/app folder. "app.module.ts"  inlcudes the required components. The manatee-scanner is provided; include it with:

import { ManateeScanner} from '../providers/manatee-scanner';

In the providers folder you should see two providers: list-factory.ts and manatee-scanner.ts. While list-factory is an important part of the demo app and its list management, manatee-scanner enables barcode scanning. 

providers/manatee-scanner.ts

The provider straightforward, with one method exposed, config(). It expects a config function to be passed as an input parameter, which will then be called from our component to setup the scanner with parser parameters. The provider manatee-scanner also exposes a scanner property, which is the mwScanner object injected in our global namespace by the Cordova framework:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

declare var mwbScanner:any;

/*
  Generated class for the ManateeScanner provider.

  See https://angular.io/docs/ts/latest/guide/dependency-injection.html
  for more info on providers and Angular 2 DI.
*/
@Injectable()
export class ManateeScanner {

  mwInit : boolean;
  public scanner : any;

  constructor(public http: Http) {
    console.log('Hello ManateeScanner Provider');
    if(typeof mwbScanner != 'undefined' && typeof mwbScanner.startScanning != 'undefined'){
    	this.scanner = mwbScanner;
    }
    else{
    	this.scanner = {};
    }
  }
  config(setFunc){
  	setFunc.call();
  	console.log('setting the function');
  	this.mwInit = true;

  }

}

app/app.component.ts

The manatee-scanner will be used by the app.component to configure the scanner with a license key and an optional settings array:

import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar, Splashscreen } from 'ionic-native';

import { ManateeScanner} from '../providers/manatee-scanner';
import { HomePage } from '../pages/home/home';


declare var device:any;


@Component({
  templateUrl: 'app.html',
  providers : [ManateeScanner]
})
export class MyApp {
  rootPage = HomePage; //root page

  constructor(platform: Platform,manateeScanner:ManateeScanner) {
    platform.ready().then(() => {
      // Okay, so the platform is ready and our plugins are available.
      // Here you can do any higher level native things you might need.

      manateeScanner.config(()=>{
          manateeScanner.scanner.setCallback((result:any) =>{});
          var mw_c : any =  manateeScanner.scanner.getConstants();
          var settings : any = [
                  {"method" : 'MWBenableZoom', "value" : [true]},
                  // {"method" : 'MWBsetZoomLevels', "value" : [200, 400, 1]},
                  // {"method" : 'MWBsetInterfaceOrientation', "value" : [mw_c.OrientationLandscapeLeft]},
                  // {"method" : 'MWBsetOverlayMode', "value" : [mw_c.OverlayModeImage]}
              ];
          var keys : any = {
              'Android'   : "VALID_ANDROID_KEY",
              'iOS'       : "VALID_IOS_KEY",
              'Win32NT'   : "VALID_WIN_WP8_KEY",
              'windows'   : "VALID_WIN_10_UWP_KEY"
          };
          var key : any = (keys[device.platform])?keys[device.platform]:'';

          return manateeScanner.scanner.setKey(key).then((response:any)=>{
              return manateeScanner.scanner.loadSettings(settings).then((response:any)=>{}).catch((reason:any)=>{
                              console.log(reason)
                          });
          });
        });      

      StatusBar.styleDefault();
      Splashscreen.hide();
    });
  }
}

 

To summarize:

  • The Manatee Barcode Scanner is imported near the top of the file;
  • In the Component decorator, the Scanner is added into the providers array;
  • The provider is setup by calling the config function in the platform.ready promise.

config()

The config function handles the following:

  • Declares the callback function of the scanner result to an empty function. We do this to overwrite the default callback function that comes packed with the manateeworks-scanner Cordova plugin. This will leave us without a callback function, but the scanner returns a promise so will just use that later on.
  • Second, we setup a settings array with the desired settings, and we provide our license keys in the keys array. The device.platform variable should be injected by cordova, so depending on which platform the app currently runs, it will pull in the proper key, when we call setKey
  • Third, we call setKey and then once this is resolved we loadSettings.

pages/history-list.ts

This is the spot where we will invoke the scanner, get it to scan a barcode, and then print that value in our list (and save it on the device). So let's have a look of the structure of the page:

history-list.html

<!--
  Generated template for the HistoryList page.

  See http://ionicframework.com/docs/v2/components/#navigation for more info on
  Ionic pages and navigation.
-->
<ion-header>

  <ion-navbar>
    <ion-title>{{list_name}}</ion-title>
  </ion-navbar>

</ion-header>


<ion-content padding>

  <ion-list> 
	  <ion-item-sliding *ngFor="let item of list_data; let i = index"> 
	    <ion-item (click)="viewData(item)"> {{item.code}} </ion-item>
	    <ion-item-options>
	      <button icon-only color="danger" (click)="removeItem(i)"><ion-icon name="trash"></ion-icon></button>
	    </ion-item-options>
	  </ion-item-sliding>

  </ion-list>


<ion-fab bottom right>
	<button ion-fab (click)="startScanner($event)" >
	<ion-icon [name]="scannerActive"></ion-icon> </button>
</ion-fab>
</ion-content>

If you look closer, a couple of things are clear,

  • We print out the previous scans that have been done with the scanner (obviously this will be empty for a new list);
  • We setup a button that starts the scanner <button ion-fab (click)="startScanner($event)" >

Let's see how this view gets used by it's component:

history-list.ts

import { Component, NgZone } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { ListsFactory} from '../../providers/lists-factory';
import { ManateeScanner} from '../../providers/manatee-scanner';
import { ViewCodePage} from '../view-code/view-code';

@Component({
  selector: 'page-history-list',
  templateUrl: 'history-list.html'
})
export class HistoryListPage {
	list:any;
	id:number;
	list_name:string;
	list_data:any[];
	scannerActive:string ="barcode";	

	constructor(public navCtrl: NavController, public navParams: NavParams, private listsFactory : ListsFactory, private zone: NgZone,private manateeScanner : ManateeScanner) {}

	ionViewDidLoad() {

		this.id = this.navParams.data.listId;

		this.listsFactory.getItem(this.navParams.data.listId).then(list=>{

			this.list = list;
			this.list_name = this.list.name;
			if (this.list.data)
				this.list_data = this.list.data;
			else
				this.list_data = [];
		});
	}
	viewData(item){
		this.navCtrl.push(ViewCodePage,{
			item : item
		});		
	}
	removeItem(item_id){

		this.list_data.splice(item_id,1);
		this.list.data = this.list_data;
		this.listsFactory.setItem(this.id,this.list).then(response=>{
			console.log('saved the list');
		});
	}
	startScanner(event){

		if(typeof this.manateeScanner.scanner != 'undefined' && typeof this.manateeScanner.scanner.startScanning != 'undefined'){
			if (this.scannerActive =="barcode"){
				this.scannerActive = "power";
				this.manateeScanner.scanner.startScanning(0,0,100,50).then(response =>{
				  if(response && response.code){
				    this.zone.run(() => {
				    	this.list_data.push(response);
				    	this.list.data = this.list_data;
				    	this.scannerActive = "barcode";
				    	this.listsFactory.setItem(this.id,this.list).then(response=>{})
		       	
				    });
				  }
				  else{
				    this.zone.run(() => {
				    	this.scannerActive = "barcode";
				    });		      	
				  }
				});
			}
			else{
				this.manateeScanner.scanner.closeScanner();
				this.scannerActive = "barcode";
			}
			
		}
		else{
			this.startDummy();
		}

	}
	startDummy(){
		let timestamp = function(){ return Number(new Date); }
		this.scannerActive = "power";
		let response:any = {
			"code":"mwbScanner not available @ " + timestamp(),
			"location" : {},
			"type" : "dummy"
		};

		this.list_data.push(response);
		this.list.data = this.list_data;
		this.listsFactory.setItem(this.id,this.list).then(response=>{
			this.scannerActive = "barcode";
		})
	}	

}

Now, obviously there's a lot of things going on here, so let's start from the top;

  • the view loads we pull the history list data, from the device (this is the data that we print in the history-list view).
  • we provide methods for deleting a list item and viewing a list item
  • most importantly we provide the startScanner method which is bind to the click event of the button we mentioned earlier in the history-list.html view
  • the startScanner function simply uses the methods provided by mwbScanner which is now available through the property on the provider (this.manateeworks.scanner).
  • the result from the scan is handled in the promise function and we use zone.js to update the view list 
  • there is also a startDummy function, which is used during app development on a browser. Ionic provides a method to run the app on a browser on the development computer, and it's much easier to style the app with Chrome/Firefox/Safari than doing it directly on the device, even though the device is livereloaded each time there is a change in one of the typescript files.

That pretty much covers setting up the scanner and using it. Now let's see the other components that are part of the solution.

pages/home

This is the component that is loaded first, it handles the mutiple lists that we can create as containers for our scan results. In the view of the home component we have:

<ion-header>
  <ion-navbar>
    <ion-title>
      MWB Scanner 
    </ion-title>

  </ion-navbar>
</ion-header>
<ion-content padding>
  <ion-list> 
	  <ion-item-sliding *ngFor="let item of lists; let i = index" #slidingList>
	    <ion-item (click)="navSomewhere(i)"> {{item.name}} </ion-item>
	    <ion-item-options>
	      <button icon-only color="danger" (click)="removeItem(i)" side="right"><ion-icon name="trash"></ion-icon></button>
	      <button icon-only color="danger" (click)="editListModal(i,item,slidingList)"><ion-icon name="create"></ion-icon></button>
	    </ion-item-options>
	  </ion-item-sliding>
  </ion-list>


<ion-fab bottom right>
	<button ion-fab (click)="showNewListModal()">
		<ion-icon name="add"></ion-icon></button> 
</ion-fab>		  

</ion-content>

A couple of things here are worth mentioning:

  • we provide option buttons for each list element, one lets us edit the list's name, the other deletes the list. The option elements are accessed by swiping the list item
  • we provide a button to create a new list
  • when a tap/click occurs on the list item, it navigates to the list-history component by invoking the navSomewhere() method

The component for this view looks like:

import { Component, NgZone } from '@angular/core';
import { ModalController, NavController, ItemSliding } from 'ionic-angular';
import { HistoryListPage } from '../history-list/history-list';
import { ListsFactory} from '../../providers/lists-factory';
import { ListModify} from './listmodify';




@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
	constructor(public navCtrl: NavController,public modalCtrl: ModalController, private zone: NgZone, private listsFactory:ListsFactory) {
		this.listsFactory.getAll().then(lists=>{
			this.lists = lists;
		});
	}
	historyListPage = HistoryListPage;
	lists:any;

	navSomewhere(id){
		this.navCtrl.push(HistoryListPage,{
			listId : id
		});
	}

	showNewListModal(){
		let newListModal = this.modalCtrl.create(ListModify,{});
		newListModal.present();
		newListModal.onDidDismiss(data => {
			if(data)
				this.lists = data.lists;
		})
	}
	editListModal(id,list,slidingList : ItemSliding){
		let editListModal = this.modalCtrl.create(ListModify,{id:id,list:list});
		editListModal.present();
		editListModal.onDidDismiss(data => {
			if(data)
				this.lists = data.lists;

			slidingList.close();
		})

	}

	removeItem(id){
		console.log(id);
		this.listsFactory.removeItem(id).then(result=>{
			this.lists = result;
		})
	}
}

You can notice right away:

  • navSomewhere() method, which will navigate our app to the list-history component,
  • showNewListModal() which will start a modal window that will let us create the list by providing a list's name,
  • editListModal() - presents a modal view for modifying a list. Both showNewListModal and editListModal are based on the listmodify page
  • removeItem() - which deletes the list

home/listmodify

This is a component that we use for showing a modal windows, it's basically a form with a form group (just the name of the list). It takes care of the actions like saving the list and validating the user input. It does that by providing a few methods:

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NavParams, ViewController } from 'ionic-angular';
import { ListsFactory} from '../../providers/lists-factory';

@Component({
	selector: 'list-modify',
	templateUrl: 'listmodify.html'
})
export class ListModify {

editListName: FormGroup;
id:Number;
lists:any;
list:any;
mode:string = 'add';

 constructor(private viewCtrl: ViewController, params: NavParams, formBuilder: FormBuilder, private listsFactory : ListsFactory) {

	this.listsFactory.getAll().then(lists=>{
		this.lists = lists;

	});


	if(params.data.list){
		this.mode = 'edit';
		this.id = params.data.id;
		this.list = params.data.list;
	}


	this.editListName = formBuilder.group({
		listName : [(this.mode=='add')?'':params.data.list.name,Validators.compose([Validators.maxLength(30), Validators.pattern('[a-zA-Z0-9 ]*'), Validators.required])]
	})

 }
 dismiss(){
 	this.viewCtrl.dismiss(false);
 }
 save(){


 	if(this.mode == 'add'){
	 	if(this.editListName.valid){
			let id:any = this.lists.length;
			this.listsFactory.setItem(id,{"name":this.editListName.value.listName}).then(lists=>{
				this.lists = lists;
				this.viewCtrl.dismiss({"lists":this.lists});
			});
	 	}
 	}
 	else{
	 	if(this.editListName.valid){

	 		let list:any = this.list;
	 		
	 		list.name = this.editListName.value.listName;

			this.listsFactory.setItem(this.id,list).then(lists=>{
				this.lists = lists;
				this.viewCtrl.dismiss({"lists":this.lists});
			});
	 	} 		

 	}

 }
}

So if it's a new list, we don't send the list id as a parameter and this automatically clears the form for input. The editlistName form item is a part of a formBuilder group and appropriate validation measures are imposed on it (we want to know if the user is trying to input a list with an empty name, we also limit it to 30 alphanumerical characters)

save() method gives us two options:

  • adding a new list
  • editing an old one.

If it's an old list then we change the listName and we use the listFactory provider to save it. If it's a new list, we just add create a new id and add it with the listFactory provider (action is similar to the edit action).

Last but not least, let's mention the listFactory provider

providers/list-factory.ts

Main thing to mention here is that we employ the help of the storage object.

import { Storage } from '@ionic/storage';

will import the Storage, which we can use in our listFactory as such:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import { Storage } from '@ionic/storage';


/*
  Generated class for the ListsFactory provider.

  See https://angular.io/docs/ts/latest/guide/dependency-injection.html
  for more info on providers and Angular 2 DI.
*/
@Injectable()
export class ListsFactory {

	storage:Storage = new Storage();
	lists:any[] = [];
	list:any;

	constructor(public http: Http) {
		console.log('Hello ListsFactory Provider');


		this.storage.get('lists').then( lists => {
			this.lists = lists;
		}).catch(error =>{
			console.log(error);
		})
	}
	setItem(id,list){
		if(Array.isArray(this.lists))
			this.lists[id] = list;
		else{
			this.lists = [];
		}
		return this.saveLists();


	}
	removeItem(id){
		this.lists.splice(id,1);
		return this.saveLists();
	}
	saveLists(){

		return this.storage.set("lists" , this.lists).then(result =>{
			console.log('response result'+result);
			return this.lists;
		});		

	}
	getItem(id){

		return this.storage.get("lists").then(lists => {
			if(!Array.isArray(lists))
				lists = [];

			this.list = lists[id];
			return this.list;
		});
	}
	getAll(){
        return this.storage.get('lists').then(lists => {

			if(!Array.isArray(lists))
				lists = [];

			this.lists = lists;
			return this.lists;
        });
	}

}

We then add a couple of methods that deal with both adding lists and actually saving items (barcode scan results) to a list

  • setItem() handles both: setting a list or adding items (barcode results) to a list, this can be observed both in listmodify.ts (where we save lists) and history-list.ts where we save barcode results
  • getAll() will pull all the lists
  • removeItem() will remove a list item

Conclusion

When all is said and done in the end we are left with a demo app capable of:

  • Creating lists
  • Invoking a Manatee Works Scanner
  • Storing results from a scan action
  • Viewing results
  • Deleting both results and lists that contain those results

All of this while using Ionic 2 as a platform. The tutorial should show some basic operations and options that an ionic developer has while using our scanner, it's by no means meant as a production app, and it probably contains more than a few bugs, but it does get the point across.

Happy coding with the Manatee Works Barcode Scanner

Develop your own Ionic 2 Demo App that utilizes the Manatee Works Barcode Scanner SDK to store barcode scan results in a list.
scanner,ionic,sublime,xcode
2017-03-20
2017-10-15
Ionic Task List App
Ionic Task List App
ManateeWorks
info@manateeworks.com
Mobile apps need to be increasingly more engaging to succeed. Manatee Works can help. Our mobile barcode scanner SDK is available on all major mobile development platforms.
ManateeWorks