Building A Hybrid Mobile App With Parse and PhoneGap

In this tutorial, I will show you how to build a hybrid mobile app with Parse and Phonegap.

What is Parse?

Parse is a cloud app platform that enables users to add a scalable and powerful backend to launch a full-featured app.

Reference: https://www.parse.com/

What is Phonegap?

PhoneGap is a free and open source framework that allows you to create mobile apps using standardized web APIs for the platforms you care about.

Reference: http://en.wikipedia.org/wiki/PhoneGap

Download and install Eclipse Juno SR2 and Xcode 6.3.1 (if you using Mac for mobile development).

Eclipse Juno SR2 – http://www.eclipse.org/downloads/packages/release/juno/sr2
Xcode 6.3.1 – https://developer.apple.com/xcode/downloads/

Setup ADT and Android SDK into your eclipse. You can refer here how to setup, http://blog.revivalx.com/2014/02/21/crud-operation-using-jquery-mobile-on-android-part-1/. Then you need to setup cordova using NPM (node package manager) from that link.

Download Jquery and Ratchet from their official website. You should unzip it to proper location. For this tutorial, we using Jquery 1.11.3 and Ratchet 2.0.2.

Sign up for your new account here, https://www.parse.com/.

Parse

Parse

Click Create a New App.

Create a New App

Create a New App

Go to Keys and save your Application ID and Client Key.

Setting up your development environment

Create a cordova project using command below in your terminal/cmd:

cordova create com.revivalx.todolist.parse
cordova create project

cordova create project

Open config.xml inside com.revivalx.todolist.parse folder and replace with this code.

<?xml version='1.0' encoding='utf-8'?>
<widget id="io.cordova.hellocordova" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>ToDo</name>
    <description>
        This source code provides example for todolist app using parse.
    </description>
    <author email="mohammadnrdn@gmail.com" href="http://revivalx.com">
        Mohammad Nurdin bin Norazan
    </author>
    <content src="index.html" />
    <access origin="*" />
</widget>

Add platform for iOS and android.

cordova platform add ios
cordova platform add android
cordova platform add ios

cordova platform add ios

cordova platform add android

cordova platform add android

Create a new project inside your eclipse.

create a new eclipse project

create a new eclipse project

Select General > Project.

new eclipse project

new eclipse project

Enter following details and click Finish.

Project name: com.revivalx.todolist.parse .

Location: your project location.

new eclipse project

new eclipse project

Overview of Cordova directory structures

Here is the Cordova directory structure:

cordova structure

cordova structure

hooks – This directory may contains scripts used to customize cordova commands.

platforms (android and iOS) – Platforms added to your application will have the native application project structures laid out within this directory.

plugins – Any added plugins will be extracted or copied into this directory.

www (css) – The main application CSS directory.

www (img) – The main application image directory.

www (js) – The main application javascript directory.

index.html – The main HTML file.

config.xml – A global configuration file.

Preview your app (android)

Import android project into your eclipse.

import cordova project

import cordova project

Select AndroidExisting Android Code Into Workspace.

import existing android code into workspace

import existing android code into workspace

Browse your project and click Finish.

import android project

import android project

Follow this tutorial how to run android app in android simulator.

http://blog.revivalx.com/2014/02/21/crud-operation-using-jquery-mobile-on-android-part-1/

Preview your app (ios)

Go to your project directory and browse for xcode project, platforms > ios > ToDo.xcodeproj then run xcode project.

Click Play on top left side to preview the app on your simulator.

xcode project

xcode project

Learning client side API and building a multi page application

Right click js folder on your project. Go to New Other.

Javascript Source File

Javascript Source File

Select JavaScript > JavaScript Source File and click Next.

javascript source file

javascript source file

Create 3 javascript files.

  • main.js – initialize function.
  • AddPage.js – to add user details.
  • ListPage.js – to retrieve user list.
  • DetailPage.js – to retrieve user details.

Click Finish.

javascript source file

javascript source file

Right click www folder and go to New > Folder to create a new folder.

add folder

add folder

Name it pages and click Finish.

add folder pages

add folder pages

Then create a few of html pages inside page folder that we already created just now. Right click pages folder and go to New > HTML File.

HTML Files

HTML Files

Select Web > HTML File and click Next.

new html file

new html file

Create 3 html files and click Finish. Repeat.

  • AddPage.html – to add task details.
  • ListPage.html – to retrieve task list.
  • DetailPage.html – to retrieve task details.
new html file

new html file

Copy Jquery javascript files that you already extract from proper location into js folder.

jquery

jquery

Go to extracted Ratchet directory and select/copy all folders inside ratchet-2.0.2 > dist .

ratchet directory

ratchet directory

Then paste it inside www folder.

ratchet

ratchet

