Feeds

Author

Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Oct 27 2016
Oct 27

One of the pain points in hybrid app development is data persistence & data storage. Though LocalStorage can be used for storing less critical data like cache, devs usually look at SQLite for consistent data storage backend. SQLite works fine for both the platforms (Android & iOS). 

In this post we discuss how to efficiently work with SQLite using a simple factory that can be used for doing simple operations on your SQLite Database. 

SQLite can be accessed natively only, so you need to install the cordova plugin for SQLite.

Download ngCordova dependancies

bower install ngCordova

Include ng-cordova.min.js in your index.html file before cordova.js and after your AngularJS / Ionic file (since ngCordova depends on AngularJS).

<script src="http://www.qed42.com/blog/sqlite-data-factory-ionic/lib/ngCordova/dist/ng-cordova.js"></script>
<script src="http://www.qed42.com/blog/sqlite-data-factory-ionic/cordova.js"></script>

Inject as an Angular dependency

angular.module('myApp', ['ngCordova'])

Install SQLite Cordova Plugin

cordova plugin add https://github.com/litehelpers/Cordova-sqlite-storage.git

Declare a global variable 

var db = null;

So that 'db' is accessible through our the scope the app.

In your app.js :-

if (window.cordova) {
    $rootScope.showHeader = false;
    db = $cordovaSQLite.openDB({ name: 'myapp.db', location: 'default' });
} else {
    db = window.openDatabase("myapp.db", "1.0", "My app", -1);
}

'myapp.db' is the name of your DB. The github page for cordova-sqlite-storage plugin have examples on how to do different operations on the database, below is a simple factory that can make these operations clean and readable:

.factory('DBA', function($cordovaSQLite, $q, $ionicPlatform) {
        var self = this;
        self.query = function(query, parameters) {
            parameters = parameters || [];
            var q = $q.defer();
            $ionicPlatform.ready(function() {
                $cordovaSQLite.execute(db, query, parameters)
                    .then(function(result) {
                        q.resolve(result);
                    }, function(error) {
                        q.reject(error);
                    });
            });
            return q.promise;
        }
        self.getAll = function(result) {
            var output = [];
            for (var i = 0; i < result.rows.length; i++) {
                output.push(result.rows.item(i));
            }
            return output;
        }
        self.getById = function(result) {
            var output = null;
            output = angular.copy(result.rows.item(0));
            return output;
        }
        return self;
    })
    .factory('Data', function($cordovaSQLite, DBA) {
        var self = this;
        self.all = function() {
            return DBA.query("SELECT key, value FROM your_table")
                .then(function(result) {
                    return DBA.getAll(result);
                });
        }
        self.get = function(key) {
            var parameters = [key];
            return DBA.query("SELECT key , value FROM your_table WHERE key = (?)", parameters)
                .then(function(result) {
                    return DBA.getById(result);
                });
        }
        self.add = function(obj) {
            var parameters = [obj.key, obj.name];
            return DBA.query("INSERT INTO your_table (key , value) VALUES (?,?)", parameters);
        }
        self.remove = function(obj) {
            var parameters = [obj.key];
            return DBA.query("DELETE FROM your_table WHERE key = (?)", parameters);
        }
        self.update = function(oldkey, newDataObj) {
            var parameters = [newDataObj.key, newDataObj.value, oldkey];
            return DBA.query("UPDATE your_table SET key = (?), value = (?) WHERE key = (?)", parameters);
        }
        return self;
    })

Disclaimer -- This factory code is for demonstration only, please sanitise and secure your user inputs. Additionally, you can make "your_table" dynamic depending on your use-case.

You may use this factory and do the CRUD operations

Add name in db  :-

var nameObj = {};
nameObj.key = "name ";
nameObj.name = "Abhay Kumar";
Data.add(nameObj);

Get name :-

Data.get("name").then(function(result) {
    userName = result.value;
});

Update name :-

var  nameObj = {};
nameObj.key = "name";
nameObj.value = "QED42";
Data.update("name", nameObj).then(function(result) {
    console.log("Result :", result);
})

Delete name :-

