Are you ready to learn how to develop a hybrid app?

Stevica Knezevic, EtonDigital’s web developer with vast experience with JavaScript technologies, will explain you how. The guide shows you how to build hybrid applications using Drupal as a REST server, and Ionic framework for creating a hybrid mobile application.

Do you know the difference between a native mobile app vs. web app? And how are these apps different from hybrid applications?

In short, native applications are coded in a native language, for instance, Objective C for iOS development, and Java for Android, and these applications use native SDK for their specific platform. Native applications run on the device and they are distributed through App stores (e.g. App Store or Google Play).

Web applications are written with HTML, CSS and JavaScript and need web browsers to run in them and interact with users. Web applications run on multiple platforms.

Hybrid applications are built with a combination of HTML, CSS and JavaScript to offer a native webview (UIWebView in iOS or WebView in Android). They run on multiple platforms, can be distributed through App stores, and have access to devices resources such as camera, contacts, accelerometer and other.

Depending on the project you are working on, the business’s needs, the industry and the market, you get to decide which type of mobile applications meets the needs best.

But, as I will guide you through the development of a hybrid mobile app, you might ask: ‘Why should I choose a hybrid app?’.

The good answer is the one in which we consider the benefits of a hybrid app. So, what are the advantages of hybrid apps?

  • Cost and time effectiveness
  • Faster and simpler development
  • Easy to maintain
  • Ability to quickly change UI design without significant changes to business logic code
  • Build multi-platform app with one line of code
  • Access to specific device and OS features
  • Greater freedom during development process
  • Ability to get app in the iOS and Android app stores

But hybrid apps have certain drawbacks related to performance – they depend on the native browser thus they are not faster than the native apps.

Now, let’s see how you can develop a hybrid app using Drupal CMS and Ionic framework. I will show you how to build an Android hybrid app using Apache Cordova.

How to start?

#1 Drupal setup

The first step in the development process is to install Drupal 7.

Download Drupal source from the official Drupal site or use Drush to install it, whichever you prefer. I will not go through the installation process of Drupal as it’s very easy and simple, thus I will show you the steps assuming that you have already installed Drupal.

The next step is to install required modules in order to set up our REST server. We will use the following modules: Services and Libraries.

You can install it with Drush as follows:

 drush dl services libraries && drush en services libraries 

Drush will ask you to install and enable dependencies ‘ctools’ – confirm this with ‘yes’.

The next step is to enable REST Server. Go to the admin panel  – Modules page – and at the bottom of the page check REST Server as showing in the picture below:

enable REST server

Now we can create our REST Server. Go to the Structure page and choose Services, and then click on Add. We have a few required input fields to fill : Name of the endpoint, Server and Path to endpoint.

Structure pageThe example fields should be as follows:

Machine-readable name of the endpoint -> mobile_api

Server -> REST

Path to endpoint ->  api

Add these information, and click Save.

After creating Services, we need to set up Service Resources. Go to ‘Edit Resources’ link next to Our Service, you will see the list of resources, expand each resource and check the one you would like to use.

In our example we’ll be using the following resources:

Node:

  • retrieve
  • index

System:

  • connect

User:

  • retrieve
  • index

Structure Page - Service Resources

Now it’s time to enable Server Response Formatters and Request parsing. Go to the ‘Server’ tab and make sure the following is checked:

Response formatters:

  •     json

Request parsing:

  •     application/json
  •     application/x-www-form-urlencoded

Server Response FormaterrsYou can enable other formatters and parsing options as you wish, but for the purpose of this guide we’ll use only these.

You can test our created resource with REST client:

URL:           http://localhost?q=api/system/connect.json

Actions:      POST

If everything is set up correctly, Response Status should be shown as ‘200 OK’, and you should get the following JSON:


{
    "sessid":"lDHKvQzOu737g5dFI1249vZYyTDQQxamPxUbjbi1RfQ",
    "session_name":"SESSdb67617a77b376ef233a0e3e342ae54c",
    "user":{
        "uid":0,
        "hostname":"127.0.0.1",
        "roles":{
            "1":"anonymous user"
        },
        "cache":0,
        "timestamp":1433243265
    }
}

That’s it, now we have set up the REST server for our mobile app. However, we will not go through the process of developing the Ionic app.

The next step is to create Content, so add some nodes using ‘Base page’ content type, for example Home, Contact and Services.

#2 Ionic Application development

Overview and requirements

In order to install Ionic and Cordova we need to have NodeJS and NPM (Node Package Manager as it’s the default package manager for Cordova) installed. Go to the Node website, and install the node.