Open your index.html and replace with this code.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>

    <!-- Sets initial viewport load and disables zooming  -->
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">

    <!-- Makes your prototype chrome-less once bookmarked to your phone's home screen -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">

    <!-- Include the compiled Ratchet CSS -->
    <link href="css/ratchet.css" rel="stylesheet">
    <script src="cordova.js" type="text/javascript"></script>
    <script src="js/jquery-1.11.3.min.js"></script>
    <script src="//www.parsecdn.com/js/parse-1.4.2.min.js"></script>
    <script type="text/javascript">
    	Parse.initialize("Application ID", "Client Key");
    </script>
    <script src="js/ratchet.js"></script>
    <script src="js/main.js"></script>
  </head>
  <body onload="init()">

  </body>
</html>

Replace all codes with given codes for each javascript files.

AddPage.js

currentPage = {};
currentPage.init = function() {
	console.log("AddPage :: init");
};
currentPage.back = function() {
	console.log("AddPage :: back");
	$("body").load(path + "pages/ListPage.html", function() {
		$.getScript(path + "js/ListPage.js", function() {
			if (currentPage.init) {
				currentPage.init();
			}
		});
	});
};
currentPage.add = function() {
	console.log("AddPage :: add");
	var name = $("#name").val();
	var description = $("#description").val();
	formData = {
		name: $("#name").val(),
		description: $("#description").val(),
	}
	if (name == "") {
		alert("Please enter name");
	} else if (description == "") {
		alert("Please enter description");
	} else {
		var Task = Parse.Object.extend("Task");
		var task = new Task();
		task.set("name", name);
		task.set("description", description);
		task.save(null, {
			success: function(task) {
				// Execute any logic that should take place after the object is saved.
				alert("Add task success");
				$("body").load(path + "pages/ListPage.html", function() {
					$.getScript(path + "js/ListPage.js", function() {
						if (currentPage.init) {
							currentPage.init();
						}
					});
				});
			},
			error: function(task, error) {
				// Execute any logic that should take place if the save fails.
				// error is a Parse.Error with an error code and message.
				alert('Failed to create new object, with error code: ' + error.message);
			}
		});
	}
};

DetailPage.js

currentPage = {};
currentPage.init = function() {
	console.log("DetailPage :: init");
	detailTask();
};
currentPage.back = function() {
	console.log("DetailPage :: back");
	$("body").load(path + "pages/ListPage.html", function() {
		$.getScript(path + "js/ListPage.js", function() {
			if (currentPage.init) {
				currentPage.init();
			}
		});
	});
};
currentPage.edit = function() {
	console.log("DetailPage :: edit");
	var taskId = sessionStorage.taskId;
	var name = $("#name").val();
	var description = $("#description").val();
	formData = {
		taskId: sessionStorage.taskId,
		name: $("#name").val(),
		description: $("#description").val()
	}
	if (name == "") {
		alert("Please enter name");
	} else if (description == "") {
		alert("Please enter description");
	} else {
		var Task = Parse.Object.extend("Task");
		var query = new Parse.Query(Task);
		query.get(taskId, {
			success: function(task) {
				// The object was retrieved successfully.
				// Create the object.
				task.set("name", name);
				task.set("description", description);
				task.save(null, {
					success: function(task) {
						alert("Edit task success");
						$("body").load(path + "pages/ListPage.html", function() {
							$.getScript(path + "js/ListPage.js", function() {
								if (currentPage.init) {
									currentPage.init();
								}
							});
						});
					}
				});
			},
			error: function(object, error) {
				// The object was not retrieved successfully.
				// error is a Parse.Error with an error code and message.
				alert(error);
			}
		});
	}
};

function detailTask() {
	formData = {
		taskId: sessionStorage.taskId
	}
	var Task = Parse.Object.extend("Task");
	var query = new Parse.Query(Task);
	query.get(taskId, {
		success: function(task) {
			// The object was retrieved successfully.
			$('#name').val(task.get("name"));
			$('#description').val(task.get("description"));
		},
		error: function(object, error) {
			// The object was not retrieved successfully.
			// error is a Parse.Error with an error code and message.
			alert(error);
		}
	});
}
currentPage.remove = function() {
	console.log("DetailPage :: delete");
	deleteTask();
};

function deleteTask() {
	formData = {
		taskId: sessionStorage.taskId
	}
	var Task = Parse.Object.extend("Task");
	var query = new Parse.Query(Task);
	query.get(taskId, {
		success: function(task) {
			// The object was retrieved successfully.
			task.destroy({
				success: function(task) {
					// The object was deleted from the Parse Cloud.
					alert("Delete task success");
					$("body").load(path + "pages/ListPage.html", function() {
						$.getScript(path + "js/ListPage.js", function() {
							if (currentPage.init) {
								currentPage.init();
							}
						});
					});
				},
				error: function(task, error) {
					// The delete failed.
					// error is a Parse.Error with an error code and message.
				}
			});
		},
		error: function(object, error) {
			// The object was not retrieved successfully.
			// error is a Parse.Error with an error code and message.
			alert(error);
		}
	});
}