var nameObj = {};
nameObj.key = "name";
Data.remove(nameObj).then(function(result) {
    console.log("Result :", result);
});

Hope you find it useful, Let us know in Comments if you extend this factory or have a better one!

Oct 20 2016
Oct 20

Android Permission Provisioning has changed recently, If an app is using Android SDK API level 22 or below, users are asked for all the permissions in bulk at the time of installation i.e. Without granting permission user can not install the app. Now with Android 6's security patch called Run Time Permission (API level 23 and above) the app while in use can request for specific permission when the need arise ( similar to iOS ) e.g. you will be asked for the location's permission when you actually try to access the location of the device.

To work with runtime permissions on Ionic, you would need to install Cordova diagnostic plugin which gives you the function to fetch the status of the native api's exposed to the app. If a certain permission is mandatory for you app you can prompt the user to grant access to proceed. Further you have specific functions for granting permissions.

Install the plugin

cordova plugin add cordova.plugins.diagnostic

To avail the features of Run Time Permission, you have to build the app with Android platform 6.

Check you current Android platform version

ionic platform

If the version of your Android's Platform is below 5, you will need to update it to 5 or above.

Remove android platform:

ionic platform remove android

Install Android platform version 5 or above:

ionic platform add [email protected]

In config.xml, set the target of sdk version to 23

<preference name="android-targetSdkVersion" value="23" />

So far we are all set to ask user's for permission on the fly. We will have to call a functions to fetch the status of particular permission for the app. On the basis of the status we will ask the user to grant permissions or ignore it.

Add the following function in your app.js in $ionicPlatform.ready() function.

This will make $rootScope.checkPermission() global and you can call it whenever you wish to check if the user has given the permission to fetch device's location.

$rootScope.checkPermission = function() {
  setLocationPermission = function() {
    cordova.plugins.diagnostic.requestLocationAuthorization(function(status) {
      switch (status) {
        case cordova.plugins.diagnostic.permissionStatus.NOT_REQUESTED:
          break;
        case cordova.plugins.diagnostic.permissionStatus.DENIED:
          break;
        case cordova.plugins.diagnostic.permissionStatus.GRANTED:
          break;
        case cordova.plugins.diagnostic.permissionStatus.GRANTED_WHEN_IN_USE:
          break;
      }
    }, function(error) {}, cordova.plugins.diagnostic.locationAuthorizationMode.ALWAYS);
  };
  cordova.plugins.diagnostic.getPermissionAuthorizationStatus(function(status) {
    switch (status) {
      case cordova.plugins.diagnostic.runtimePermissionStatus.GRANTED:
        break;
      case cordova.plugins.diagnostic.runtimePermissionStatus.NOT_REQUESTED:
        setLocationPermission();
        break;
      case cordova.plugins.diagnostic.runtimePermissionStatus.DENIED:
        setLocationPermission();
        break;
      case cordova.plugins.diagnostic.runtimePermissionStatus.DENIED_ALWAYS:
        setLocationPermission();
        break;
    }
  }, function(error) {}, cordova.plugins.diagnostic.runtimePermission.ACCESS_COARSE_LOCATION);
};
Run time location permission

Here is a link of the code snippet.

Of course, you can choose to skip all this and stick to sdk target version 22, but you will miss out the new cool feature of Android 6 and amazing user experience. 

Sep 19 2016
Sep 19

If you have been building Ionic / Cordova apps, you might have noticed your app breaking with public release of iOS 10 launched recently. Common symptom of apps suffering from this issue is that the app continues to work as expected on Android, uptill iOS 9 but doesn't launch / load on iOS 10.

This is because of the newly introduced Content Security policy.

Content Security policy is used to prevent cross-site scripting (XSS). It is now supported by all modern browsers.

Browser Support

CSP provides a standard method for website owners to declare approved origins of content that browsers should be allowed to load on that website.

CSP comprises of multiple directives each separated by a ‘;’

If your Ionic / Cordova app does not load data from remote content / stuck on splash screen on iOS 10, this could be the issue you must be facing and the fix is to add the below meta tag in your index.html