To build an Android application you need to install Java on your system and have Android SDK.

We will keep the scope of our app very simple. The app will contain the following:

  •     Dashboard – Welcome Page
  •     Posts – Teasers and full nodes
  •     Node details – Information about created and modified nodes

Create a new Ionic project

Ionic comes with a command line utility to start and build Ionic apps. To install Ionic and Cordova run this command in terminal after Installing NodeJS:

 npm install ionic cordova -g 

We can now create a basic Ionic app – Ionic CLI provides a method for creating an app based on a few starter templates to bootstrap project. In our example we will use a blank template.

 ionic start drupalApp blank 

This will generate the new Ionic project with all the files we need for building the application:

 cd drupalApp && ls

You can see the following structure:


├── bower.json	        // bower dependencies
├── config.xml  	// cordova configuration
├── gulpfile.js 	// gulp tasks
├── hooks       	// custom cordova hooks to execute on specific commands
├── ionic.project       // ionic configuration
├── package.json	// node dependencies
├── platforms   	// iOS/Android specific builds will reside here
├── plugins     	// where your cordova/ionic plugins will be installed
├── scss        	// scss code, which will output to www/css/
└── www         	// application - JS code and libs, CSS, images, etc.

You can view the application live through the browser, and navigate to the root project running the following command:

ionic serve

This command starts a local server (based on express) and allows live-reload via websockets.

When you make changes to the app, you will see those changes immediately in the browser.

Configure the router of the app

Ionic doesn’t use the default angular router, but uses ui-router that helps with managing complex nested states.

We will add router configuration in drupalApp/www/js/app.js

Place the following code below .run method:


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

	$urlRouterProvider.otherwise('/app/dash');

	$stateProvider

  	.state('app', {
    	url: '/app',
    	abstract: true,
    	templateUrl: 'templates/app.html'
  	})
  	.state('app.dash', {
    	url: '/dash',
    	views: {
      	'app-dash': {
        	templateUrl: 'templates/dash.html',
        	controller: 'dashCtrl'
      	}
    	}
  	})
  	.state('app.home', {
    	url: '/home',
    	views: {
      	'app-home': {
        	templateUrl: 'templates/home.html',
        	controller: 'homeCtrl'
      	}
    	}
  	})
  	.state('app.nodeid', {
    	url: '/home/:nodeId',
    	views: {
      	'app-home': {
        	templateUrl: 'templates/node.html',
        	controller: 'nodeCtrl'
      	}
    	}
  	})
  	.state('app.details', {
    	url: '/details',
    	views: {
      	'app-details': {
        	templateUrl: 'templates/details.html',
        	controller: 'detailsCtrl'
      	}
    	}
  	});
  })
  .constant('REMOTE_URL', 'http://local.edblog/api/');

Note: We have added constant to the last line to define the constant which contains remote URL that will be used later in our controllers.

We will create Services using $http service because we need to do a simple HTTP request to remote/local server – as response we will get object.

What we need in our services is $statePaarams service as well because we should get object that will contain key per URL parameter.

Add a new file called services.js by adding the following code:


angular.module('starter.services', [])

  // Ged node content
  .factory('Drupal', function ($http, REMOTE_URL) {
	return {
  	nodes: $http.get(REMOTE_URL + 'node/')
	}
  })

  // Get node content by nid
  .factory('DrupalNode', function ($http, $stateParams, REMOTE_URL) {
	id = $stateParams.nodeId;
	return {
  	node: $http.get(REMOTE_URL + 'node/' + id)
	}
  });

We now have the following structure:


 
├── www                       	  	// www root
   	├── js                    		// our javascript directory
         	└── services.js   	 	// contain our controllers
         	└── app.js             	// already existing file

It’s time to create our controllers.

Add new Javascript file within the existing  js directory, and name it as controllers.js


 
├── www                       	  	// www root
   	├── js                    		// our javascript directory
         	└── services.js   	 	// contain our controllers
         	└── controllers.js   	  // contain our controllers
         	└── app.js             	// already existing file

We have created a new controllers.js file and we should define the new module and new controllers first and then include our services.

Open and add the following code into controllers.js:


angular.module('starter.controller', [])

  .controller('homeCtrl', ['$scope', 'Drupal',
	function ($scope, Drupal) {
  	Drupal.nodes
    	.success(function (data) {
      	$scope.data = data;
    	});
	}])

  .controller('dashCtrl', ['$scope', 'Drupal',
	function ($scope, Drupal) {
  	Drupal.nodes
    	.success(function (data) {
      	$scope.data = data;
    	});
	}])

  .controller('nodeCtrl', ['$scope', 'DrupalNode',
	function ($scope, DrupalNode) {
  	DrupalNode.node
    	.success(function (data) {
      	$scope.data = data;
    	});
	}])

  .controller('detailsCtrl', ['$scope', 'Drupal',
	function ($scope, Drupal) {
  	Drupal.nodes
    	.success(function (data) {
      	$scope.data = data;
    	});
	}]);

