Page 1 of 1

Hybrid Mobile Apps: Intro to Cordova with Ionic Framework Rate Topic: -----

#1 Dogstopper  Icon User is offline

  • The Ninjaducky
  • member icon

Reputation: 2948
  • View blog
  • Posts: 11,217
  • Joined: 15-July 08

Posted 27 April 2015 - 08:47 AM

Hybrid Mobile Apps: Intro to Cordova with Ionic Framework

What is Cordova?
Cordova is a Javascript library that allows us to access native functions on the device, such as GPS, accelerometer, and camera. There are nearly 1000 plugins at the time of writing. A full list can be found here. However, since I use ionic framework for these tutorials, I am going to use a subset of these plugins known as ngCordova. These give us nice AngularJS wrappers around some of the most common plugins.

For this tutorial, we are going to make a contacts list app. In part 1, we are going to focus on retrieving a list of contacts and using Angular's local storage. Full code for this tutorial can be found at my Github. Note that this tutorial assumes that have read and understand the previous tutorial in this series.

The completed app will list contacts and do search:
Attached Image
Attached Image

Plugins
First, you need to generate a new ionic project:
ionic start CordovaContacts blank



This will generate the basic file structure scaffolding that we want. Next, we need to install ngCordova, the corona contacts plugin, and the Angular-spinkit component (which we use to make a spinning animation).
bower install ngCodova --save
cordova plugin add org.apache.cordova.contacts 
bower install angular-spinkit --save



Setting up the app and tabs
Let's dive into the code. First, we need to modify index.html to include all of the needed JS and CSS files. In this project, we are going to have 3 JS files - app.js, contacts.controllers.js, and contacts.services.js. Also, we want to set up the navigation bar just like we did in the last tutorial:

index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title></title>

    <link href="lib/ionic/css/ionic.css" rel="stylesheet">
    <link href="css/style.css" rel="stylesheet">

    <!-- ionic/angularjs js -->
    <script src="lib/ionic/js/ionic.bundle.js"></script>

    <!-- cordova script (this will be a 404 during development) -->
    <script src="lib/ngCordova/dist/ng-cordova.js"></script>
    <script src="cordova.js"></script>

    <!-- Angular Spinkit -->
    <link rel="stylesheet" href="lib/angular-spinkit/build/angular-spinkit.min.css">
    <script src="lib/angular-spinkit/build/angular-spinkit.min.js"></script>
    
    <!-- your app's js -->
    <script src="js/app.js"></script>
    <script src="js/contacts.controllers.js"></script>
    <script src="js/contacts.services.js"></script>
</head>

<body ng-app="contacts">
    <ion-nav-bar class="bar-stable">
        <ion-nav-back-button>
        </ion-nav-back-button>
    </ion-nav-bar>
    <!--
      Recall that the ion-nav-view is what the $stateProvider injects each template
      into. It holds its own navigation stack.
    -->
    <ion-nav-view></ion-nav-view>
</body>

</html>



The only thing to really note here is the ng-app="contacts" directive, which is where we name our app. In addition, we set up the navigation stack. Next, we are going to set up app.js, which looks extremely familiar to the last tutorial. We are going to set up a set of states that will be used in our tab layout. For now, we only have a single state, but later, we are going to add a state that allows us to create a contact.

Let's first start by setting up the angular module:

app.js
angular.module('contacts', ['ionic', 'contacts.controllers'])

.run(function ($ionicPlatform) {
    $ionicPlatform.ready(function () {
        // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
        // for form inputs)
        if (window.cordova && window.cordova.plugins.Keyboard) {
            cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
        }
        if (window.StatusBar) {
            StatusBar.styleDefault();
        }
    });
})



Here, we name our module "contacts" and tell angular that we need to load the "ionic" and "contacts.controllers" modules. The "contacts.controllers" module contains the controllers for our app.

Next, let's set up the tabs.

app.js
.config(function ($stateProvider, $urlRouterProvider) {
    $stateProvider

    .state('tab', {
        url: '/tab',
        abstract: true,
        templateUrl: 'templates/tabs.html'
    })

    .state('tab.home', {
        url: '/home',
        views: {
            'tab-home': {
                templateUrl: 'templates/tab-home.html',
                controller: 'ContactsHomeController'
            }
        }
    });
    // if none of the above states are matched, use this as the fallback
    $urlRouterProvider.otherwise('/tab/home');
});