ListPage.js

currentPage = {};
currentPage.init = function() {
	console.log("ListPage :: init");
	listTasks();
};
currentPage.loadPage = function(pageIndex) {
	console.log("ListPage :: loadPage :: pageIndex: " + pageIndex);
	$("body").load(path + "pages/" + pageIndex + ".html");
	$.getScript(path + "js/" + pageIndex + ".js", function() {
		if (currentPage.init) {
			currentPage.init();
		}
	});
};
currentPage.detailPage = function(userId) {
	sessionStorage.setItem("userId", userId);
	$("body").load(path + "pages/DetailPage.html");
	$.getScript(path + "js/DetailPage.js", function() {
		if (currentPage.init) {
			currentPage.init();
		}
	});
};

function listTasks() {
	var Task = Parse.Object.extend("Task");
	var query = new Parse.Query(Task);
	query.equalTo("typeId", "3");
	query.find({
		success: function(results) {
			var index = 0;
			var Arrlen = results.length;
			for (index = 0; index < Arrlen; ++index) {
				var obj = results[index];
				html += '<li class="table-view-cell">';
				html += '<a class="navigate-right" onclick="currentPage.detailPage(' + obj.id + ');" >';
				html += obj.attributes.name;
				html += '</a></li>';
			}
		},
		error: function(object, error) {
			alert(error);
		}
	});
}

main.js

var pagesHistory = [];
var currentPage = {};
var path = "";

function init(){

    $("body").load(path + "pages/ListPage.html", function(){
        $.getScript(path + "js/ListPage.js", function() {
            if (currentPage.init) {
                currentPage.init();
            }
        });
    });

}

Replace all codes with given codes for each html files.

AddPage.html

<script>
    $.getScript(path + "js/AddPage.js");
</script>
 
<header class="bar bar-nav">
    <a class="icon icon-left-nav pull-left" onclick="currentPage.back();"></a>
    <h1 class="title">AddPage</h1>
</header>

<div class="content">
	<div class="card">
    <ul class="table-view">
        <li class="table-view-cell table-view-divider">Name:</li>

        <li class="table-view-cell"><input id="name" name="name" type="text"></li>

        <li class="table-view-cell table-view-divider">Description:</li>

        <li class="table-view-cell"><input id="description" name="description" type="text"></li>

        <li class="table-view-cell"></li>
    </ul>
   </div>
    <div class="card" style="margin-bottom:40px;">
    	<button class="btn btn-positive btn-block" onclick="currentPage.add();">ADD TASK</button>
    </div>
</div>

DetailPage.html

<script>
    $.getScript(path + "js/DetailPage.js");
</script>

<header class="bar bar-nav">
    <a class="icon icon-left-nav pull-left" onclick="currentPage.back();"></a>
    <h1 class="title">DetailPage</h1>
</header>

<div class="content">
	<div class="card">
    <ul class="table-view">
        <li class="table-view-cell table-view-divider">Name:</li>

        <li class="table-view-cell"><input id="name" name="name" type="text"></li>

        <li class="table-view-cell table-view-divider">Description:</li>

        <li class="table-view-cell"><input id="description" name="description" type="text"></li>

    </ul>
   </div>
   <div class="card" style="margin-bottom:40px;">
    	<button class="btn btn-positive btn-block" onclick="currentPage.edit();">EDIT TASK</button>
   </div>
   <div class="card" style="margin-bottom:40px;">
    	<button class="btn btn-negative btn-block" onclick="currentPage.remove();">REMOVE TASK</button>
   </div>
</div>

ListPage.html

<script>
    $.getScript(path + "js/ListPage.js");
</script>

<header class="bar bar-nav">
	<button id="LoadAddButton" class="btn pull-right" onclick="currentPage.loadPage('AddPage');">Add</button>
  	<h1 class="title">ListPage</h1>
</header>

<div class="content">
	<ul id="taskList" class="table-view"></ul>
</div>

Build and run your project. I’m using Genymotion. If you don’t have, you can download it here,https://www.genymotion.com/.

 

Result.

You can download the source code here, https://github.com/datomnurdin/com.revivalx.todolist.parse.

How to call REST APIs with AlamoFire and Swift

Alamofire is a networking API that leverages the full power of Swift.

Reference:
http://www.raywenderlich.com/85080/beginning-alamofire-tutorial
https://github.com/Alamofire/Alamofire

alamofire

alamofire

Here is sample how to call REST APIs with AlamoFire.

import UIKit
import Alamofire