We have created new services and controller module and it’s necessary to include them in our main ‘starter’ module.

In app.js make sure to update the following line:

angular.module('starter', ['ionic', 'starter.controller', 'starter.services'])

Next what we should to do is to call these script to our index.html file:


<script src="js/controllers.js"></script>
<script src="js/services.js"></script>

Let’s get started with HTML Templates

Now we should code the views of our app – open the file in index.html., remove all within the body tag, and add the following piece of code:


<ion-nav-bar class="bar-assertive">
      <ion-nav-back-button>
      </ion-nav-back-button>
</ion-nav-bar>
<ion-nav-view></ion-nav-view>

Create a template directory called templates and within create the following files:

app.html, dash.html, home.html, node.html, details.html


├── www                   		// www root
   	├── templates          	// new created directory
         	└── app.html    	// contains tab content
         	└── dash.html   	// contains dashboard content
         	└── home.html   	// contains list of nodes
         	└── node.html   	// Contain full node
         	└── details.html	// contain details about nodes

Now it’s time to add code in our templates:

app.html


<ion-tabs class="tabs-striped tabs-color-assertive">
<ion-tab title="Dashboard" icon-off="ion-grid" icon-on="ion-grid" href="#/app/dash">
        <ion-nav-view name="app-dash"></ion-nav-view>
</ion-tab>
<ion-tab title="Nodes" icon-off="ion-ios-chatboxes-outline" icon-on="ion-ios-chatboxes" href="#/app/home">
<ion-nav-view name="app-home"></ion-nav-view>
</ion-tab>
<ion-tab title="Details" icon-off="ion-information-circled" icon-on="ion-information-circled" href="#/app/details">
<ion-nav-view name="app-details"></ion-nav-view>
</ion-tab>
</ion-tabs>

dash.html – This will contain the number of nodes and static title


<ion-view view-title="Dashboard">
<ion-content>
        <div class="item item-body">
              <h1>Hybrid application</h1>
        </div>
    <div class="list list-inset">
              <div class="item">
        <i class="icon ion-android-folder-open"></i>
        Current number of nodes: {{data.length}}
      </div>
    </div>
</ion-content>
</ion-view>

home.html – We use ng-repeat to list all nodes


<ion-view view-title="Posts">
<ion-content>
        <ion-list>
      <ion-item
          type="item-text-wrap"
          ng-repeat="item in data"
          href="#/app/home/{{item.nid}}">
        {{item.title}}
        <i class="icon ion-android-exit"></i>
      </ion-item>
</ion-list>
</ion-content>
</ion-view>

node.html – This template contains full node content


<ion-view view-title="{{item.title}}">
<ion-content class="padding">
        <h2>{{data.title}}</h2>
    <p>{{data.body.und[0].value}}</p>
    <div class="icon ion-clock">
              Updated: {{data.changed * 1000 | date: 'medium'}}
</div>
    <div class="icon ion-person">
              Author:
      {{data.name}}
</div>
</ion-content>
</ion-view>

details.html – Information about nodes, such as created and updated nodes and a type of node


<ion-view view-title="Details">
<ion-content>
    <div class="list list-inset">

      <div class="item icon ion-ios-information" ng-repeat="item in data">
        {{item.title}}

        <span class="item-note">
          <div>
            <strong>Created:</strong> {{item.created *1000 | date: 'medium'}}
          </div>

          <div>
            <strong>Updated:</strong> {{item.changed * 1000 | date:'medium'}}
          </div>

          <div>
            <strong>Type:</strong> {{item.type}}
          </div>
        </span>
      </div>
    </div>
  </ion-content>
</ion-view>

#3 Build A Hybrid Application

Configure Platforms, Build and Emulate App

Once we have built a web application we can create a build for this app. We are able to create iOS and Android builds but we can only deploy Android build to devices without a developer account.

Note: If you want to deploy iOS app on a device, you need to have the Apple Developer Account. If you don’t have it, you are only able to test the app locally on iOS emulator.

Let’s start with build and emulate our application –  type the following in terminal:

Android:


ionic platform add android      
ionic build android             
ionic emulate android           

iOS


ionic platform add ios
ionic build ios
ionic emulate ios