<meta http-equiv="Content-Security-Policy" content="default-src gap://ready file://* *; script-src 'self' 'unsafe-inline' 'unsafe-eval' *; style-src 'self' 'unsafe-inline' *”>

Below is a brief description of the attributes: 

  • default-src :  default policy for CSP.
  • gap://ready file://* : is required to allow the for loading remote content in our app iOS 10.
  • script-src : Defines the valid sources of Javascript to be loaded.
  • unsafe-inline: Allows use of inline source elements such as style attribute, onclick, or script tag bodies.
  • unsafe-eval : Allows unsafe dynamic code evaluation such as JavaScript eval().
  • self : Allows loading resources from the same origin (same scheme, host and port).
  • style-src : Defines valid sources of stylesheets.

Disclaimer: Please use it in line with your security considerations and evaluations.

Mar 19 2016
Mar 19

Google Maps are changing the way we see the world. Lets change the way we look at Google Maps (smile)

Google Maps are a perfect match when you are dealing with location based apps. In this article we will learn to integrate the Google Maps api and Google Places api in our Ionic App and look at the solution to long press issue that Ionic suffers from and how to resolve that. 

Get Ionic running:

  • Make sure you have node.js installed on your system
  • $ sudo npm install -g cordova
  • $ sudo npm install -g ionic

Create a new Ionic Application:

ionic start googlePlacesDemo blank
cd googlePlacesDemo/

Get list of all the platforms added in your application:

ionic platform

you will see the list of all the installed platforms

Example :
Installed platforms: ios 3.8.0
Available platforms: amazon-fireos, android, blackberry10, browser, firefoxos, osx, webos

Add Android and iOS platforms to you app:

ionic platform add android
ionic platform add ios

Following plugins will be installed once any platform is added:

ng-cordova is an AngularJs Service which integrates cordova plugins into IONIC Applications. Download ng-cordova.master.zip from here and place the ng-cordova.min.js in the www/js. ngCordova depends on AngularJS  . In your index.html , place ng-cordova.min.js before cordova.js and after AngularJs/ Ionic files.

<script src="http://www.qed42.com/blog/ionic-google-places-api/lib/ngCordova/dist/ng-cordova.js"></script>
<script src="http://www.qed42.com/blog/ionic-google-places-api/cordova.js"></script>

Inject ngCordova as an angular dependency in your angular module

angular.module('myApp', ['ngCordova'])

Test the app on the browser

ionic serve --lab

Lets add google places api in your index.html

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=places,geometry&sensor=false"></script>

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="http://www.qed42.com/blog/ionic-google-places-api/lib/ionic/css/ionic.css" rel="stylesheet">
    <link href="http://www.qed42.com/blog/ionic-google-places-api/css/style.css" rel="stylesheet">
    <!-- google places api -->
    <script src="http://maps.googleapis.com/maps/api/js?sensor=false&amp;libraries=places"></script>
    <!-- google places api ends -->
    <!-- ionic/angularjs js -->
    <script src="http://www.qed42.com/blog/ionic-google-places-api/lib/ionic/js/ionic.bundle.js"></script>
    <!-- cordova script (this will be a 404 during development) -->
    <script src="http://www.qed42.com/blog/ionic-google-places-api/js/ng-cordova.min.js"></script>
    <script src="http://www.qed42.com/blog/ionic-google-places-api/cordova.js"></script>
    <!-- your app's js -->
    <script src="http://www.qed42.com/blog/ionic-google-places-api/js/app.js"></script>
</head>

<body ng-app="starter">
    <ion-pane>
        <ion-header-bar class="bar-stable">
            <h1 class="title">Google Places Demo  </h1>
        </ion-header-bar>
        <ion-content ng-controller="MapCtrl">
            <div class="item item-input controls">
                <i class="icon ion-search placeholder-icon"></i>
                <input id="pac-input" type="text" placeholder="Search Location" data-tap-disabled="true" ng-model="search">
            </div>
            <div id="map" data-tap-disabled="true"></div>
        </ion-content>
    </ion-pane>
</body>

</html>

app.js