class ViewController: ViewController {

    var user: [JSON] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()

        Alamofire.request(.GET, "https://api.github.com/users/datomnurdin").responseJSON { (request, response, json, error) in
            if json != nil {
                var jsonObj = JSON(json!)
                if let data = jsonObj["user"].arrayValue as [JSON]?{
                    println(data)
                }
            }
        }
    }
}

Swift iOS Tutorial – Core Data – Add Update Delete

Core Data is an object graph and persistence framework provided by Apple in the Mac OS X and iOS operating systems. It was introduced in Mac OS X 10.4 Tiger and iOS with iPhone SDK 3.0.

Reference: https://en.wikipedia.org/wiki/Core_Data

Core Data

Core Data

Here I show some useful core data queries below (before that you need to create a model):

import UIKit
import CoreData

@objc(Entityones)
class Entityones: NSManagedObject {

@NSManaged var attribute_one: String
@NSManaged var attribute_two: String
@NSManaged var attribute_three: String

}

Core Data queries.

Create/Add query

let appDel: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
        
        let context: NSManagedObjectContext = appDel.managedObjectContext!
        let en = NSEntityDescription.entityForName("Entityones", inManagedObjectContext: context)

        var newEn =  Entityones(entity: en!, insertIntoManagedObjectContext: context)
            
        newEn.attribute_one = "param1"
        newEn.attribute_two = "param2"
        newEn.attribute_three = "param3"
        
        context.save(nil)

Update query

let appDel: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
                        let context: NSManagedObjectContext = appDel.managedObjectContext!
                        
                        let request = NSFetchRequest(entityName: "Entityones")
                        request.predicate = NSPredicate(format: "attribute1 = %@", "param1")
                        
                        if let fetchResults = appDel.managedObjectContext!.executeFetchRequest(request, error: nil) as? [NSManagedObject] {
                            if fetchResults.count != 0{
                                
                                var managedObject = fetchResults[0]
                                managedObject.setValue("param1", forKey: "attribute_one")
                                managedObject.setValue("param2", forKey: "attribute_two")
                                managedObject.setValue("param3", forKey: "attribute_three")
                                
                                context.save(nil)
                             }
                        }

Delete query

//use on UITableView
var myList: Array <AnyObject> = []

let appDel: AppDelegate = (UIApplication.sharedApplication().delegate as! AppDelegate)
            let context: NSManagedObjectContext = appDel.managedObjectContext!
            context.deleteObject(myList[indexPath.row] as! NSManagedObject)
            myList.removeAtIndex(indexPath.row)
            context.save(nil)

If you want know more details how to integrate these queries to your application, feel free to drop your email here, nurdinnorazanservices@gmail.com.

Image cache using Haneke.

Haneke is a lightweight generic cache for iOS written in Swift 1.2. It’s designed to be super-simple to use. https://github.com/Haneke/HanekeSwift

Open Xcode6 and create a new Single View Application. For product name, use RevivalxHanekeSwift and then fill out the Organization Name and Organization Identifier with your customary values. Enter Swift as Language and make sure only iPhone is selected in Devices.

Single View Application

Single View Application

Choose options for your new project

Choose options for your new project

Download Haneke for Swift from Github here, https://github.com/Haneke/HanekeSwift. Copy and drag Haneke library into your project.

Finder

Finder

Xcode6 – Project info

Xcode6 – Project info

Don’t forget to set Embedded Binaries. Target > RevivalxHanekeSwift > SwiftEmbedded Binaries.

Embedded Binaries

Embedded Binaries

Set to Compact Width | Any Height.

Compact Width | Any Height

Compact Width | Any Height

Go to the Storyboard and drag an Image View to the main View. Select the Image View and go to the Size inspector and fill in the following values.

 

And image mode change to Aspect Fill.

Image View mode

Image View mode

Open document outline, select the Image View again and set constraint to Center Horizontally in ContainerCenter Vertically in ContainerTop space to Top Layout Guide and Leading Space to Container Margin.

Document Outline and Constraints

Document Outline and Constraints

The Storyboard will look like this.

Main.Storyboard

Main.Storyboard

Select the Assistant Editor and make sure the ViewController.swift is visible. Ctrl and drag from the Image View to the ViewController class and create the following Outlet.

Image View property outlet

Image View property outlet

Go to the ViewController.swift file, import Haneke and change the viewDidLoad method.

import UIKit
import Haneke

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        let url = NSURL(string: "https://graph.facebook.com/10204521055439574/picture?type=large")
        //profilePhotoImage.contentMode = .ScaleAspectFill
        imageView.hnk_setImageFromURL(url!)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

Your output.

iOS simulator

iOS simulator

Build and Run the project. You can download the source code here, https://github.com/datomnurdin/RevivalxHaneke.