The command above will integrate the Android platform, build the application and launch the emulator with our app loaded in it.

The results should look like this:

dashboardposts

full-nodedetails

That’s how your hybrid app should look like.

You can view the demo on this link or download .apk file.

Remember: It’s important to consider whether you want to build hybrid or native applications.

Before you start with the development process, consider these questions:

  • Do you want to target Android and/or iOS platforms?
  • Do you want to distribute your application via App Stores (where user will receive regular update)
  • Do you want to utilize specific capabilities of mobile devices?

Both native and hybrid applications have their strengths and weaknesses; moreover, they answer to different needs and preferences of users and developers. Thus, each of the apps can be a perfect solution for a particular type of a project.

However, if you want to use full native features (e.g. access to Camera, Contacts, etc.) then the native app development will work better as hybrid app may not have an access to needed native features. On the other hand, if you want to have the application that is cross-platform ready, a hybrid app is a way to go. Moreover, if you have a limited budget and need a good solution developed in a short time,  hybrid apps are a good choice.

Stevica Knezevic has experience with AngularJS, jQuery, Ionic, Express.js, Node.js, Drupal, and various API’s, so feel free to ask any questions in the comments below.

11 Comments

  • Aaron Saunders

    This is great stuff, but for someone coming at this from the mobile side, a brief overview of Drupal and at a minimum some screenshots of what the CMS UI for the data would look like would be of some benefit IMHO

    • Damjan Gataric

      Thank you Aaron, that’s a great idea! We will cover Drupal CMS UI and ease of content management in one of our following Blog articles. To make it more convenient, it should be cross-linked here ;)

  • Seta International

    Thanks for providing a good utility on generating HTML5-based apps for IOS and Android. Web developers can develop IOS/Android apps without knowing about Object C/Java.

    • Stevica Knezevic

      Thanks for reading! We are here to help :)

  • Steve

    Very nice Tutorial! Everything is working very good so far. Does anybody know which variable i dod have to put in to print out an image field?

    Like: {{data.field_image}} in node.html

    But that doesn’t give me an image… just a String with all kinds of infos about the image.

    Many thanks in advance!

    • Stevica Knezevic

      Hi Steve,

      The thing is, you cannot get the picture using one variable, we can only get the name of the picture as a response in json.

      We need to add a new constant in the application (app.js) that contains the path to the public directory on our remote server, in this case, it’s “http://yourServerName/sites/default/files”.

      You can name the constant PUBLIC_URL, for instance, and forward it to controller nodeCtrl creating a new scope, as in the example: $scope.publicUrl = PUBLIC_URL. Then, in views, we can simple add this path within img tag, with src='{{publicUr}}{{data.field_page_image.und[0].filename}}’

      This is one of the ways you can show the picture via REST. If someone knows another way to solve this, feel free to share with us.

      I hope this helped you.

  • coderkk

    Hello, there has a bug at service.html

    Instead of
    // Get node content by nid
    .factory(‘DrupalNode’, function ($http, $stateParams, REMOTE_URL) {
    id = $stateParams.nodeId;
    return {
    node: $http.get(REMOTE_URL + ‘node/’ + id)
    }
    });

    I have modified my code to
    // Get node content by nid
    .factory(‘DrupalNode’, function ($http, $stateParams, REMOTE_URL) {
    return {
    node: fucntion() {
    id = $stateParams.nodeId;
    $http.get(REMOTE_URL + ‘node/’ + id)
    }
    }
    });

    Then modify the controller.php
    .controller(‘nodeCtrl’, [‘$scope’, ‘DrupalNode’,
    function ($scope, DrupalNode) {
    DrupalNode.node()
    .success(function (data) {
    $scope.data = data;
    });
    }])

    • coderkk

      Got mistake

      factory(‘DrupalNode’, function ($http, $stateParams, REMOTE_URL) {
      return {
      node: fucntion() {
      id = $stateParams.nodeId;
      return $http.get(REMOTE_URL + ‘node/’ + id)
      }
      }
      });

      • Stevica Knezevic

        Thanks for reading my guide.

        We both agree that there’s no bug in the code :) Although one can write it the way you did, I used a simpler way to get response in the factory service.

        Thanks again for reading.

      • Nuno

        .. node:function() NOT fucntion

        just a typo issue ;)

  • Nuno

    Great help getting start, but I guess theres something wrong in the code, because the content of the pages is always the same, it does not update.
    When you enter the details page, back to list, select diferent item, it always show the first you.

    Try with $scope.$apply(); after the get of data, but not updating.

Comments are closed.