angular.module('starter', ['ionic'])
    .run(function($ionicPlatform) {
        $ionicPlatform.ready(function() {
            if (window.cordova && window.cordova.plugins.Keyboard) {
                cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
                cordova.plugins.Keyboard.disableScroll(true);
            }
            if (window.StatusBar) {
                StatusBar.styleDefault();
            }
        });
    })
    .controller('MapCtrl', ['$scope', function($scope) {
        function initialize() {
            var mapOptions = {
                center: { lat: 28.613939, lng: 77.209021 },
                zoom: 13,
                disableDefaultUI: true,// DISABLE MAP TYPE
                scrollwheel: false
            };
            var map = new google.maps.Map(document.getElementById('map'),
                mapOptions);

            var input = /** @type {HTMLInputElement} */ (
                document.getElementById('pac-input'));

            // Create the autocomplete helper, and associate it with
            // an HTML text input box.
            var autocomplete = new google.maps.places.Autocomplete(input);
            autocomplete.bindTo('bounds', map);

            map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);

            var infowindow = new google.maps.InfoWindow();
            var marker = new google.maps.Marker({
                map: map
            });
            google.maps.event.addListener(marker, 'click', function() {
                infowindow.open(map, marker);
            });

            // Get the full place details when the user selects a place from the
            // list of suggestions.
            google.maps.event.addListener(autocomplete, 'place_changed', function() {
                infowindow.close();
                var place = autocomplete.getPlace();
                if (!place.geometry) {
                    return;
                }

                if (place.geometry.viewport) {
                    map.fitBounds(place.geometry.viewport);
                } else {
                    map.setCenter(place.geometry.location);
                    map.setZoom(17);
                }

                // Set the position of the marker using the place ID and location.
                marker.setPlace( /** @type {!google.maps.Place} */ ({
                    placeId: place.place_id,
                    location: place.geometry.location
                }));
                marker.setVisible(true);

                infowindow.setContent('<div><strong>' + place.name + '</strong><br>' +
                    'Place ID: ' + place.place_id + '<br>' +
                    place.formatted_address + '</div>');
                infowindow.open(map, marker);
            });
        }

        // Run the initialize function when the window has finished loading.
        google.maps.event.addDomListener(window, 'load', initialize);
    }])

style.css

#map {
    width: 100%;
    height: 100%;
}

.controls {
    border: 1px solid transparent;
    border-radius: 2px 0 0 2px;
    box-sizing: border-box;
    position: absolute;
    line-height: 22px;
}

#pac-input {
    background-color: #fff;
    font-family: Roboto;
    font-size: 15px;
    font-weight: 300;
    padding: 0 11px 0 13px;
    text-overflow: ellipsis;
    width: 90%;
    margin-bottom: 0;
    line-height: 15px;
    font-weight: bold;
    margin-left: 10%;
}

#pac-input:focus {
    border-color: #4d90fe;
}

.pac-container {
    font-family: Roboto;
}

#type-selector {
    color: #fff;
    background-color: #4d90fe;
    padding: 5px 11px 0px 11px;
}

#type-selector label {
    font-family: Roboto;
    font-size: 13px;
    font-weight: 300;
}

Now run the app on the browser:

ionic serve --lab

You will see it work as expected in the browser.

Now run the app on Android Device: 

ionic run android

Issue with Selecting Auto Complete suggestions

All Set, you might assume. There is a problem though, you will have to long press the autocomplete option to actually get it selected. The issue is that Gmap dynamically adds elements that need the data-tap-disabled property, so you'll have to manually add the property after google has added these elements to the dom.

To get it working on the Android device you need to add the following function in your controller:

$scope.disableTap = function() {
                var container = document.getElementsByClassName('pac-container');
                angular.element(container).attr('data-tap-disabled', 'true');
                var backdrop = document.getElementsByClassName('backdrop');
                angular.element(backdrop).attr('data-tap-disabled', 'true');
                angular.element(container).on("click", function() {
                    document.getElementById('pac-input').blur();
                });
            };

add disableTap() function in the input type 

<input id="pac-input" type="text" placeholder="Search Location" data-tap-disabled="true" ng-change='disableTap()' ng-model="search">

Now run the App on the Android Phone. Voila!  (smile)

For live demo please visit this github link

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web