We do the same thing as we did in the last app by setting up an abstract state that defines a tabs template. The "tab.home" state inherits from the "tab" state and is located at "tab/home". It specifies a view and controller to be injected into the tabs template. At this point, we should set up the tabs.html file to set up the tab.

template/tabs.html:
<ion-tabs class="tabs-icon-top tabs-color-active-positive">

  <!-- View Contacts Tab -->
  <ion-tab title="View" icon-off="ion-ios-home-outline" icon-on="ion-ios-home" href="#/tab/home">

    <!-- Set up a view with a name so that we can inject the tab.home state here -->
    <ion-nav-view name="tab-home"></ion-nav-view>

  </ion-tab>

</ion-tabs>



First, we set up our tab bar and set its CSS attributes. These specify that we want the tab icon to appear above the text and that we want the bar to appear blue (positive color). Then we set up the first tab with the text "View" and the image to be "ion-ios-home" when we are on that tab and "ion-ios-home-outline" when the tab is not selected. We also indicate that when it's selected, we want to load the #/tab/home state that was specified in app.js. Inside of the tab, there is an ion-view into which the state provider injects the "tab-home" state.

Setting up our tab-home state
As usual, let's set up the template file for this state. First lets set up the basic stuff before grabbing the contacts.

tab-home.html
<ion-view view-title="Contacts" style="background-color: rgba(216, 212, 225, 0.44)">
    <ion-content>
        <label class="item item-input">
            <i class="icon ion-search placeholder-icon"></i>
            <input type="search" placeholder="Search" ng-model="search">
        </label>
        <circle-spinner ng-show="loading"></circle-spinner>
        <!-- Going to put the contacts here -->
    </ion-content>
</ion-view>



What this is doing is setting up a scroll view with a search bar at the top. We set the ng-model to be called "search". This is going to be useful for filtering the list later. The <circle-spinner> is from the spinkit. We'll need this as loading the initial contacts takes a while. The ng-show directive looks in the associated model for a variable called "loading" and if it's true, this section will display.

Now, let's add the contacts list. Each contact will appear as a card with a list inside of it with phone numbers and emails in it. Most of this is CSS magic, so I'll show the code and then go over the important parts of it.
        <div class="card" ng-repeat="contact in contacts | filter:search" style="padding: 0" ng-show="!loading">
            <div class="item item-divider" ng-style="{ 'background-color': getButtonColor($index) }">
                <h3>{{ contact.name.formatted }}</h3>
            </div>
            <div class="item" style="padding:0">
                <div class="list">
                    <div class="item" style="background-color: #fbfbfb">
                        <h2>Phone Numbers:</h2>
                        <p ng-repeat="number in contact.phoneNumbers">
                            <b>{{ number.type }}</b>: {{ number.value }}
                        </p>
                    </div>
                    <div class="item" style="background-color: #fbfbfb">
                        <h2>Emails:</h2>
                        <p ng-repeat="email in contact.emails">
                            <b>{{ email.type }}</b>: {{ email.value }}
                        </p>
                    </div>
                </div>
            </div>
        </div>



The ng-repeat="contact in contacts | filter:search directive is requesting the list of contacts from the controller and piping each one through the filter. This filter will search against the ng-model we set up in the search bar. It will match any field - the name, the phone numbers, and the emails. Next, the ng-style="{ 'background-color': getButtonColor($index) }" is just making the card title a different color (this is also set up in the controller. It is basically like adding a style attribute, except that it allows you to use the angular controller directly. The $index variable is accessible inside of any "ng-repeat" directive. The rest of this code is stuff we have seen before. The handlebar syntax ({ email.value }) allows us to pull values out of the contact object.

The contact object has a lot of fields that we can use. The full list from https://github.com/a...lugin-contacts/ :

Quote

  • id: A globally unique identifier. (DOMString)
  • displayName: The name of this Contact, suitable for display to end users. (DOMString)
  • name: An object containing all components of a persons name. (ContactName)
  • nickname: A casual name by which to address the contact. (DOMString)
  • phoneNumbers: An array of all the contact's phone numbers. (ContactField[])
  • emails: An array of all the contact's email addresses. (ContactField[])
  • addresses: An array of all the contact's addresses. (ContactAddress[])
  • ims: An array of all the contact's IM addresses. (ContactField[])
  • organizations: An array of all the contact's organizations. (ContactOrganization[])
  • birthday: The birthday of the contact. (Date)
  • note: A note about the contact. (DOMString)
  • photos: An array of the contact's photos. (ContactField[])
  • categories: An array of all the user-defined categories associated with the contact. (ContactField[])
  • urls: An array of web pages associated with the contact. (ContactField[])


You can look at the docs for more object information.

Now, let's set up the controller:

contacts.controllers.js
angular.module('contacts.controllers', ['ngCordova', 'angular-spinkit', 'contacts.services'])

.controller('ContactsHomeController', ['$scope', '$cordovaContacts', '$ionicPlatform', '$localstorage',
                                          function ($scope, $cordovaContacts, $ionicPlatform, $localstorage) {
        $scope.loading = true;
        $scope.contacts = [{}]
        $ionicPlatform.ready(function () {
            var options = new ContactFindOptions();
            options.filter = "";
            options.multiple = true;
            var fieldsToFilter = ['name', 'desiredName'];

            $cordovaContacts.find(options).then(
                function (result) {
                    $scope.contacts = $scope.filterNonNull(result);
                    $scope.loading = false;
                },
                function (error) {

                }
            );
        });

        var colorArray = ['#f0b840', '#43cee6', '#4a87ee', '#66cc33', '#ef4e3a', '#8a6de9'];
        $scope.getButtonColor = function (index) {
            return colorArray[index % 6];
        };
        $scope.filterNonNull = function(results) {
              return results.filter(function(value) {
                  return value.name.formatted.trim() !== "" &&
                      (value.phoneNumbers.length > 0 || value.emails.length > 0);
              });
        };
}]);


You should understand most of this from the last tutorial. The cordova part is the new stuff:
        $scope.contacts = [{}]
        $ionicPlatform.ready(function () {
            var options = new ContactFindOptions();
            options.filter = "";
            options.multiple = true;
            var fieldsToFilter = ['name', 'desiredName'];

            $cordovaContacts.find(options).then(
                function (result) {
                    $scope.contacts = $scope.filterNonNull(result);
                    $scope.loading = false;
                },
                function (error) {

                }
            );
        });



Firstly, for every cordova plugin, we need to wait until the ionic platform is ready. The ContactFindOptions object contains information about the contacts that we want to find. In this case, we want them all. We then ask cordova to find contacts based on that object and then when it finishes, we set the contacts variable to equal the list of contacts return. Here I made a helper function called filterNonNull that removes contacts that either have no title or no contact information. We then set "$scope.loading" to be false, which makes the spinkit disappear and causes the list to appear.

That's basically it! Most cordova plugins are as easy to implement as this one is, so it's really fast to do cross platform development and still use native functionality. You will notice that it takes a really long time to load contacts though, so let's make a simple service to cache contacts between runs. This makes only the FIRST run be slow. And after that, the list will simply update when it is done loading (which usually appears seamlessly).

To do this, let's make a service that wraps local storage. There already exist a really great service at http://learn.ionicfr...s/localstorage/ , so I'm just going to use that in this tutorial.

contacts.services.js
angular.module('contacts.services', [])

.factory('$localstorage', ['$window', function ($window) {
    return {
        set: function (key, value) {
            $window.localStorage[key] = value;
        },
        get: function (key, defaultValue) {
            return $window.localStorage[key] || defaultValue;
        },
        setObject: function (key, value) {
            $window.localStorage[key] = JSON.stringify(value);
        },
        getObject: function (key) {
            return JSON.parse($window.localStorage[key] || '[{}]');
        }
    }
}]);



This is a simple API that allows us to store JSON objects into local app storage. To consume this service in our controller is really simple. First, edit the $scope.contacts variable to the following:
$scope.contacts = $localstorage.getObject('contact_list');



This sets the initial contact list to be our cached object that we are calling "contact_list". The service will return an empty array in the event that there are no values to be found. The only thing you have to do now is to update the cache whenever we finish loading:

                function (result) {
                    $scope.contacts = $scope.filterNonNull(result);
                    $scope.loading = false;
                    $localstorage.setObject('contact_list', result);
                },



That's about it! I hoped you thought cordova was fun, easy, and portable!

Is This A Good Question/Topic? 1
  • +

Page 1 of 1