Quantcast
Channel: json – androidhive
Viewing all 27 articles
Browse latest View live

Android JSON Parsing Tutorial

$
0
0



JSON is the best alternative to XML for storing data in files. It is easy to parse and access data stored in JSON format. Previously i explained parsing XML and today i am going to discuess parsing JSON data with an example.

Download Code

The JSON Structure

I am taking an example of following JSON which will give you list of contacts and each contact will have details like name, email, address, phone number ertc,. You can get this JSON data by accessing http://api.androidhive.info/contacts/

{
    "contacts": [
        {
                "id": "c200",
                "name": "Ravi Tamada",
                "email": "ravi@gmail.com",
                "address": "xx-xx-xxxx,x - street, x - country",
                "gender" : "male",
                "phone": {
                    "mobile": "+91 0000000000",
                    "home": "00 000000",
                    "office": "00 000000"
                }
        },
        {
                "id": "c201",
                "name": "Johnny Depp",
                "email": "johnny_depp@gmail.com",
                "address": "xx-xx-xxxx,x - street, x - country",
                "gender" : "male",
                "phone": {
                    "mobile": "+91 0000000000",
                    "home": "00 000000",
                    "office": "00 000000"
                }
        },
        .
        .
        .
        .
  ]
}

The difference between [ and { - (Square brackets and Curly brackets)

If you observe normally JSON data will have square brackets and curly brackets. The difference between [ and { is, the square bracket represents starting of an JSONArray node whereas curly bracket represents JSONObject. While accessing these elements we need to call different methods to access these nodes.

json parsing structor

Writing JSON Parser Class

In your project create a class file and name it as JSONParser.java. The parser class has a method which will make http request to get JSON data and returns a JSONObject.

package com.androidhive.jsonparsing;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

public class JSONParser {

	static InputStream is = null;
	static JSONObject jObj = null;
	static String json = "";

	// constructor
	public JSONParser() {

	}

	public JSONObject getJSONFromUrl(String url) {

		// Making HTTP request
		try {
			// defaultHttpClient
			DefaultHttpClient httpClient = new DefaultHttpClient();
			HttpPost httpPost = new HttpPost(url);

			HttpResponse httpResponse = httpClient.execute(httpPost);
			HttpEntity httpEntity = httpResponse.getEntity();
			is = httpEntity.getContent();			

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(
					is, "iso-8859-1"), 8);
			StringBuilder sb = new StringBuilder();
			String line = null;
			while ((line = reader.readLine()) != null) {
				sb.append(line + "\n");
			}
			is.close();
			json = sb.toString();
		} catch (Exception e) {
			Log.e("Buffer Error", "Error converting result " + e.toString());
		}

		// try parse the string to a JSON object
		try {
			jObj = new JSONObject(json);
		} catch (JSONException e) {
			Log.e("JSON Parser", "Error parsing data " + e.toString());
		}

		// return JSON String
		return jObj;

	}
}

Parsing JSON Data

Once you created parser class next thing is to know how to use that class. Below i am explaining how to parse the json (taken in this example) using the parser class.

1. In the contacts json we have items like name, email, address, gender and phone numbers. So first thing is to store all these node names in variables. Open your main activity class and declare store all node names in static variables.

    // url to make request
	private static String url = "http://api.androidhive.info/contacts/";
	
	// JSON Node names
	private static final String TAG_CONTACTS = "contacts";
	private static final String TAG_ID = "id";
	private static final String TAG_NAME = "name";
	private static final String TAG_EMAIL = "email";
	private static final String TAG_ADDRESS = "address";
	private static final String TAG_GENDER = "gender";
	private static final String TAG_PHONE = "phone";
	private static final String TAG_PHONE_MOBILE = "mobile";
	private static final String TAG_PHONE_HOME = "home";
	private static final String TAG_PHONE_OFFICE = "office";

	// contacts JSONArray
	JSONArray contacts = null;

2. Next step to is to use parser class to get JSONObject and looping through each json item. Below i am creating an instance of JSONParser class and using for loop i am looping through each json item and finally storing each json data in variable.

        // Creating JSON Parser instance
		JSONParser jParser = new JSONParser();

		// getting JSON string from URL
		JSONObject json = jParser.getJSONFromUrl(url);

		try {
			// Getting Array of Contacts
			contacts = json.getJSONArray(TAG_CONTACTS);
			
			// looping through All Contacts
			for(int i = 0; i < contacts.length(); i++){
				JSONObject c = contacts.getJSONObject(i);
				
				// Storing each json item in variable
				String id = c.getString(TAG_ID);
				String name = c.getString(TAG_NAME);
				String email = c.getString(TAG_EMAIL);
				String address = c.getString(TAG_ADDRESS);
				String gender = c.getString(TAG_GENDER);
				
				// Phone number is agin JSON Object
				JSONObject phone = c.getJSONObject(TAG_PHONE);
				String mobile = phone.getString(TAG_PHONE_MOBILE);
				String home = phone.getString(TAG_PHONE_HOME);
				String office = phone.getString(TAG_PHONE_OFFICE);
				
			}
		} catch (JSONException e) {
			e.printStackTrace();
		}

Parsing JSON data and updating into ListView

In my previous tutorial Android ListView Tutorial i explained how to create listview and updating with list data. Below i am implementing same listview but the list data i am updating is from parsed JSON.

package com.androidhive.jsonparsing;

import java.util.ArrayList;
import java.util.HashMap;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;

public class AndroidJSONParsingActivity extends ListActivity {

	// url to make request
	private static String url = "http://api.androidhive.info/contacts/";
	
	// JSON Node names
	private static final String TAG_CONTACTS = "contacts";
	private static final String TAG_ID = "id";
	private static final String TAG_NAME = "name";
	private static final String TAG_EMAIL = "email";
	private static final String TAG_ADDRESS = "address";
	private static final String TAG_GENDER = "gender";
	private static final String TAG_PHONE = "phone";
	private static final String TAG_PHONE_MOBILE = "mobile";
	private static final String TAG_PHONE_HOME = "home";
	private static final String TAG_PHONE_OFFICE = "office";

	// contacts JSONArray
	JSONArray contacts = null;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		
		// Hashmap for ListView
		ArrayList<HashMap<String, String>> contactList = new ArrayList<HashMap<String, String>>();

		// Creating JSON Parser instance
		JSONParser jParser = new JSONParser();

		// getting JSON string from URL
		JSONObject json = jParser.getJSONFromUrl(url);

		try {
			// Getting Array of Contacts
			contacts = json.getJSONArray(TAG_CONTACTS);
			
			// looping through All Contacts
			for(int i = 0; i < contacts.length(); i++){
				JSONObject c = contacts.getJSONObject(i);
				
				// Storing each json item in variable
				String id = c.getString(TAG_ID);
				String name = c.getString(TAG_NAME);
				String email = c.getString(TAG_EMAIL);
				String address = c.getString(TAG_ADDRESS);
				String gender = c.getString(TAG_GENDER);
				
				// Phone number is agin JSON Object
				JSONObject phone = c.getJSONObject(TAG_PHONE);
				String mobile = phone.getString(TAG_PHONE_MOBILE);
				String home = phone.getString(TAG_PHONE_HOME);
				String office = phone.getString(TAG_PHONE_OFFICE);
				
				// creating new HashMap
				HashMap<String, String> map = new HashMap<String, String>();
				
				// adding each child node to HashMap key => value
				map.put(TAG_ID, id);
				map.put(TAG_NAME, name);
				map.put(TAG_EMAIL, email);
				map.put(TAG_PHONE_MOBILE, mobile);

				// adding HashList to ArrayList
				contactList.add(map);
			}
		} catch (JSONException e) {
			e.printStackTrace();
		}
		
		
		/**
		 * Updating parsed JSON data into ListView
		 * */
		ListAdapter adapter = new SimpleAdapter(this, contactList,
				R.layout.list_item,
				new String[] { TAG_NAME, TAG_EMAIL, TAG_PHONE_MOBILE }, new int[] {
						R.id.name, R.id.email, R.id.mobile });

		setListAdapter(adapter);

		// selecting single ListView item
		ListView lv = getListView();

		// Launching new screen on Selecting Single ListItem
		lv.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				// getting values from selected ListItem
				String name = ((TextView) view.findViewById(R.id.name)).getText().toString();
				String cost = ((TextView) view.findViewById(R.id.email)).getText().toString();
				String description = ((TextView) view.findViewById(R.id.mobile)).getText().toString();
				
				// Starting new intent
				Intent in = new Intent(getApplicationContext(), SingleMenuItemActivity.class);
				in.putExtra(TAG_NAME, name);
				in.putExtra(TAG_EMAIL, cost);
				in.putExtra(TAG_PHONE_MOBILE, description);
				startActivity(in);
			}
		});
	}

}
android json parsing list view
This image for thumbnail purpose
android json parsing

Android Login and Registration with PHP, MySQL and SQLite

$
0
0


In my previous article Android Login and Registration Screen Design i explained designing the login and registration interfaces, but it has no functionality. In this tutorial i am explaining how to build complete login and registration system in android using PHP, MySQL and SQLite. Also this tutorial covers how to build simple API using PHP and MySQL.

Download Code

Prerequisites

This tutorial is combination of some of my previous tutorials. I hope you covered these tutorials before.
Android making HTTP Requests
Android JSON Parsing Tutorial
Android SQLite Database Tutorial
Android Login and Registration Screen Design

API (Application Programming Interface)

⇒ Accepting requests by GET/POST methods
⇒ Interact with PHP classes to get data from database or store in database
⇒ Finally will give output in JSON format

Android Mysql PHP connect

1. Creating MySQL Database and Tables

As I am writing API in PHP I selected MySql database to maintain users and other related information. Open your mysql console or phpmyadmin and run following query to create database and users table.

create database android_api /** Creating Database **/
use android_api /** Selecting Database **/
create table users(
   uid int(11) primary key auto_increment,
   unique_id varchar(23) not null unique,
   name varchar(50) not null,
   email varchar(100) not null unique,
   encrypted_password varchar(80) not null,
   salt varchar(10) not null,
   created_at datetime,
   updated_at datetime null
); /** Creating Users Table **/


2. Building PHP API Classes

To make it minimum i tried to use less number of php files. Following are the files are required to build API in php. You can find description of each file in the below image.

android php api directory structor

config.php – This file contains constant variables to connect to database.

<?php
/**
 * Database config variables
 */
define("DB_HOST", "localhost");
define("DB_USER", "root");
define("DB_PASSWORD", "");
define("DB_DATABASE", "android_api");
?>

DB_Connect.php – This file is used to connect or disconnect to database.

<?php

class DB_Connect {

    // constructor
    function __construct() {
        
    }

    // destructor
    function __destruct() {
        // $this->close();
    }

    // Connecting to database
    public function connect() {
        require_once 'config.php';
        // connecting to mysql
        $con = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
        // selecting database
        mysql_select_db(DB_DATABASE);

        // return database handler
        return $con;
    }

    // Closing database connection
    public function close() {
        mysql_close();
    }

}

?>

DB_Functions.php – This file contains functions to store user in database, get user from database. You can also add methods like update user, delete user.

user unique id – I am generating unique user id in php using uniqid(”, true) function. Sample user id will be like 4f074eca601fb8.88015924

Encrypted Password – This password is stored using base64_encode method. Each password will need two columns to store in database. One is to store encrypted password and second column is to store salt used to encrypt the password.

<?php

class DB_Functions {

    private $db;

    //put your code here
    // constructor
    function __construct() {
        require_once 'DB_Connect.php';
        // connecting to database
        $this->db = new DB_Connect();
        $this->db->connect();
    }

    // destructor
    function __destruct() {
        
    }

    /**
     * Storing new user
     * returns user details
     */
    public function storeUser($name, $email, $password) {
        $uuid = uniqid('', true);
        $hash = $this->hashSSHA($password);
        $encrypted_password = $hash["encrypted"]; // encrypted password
        $salt = $hash["salt"]; // salt
        $result = mysql_query("INSERT INTO users(unique_id, name, email, encrypted_password, salt, created_at) VALUES('$uuid', '$name', '$email', '$encrypted_password', '$salt', NOW())");
        // check for successful store
        if ($result) {
            // get user details 
            $uid = mysql_insert_id(); // last inserted id
            $result = mysql_query("SELECT * FROM users WHERE uid = $uid");
            // return user details
            return mysql_fetch_array($result);
        } else {
            return false;
        }
    }

    /**
     * Get user by email and password
     */
    public function getUserByEmailAndPassword($email, $password) {
        $result = mysql_query("SELECT * FROM users WHERE email = '$email'") or die(mysql_error());
        // check for result 
        $no_of_rows = mysql_num_rows($result);
        if ($no_of_rows > 0) {
            $result = mysql_fetch_array($result);
            $salt = $result['salt'];
            $encrypted_password = $result['encrypted_password'];
            $hash = $this->checkhashSSHA($salt, $password);
            // check for password equality
            if ($encrypted_password == $hash) {
                // user authentication details are correct
                return $result;
            }
        } else {
            // user not found
            return false;
        }
    }

    /**
     * Check user is existed or not
     */
    public function isUserExisted($email) {
        $result = mysql_query("SELECT email from users WHERE email = '$email'");
        $no_of_rows = mysql_num_rows($result);
        if ($no_of_rows > 0) {
            // user existed 
            return true;
        } else {
            // user not existed
            return false;
        }
    }

    /**
     * Encrypting password
     * @param password
     * returns salt and encrypted password
     */
    public function hashSSHA($password) {

        $salt = sha1(rand());
        $salt = substr($salt, 0, 10);
        $encrypted = base64_encode(sha1($password . $salt, true) . $salt);
        $hash = array("salt" => $salt, "encrypted" => $encrypted);
        return $hash;
    }

    /**
     * Decrypting password
     * @param salt, password
     * returns hash string
     */
    public function checkhashSSHA($salt, $password) {

        $hash = base64_encode(sha1($password . $salt, true) . $salt);

        return $hash;
    }

}

?>

index.php – This file plays role of accepting requests and giving response. This file accepts all GET and POST requests. On each request it will talk to database and will give appropriate response in JSON format.

<?php
/**
 * File to handle all API requests
 * Accepts GET and POST
 * 
 * Each request will be identified by TAG
 * Response will be JSON data

  /**
 * check for POST request 
 */
if (isset($_POST['tag']) && $_POST['tag'] != '') {
    // get tag
    $tag = $_POST['tag'];

    // include db handler
    require_once 'include/DB_Functions.php';
    $db = new DB_Functions();

    // response Array
    $response = array("tag" => $tag, "success" => 0, "error" => 0);

    // check for tag type
    if ($tag == 'login') {
        // Request type is check Login
        $email = $_POST['email'];
        $password = $_POST['password'];

        // check for user
        $user = $db->getUserByEmailAndPassword($email, $password);
        if ($user != false) {
            // user found
            // echo json with success = 1
            $response["success"] = 1;
            $response["uid"] = $user["unique_id"];
            $response["user"]["name"] = $user["name"];
            $response["user"]["email"] = $user["email"];
            $response["user"]["created_at"] = $user["created_at"];
            $response["user"]["updated_at"] = $user["updated_at"];
            echo json_encode($response);
        } else {
            // user not found
            // echo json with error = 1
            $response["error"] = 1;
            $response["error_msg"] = "Incorrect email or password!";
            echo json_encode($response);
        }
    } else if ($tag == 'register') {
        // Request type is Register new user
        $name = $_POST['name'];
        $email = $_POST['email'];
        $password = $_POST['password'];

        // check if user is already existed
        if ($db->isUserExisted($email)) {
            // user is already existed - error response
            $response["error"] = 2;
            $response["error_msg"] = "User already existed";
            echo json_encode($response);
        } else {
            // store user
            $user = $db->storeUser($name, $email, $password);
            if ($user) {
                // user stored successfully
                $response["success"] = 1;
                $response["uid"] = $user["unique_id"];
                $response["user"]["name"] = $user["name"];
                $response["user"]["email"] = $user["email"];
                $response["user"]["created_at"] = $user["created_at"];
                $response["user"]["updated_at"] = $user["updated_at"];
                echo json_encode($response);
            } else {
                // user failed to store
                $response["error"] = 1;
                $response["error_msg"] = "Error occured in Registartion";
                echo json_encode($response);
            }
        }
    } else {
        echo "Invalid Request";
    }
} else {
    echo "Access Denied";
}
?>

Types of API JSON Responses

The following are the different types of JSON responses generated by API.
Registration Success Response – Success Code = 1 (User Successfully Stored)

{
    "tag": "register",
    "success": 1,
    "error": 0,
    "uid": "4f074ca1e3df49.06340261",
    "user": {
        "name": "Ravi Tamada",
        "email": "ravi8x@gmail.com",
        "created_at": "2012-01-07 01:03:53",
        "updated_at": null
    }
}

Registration Error Response – Error Code = 1 (Error in storing)

{
    "tag": "register",
    "success": 0,
    "error": 1,
    "error_msg": "Error occured in Registartion"
}

Registration Error Response – Error Code = 2 (User Already Existed)

{
    "tag": "register",
    "success": 0,
    "error": 2,
    "error_msg": "User already existed"
}

Login Success Response – Success Code = 1 (User Logged in)

{
    "tag": "login",
    "success": 1,
    "error": 0,
    "uid": "4f074eca601fb8.88015924",
    "user": {
        "name": "Ravi Tamada",
        "email": "ravi8x@gmail.com",
        "created_at": "2012-01-07 01:03:53",
        "updated_at": null
    }
}

Login Error Response – Error Code = 1 (Login Error – Incorrect username/password)

{
    "tag": "login",
    "success": 0,
    "error": 1,
    "error_msg": "Incorrect email or password!"
}

Here it completes the API part and start the Android Project.

3. Starting Android Project

Until now we wrote server side programming to build simple api. Next thing is build android app to interact with the API. In this project i am coding simple app which will have three screens Login Screen, Registration Screen and a welcome Dashboard Screen. So let’s get started by creating new project in you Eclipse IDE.

1. Create a new project by going to File ⇒ New Android Project. Fill all the details and name your activity as DashboardActivity.
2. Next step is to create a new package to store all our library files. Right Click on ⇒ src ⇒ New ⇒ Package and name it as library.

Android create new package

JSON Parser Class

3. Next we need parser class to parse api response JSON. So create a new class in your library package name it as JSONParser.java and fill it with following code.

package com.example.androidhive.library;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

public class JSONParser {

	static InputStream is = null;
	static JSONObject jObj = null;
	static String json = "";

	// constructor
	public JSONParser() {

	}

	public JSONObject getJSONFromUrl(String url, List<NameValuePair> params) {

		// Making HTTP request
		try {
			// defaultHttpClient
			DefaultHttpClient httpClient = new DefaultHttpClient();
			HttpPost httpPost = new HttpPost(url);
			httpPost.setEntity(new UrlEncodedFormEntity(params));

			HttpResponse httpResponse = httpClient.execute(httpPost);
			HttpEntity httpEntity = httpResponse.getEntity();
			is = httpEntity.getContent();

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(
					is, "iso-8859-1"), 8);
			StringBuilder sb = new StringBuilder();
			String line = null;
			while ((line = reader.readLine()) != null) {
				sb.append(line + "n");
			}
			is.close();
			json = sb.toString();
			Log.e("JSON", json);
		} catch (Exception e) {
			Log.e("Buffer Error", "Error converting result " + e.toString());
		}

		// try parse the string to a JSON object
		try {
			jObj = new JSONObject(json);			
		} catch (JSONException e) {
			Log.e("JSON Parser", "Error parsing data " + e.toString());
		}

		// return JSON String
		return jObj;

	}
}

SQLite Database Handler Class

4. In the application to store user information i am using SQLite Database. So create new class in you library package folder and name it as DatabaseHandler.java and fill the class with following code. This class file has functions to handle database operations like storing user and getting user.

Android sqlite table structor
package com.example.androidhive.library;

import java.util.HashMap;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHandler extends SQLiteOpenHelper {

	// All Static variables
	// Database Version
	private static final int DATABASE_VERSION = 1;

	// Database Name
	private static final String DATABASE_NAME = "android_api";

	// Login table name
	private static final String TABLE_LOGIN = "login";

	// Login Table Columns names
	private static final String KEY_ID = "id";
	private static final String KEY_NAME = "name";
	private static final String KEY_EMAIL = "email";
	private static final String KEY_UID = "uid";
	private static final String KEY_CREATED_AT = "created_at";

	public DatabaseHandler(Context context) {
		super(context, DATABASE_NAME, null, DATABASE_VERSION);
	}

	// Creating Tables
	@Override
	public void onCreate(SQLiteDatabase db) {
		String CREATE_LOGIN_TABLE = "CREATE TABLE " + TABLE_LOGIN + "("
				+ KEY_ID + " INTEGER PRIMARY KEY," 
				+ KEY_NAME + " TEXT,"
				+ KEY_EMAIL + " TEXT UNIQUE,"
				+ KEY_UID + " TEXT,"
				+ KEY_CREATED_AT + " TEXT" + ")";
		db.execSQL(CREATE_LOGIN_TABLE);
	}

	// Upgrading database
	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		// Drop older table if existed
		db.execSQL("DROP TABLE IF EXISTS " + TABLE_LOGIN);

		// Create tables again
		onCreate(db);
	}

	/**
	 * Storing user details in database
	 * */
	public void addUser(String name, String email, String uid, String created_at) {
		SQLiteDatabase db = this.getWritableDatabase();

		ContentValues values = new ContentValues();
		values.put(KEY_NAME, name); // Name
		values.put(KEY_EMAIL, email); // Email
		values.put(KEY_UID, uid); // Email
		values.put(KEY_CREATED_AT, created_at); // Created At

		// Inserting Row
		db.insert(TABLE_LOGIN, null, values);
		db.close(); // Closing database connection
	}
	
	/**
	 * Getting user data from database
	 * */
	public HashMap<String, String> getUserDetails(){
		HashMap<String,String> user = new HashMap<String,String>();
		String selectQuery = "SELECT  * FROM " + TABLE_LOGIN;
		 
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(selectQuery, null);
        // Move to first row
        cursor.moveToFirst();
        if(cursor.getCount() > 0){
        	user.put("name", cursor.getString(1));
        	user.put("email", cursor.getString(2));
        	user.put("uid", cursor.getString(3));
        	user.put("created_at", cursor.getString(4));
        }
        cursor.close();
        db.close();
		// return user
		return user;
	}

	/**
	 * Getting user login status
	 * return true if rows are there in table
	 * */
	public int getRowCount() {
		String countQuery = "SELECT  * FROM " + TABLE_LOGIN;
		SQLiteDatabase db = this.getReadableDatabase();
		Cursor cursor = db.rawQuery(countQuery, null);
		int rowCount = cursor.getCount();
		db.close();
		cursor.close();
		
		// return row count
		return rowCount;
	}
	
	/**
	 * Re crate database
	 * Delete all tables and create them again
	 * */
	public void resetTables(){
		SQLiteDatabase db = this.getWritableDatabase();
		// Delete All Rows
		db.delete(TABLE_LOGIN, null, null);
		db.close();
	}

}

User Functions Class

5. Create a new class file under library package and name it as UserFunctions.java. This class will have functions to handle all user events like
loginUser()
registerUser()
getLoginStatus()
logoutUser().

android testing app localhost vs online

In this class all the functions will interact with JSONParser, DatabaseHandler classes. I am testing API in localhost using xampp software. Normally localhost will run on port http://127.0.0.1 or http://localhost/. In AVD to connect to localhost you need to use url http://10.0.2.2/ instead of http://localhost/. If you want deploy your api on website the use the url http://yoursite.com/api/

package com.example.androidhive.library;

import java.util.ArrayList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONObject;

import android.content.Context;

public class UserFunctions {
	
	private JSONParser jsonParser;
	
    // Testing in localhost using wamp or xampp 
    // use http://10.0.2.2/ to connect to your localhost ie http://localhost/
	private static String loginURL = "http://10.0.2.2/ah_login_api/";
	private static String registerURL = "http://10.0.2.2/ah_login_api/";
	
	private static String login_tag = "login";
	private static String register_tag = "register";
	
	// constructor
	public UserFunctions(){
		jsonParser = new JSONParser();
	}
	
	/**
	 * function make Login Request
	 * @param email
	 * @param password
	 * */
	public JSONObject loginUser(String email, String password){
		// Building Parameters
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("tag", login_tag));
		params.add(new BasicNameValuePair("email", email));
		params.add(new BasicNameValuePair("password", password));
		JSONObject json = jsonParser.getJSONFromUrl(loginURL, params);
		// return json
		// Log.e("JSON", json.toString());
		return json;
	}
	
	/**
	 * function make Login Request
	 * @param name
	 * @param email
	 * @param password
	 * */
	public JSONObject registerUser(String name, String email, String password){
		// Building Parameters
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("tag", register_tag));
		params.add(new BasicNameValuePair("name", name));
		params.add(new BasicNameValuePair("email", email));
		params.add(new BasicNameValuePair("password", password));
		
		// getting JSON Object
		JSONObject json = jsonParser.getJSONFromUrl(registerURL, params);
		// return json
		return json;
	}
	
	/**
	 * Function get Login status
	 * */
	public boolean isUserLoggedIn(Context context){
		DatabaseHandler db = new DatabaseHandler(context);
		int count = db.getRowCount();
		if(count > 0){
			// user logged in
			return true;
		}
		return false;
	}
	
	/**
	 * Function to logout user
	 * Reset Database
	 * */
	public boolean logoutUser(Context context){
		DatabaseHandler db = new DatabaseHandler(context);
		db.resetTables();
		return true;
	}
	
}

Designing the Screens

6. Until now we have developed the library classes needed in this application. Next thing is build screens. We need three screens Login Screen, Registration Screen and Dashboard Screen.
Create 3 xml files under res ⇒ layout folder and name them as login.xml, register.xml and dashboard.xml

login.xml – login screen design layout

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#3b3b3b" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical"
        android:padding="10dip" >
		<!--  View Title Label -->
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dip"
            android:text="LOGIN"
            android:textSize="25dip"
            android:textStyle="bold" />
		<!--  Email Label -->
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Email" />
		<!--  Email TextField -->
        <EditText
            android:id="@+id/loginEmail"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        
		<!--  Password Label -->
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="15dip"
            android:text="Password" />
		<!--  Password TextField -->
        <EditText
            android:id="@+id/loginPassword"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:password="true" />
        
        <!--  Error message -->
        <TextView android:id="@+id/login_error"
            		android:layout_width="fill_parent"
            		android:layout_height="wrap_content"
            		android:textColor="#e30000"
            		android:padding="10dip"
            		android:textStyle="bold"/>

		<!--  Login Button -->        
        <Button
            android:id="@+id/btnLogin"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dip"
            android:text="Login" />

        <!--  Link to Registration Screen -->
        <Button
            android:id="@+id/btnLinkToRegisterScreen"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dip"
            android:background="@null"
            android:text="I don&apos;t have account. Register Me!"
            android:textColor="#21dbd4"
            android:textStyle="bold" />
    </LinearLayout>

</ScrollView>
android login screen

register.xml – registration screen design layout

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#3b3b3b" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical"
        android:padding="10dip" >
		<!--  View Title Label -->
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dip"
            android:text="REGISTER"
            android:textSize="25dip"
            android:textStyle="bold" />
        <!--  Name Label -->
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Full Name" />
		<!--  Name TextField -->
        <EditText
            android:id="@+id/registerName"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        
		<!--  Email Label -->
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Email" />
		<!--  Email TextField -->
        <EditText
            android:id="@+id/registerEmail"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        
		<!--  Password Label -->
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="15dip"
            android:text="Password" />
		<!--  Password TextField -->
        <EditText
            android:id="@+id/registerPassword"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:password="true" />
        
        <!--  Error message -->
        <TextView android:id="@+id/register_error"
            		android:layout_width="fill_parent"
            		android:layout_height="wrap_content"
            		android:textColor="#e30000"
            		android:padding="10dip"
            		android:textStyle="bold"/>

		<!--  Login Button -->        
        <Button
            android:id="@+id/btnRegister"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dip"
            android:text="Register" />

        <!--  Link to Login Screen -->
        <Button
            android:id="@+id/btnLinkToLoginScreen"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dip"
            android:background="@null"
            android:text="Already registred. Login Me!"
            android:textColor="#21dbd4"
            android:textStyle="bold" />
    </LinearLayout>

</ScrollView>
android registration screen

dashboard.xml – dashboard screen design layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#3b3b3b">
    
    <TextView android:layout_width="fill_parent"
        	  android:layout_height="wrap_content"
        	  android:text="WELCOME"
        	  android:textSize="40dip"
        	  android:gravity="center"
        	  android:layout_marginTop="20dip"/>
    
    <Button android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Logout Me"
        android:textSize="20dip"
        android:textColor="#21dbd4"
        android:textStyle="bold" 
        android:id="@+id/btnLogout"
        android:layout_marginTop="80dip"
        android:background="@null"/>

</LinearLayout>
Android welcome screen

Switching between Activites

7. Now the designing part of the app is done next thing is to create activities for each layout and write functionality to achieve login and registration process.
Create new activities LoginActivity.java and RegisterActivity.java and fill them with respective code below.

LoginActivity.java – Activity to handle login event

package com.example.androidhive;

import java.util.HashMap;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.example.androidhive.library.DatabaseHandler;
import com.example.androidhive.library.UserFunctions;

public class LoginActivity extends Activity {
	Button btnLogin;
	Button btnLinkToRegister;
	EditText inputEmail;
	EditText inputPassword;
	TextView loginErrorMsg;

	// JSON Response node names
	private static String KEY_SUCCESS = "success";
	private static String KEY_ERROR = "error";
	private static String KEY_ERROR_MSG = "error_msg";
	private static String KEY_UID = "uid";
	private static String KEY_NAME = "name";
	private static String KEY_EMAIL = "email";
	private static String KEY_CREATED_AT = "created_at";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.login);

		// Importing all assets like buttons, text fields
		inputEmail = (EditText) findViewById(R.id.loginEmail);
		inputPassword = (EditText) findViewById(R.id.loginPassword);
		btnLogin = (Button) findViewById(R.id.btnLogin);
		btnLinkToRegister = (Button) findViewById(R.id.btnLinkToRegisterScreen);
		loginErrorMsg = (TextView) findViewById(R.id.login_error);

		// Login button Click Event
		btnLogin.setOnClickListener(new View.OnClickListener() {

			public void onClick(View view) {
				String email = inputEmail.getText().toString();
				String password = inputPassword.getText().toString();
				UserFunctions userFunction = new UserFunctions();
				JSONObject json = userFunction.loginUser(email, password);

				// check for login response
				try {
					if (json.getString(KEY_SUCCESS) != null) {
						loginErrorMsg.setText("");
						String res = json.getString(KEY_SUCCESS); 
						if(Integer.parseInt(res) == 1){
							// user successfully logged in
							// Store user details in SQLite Database
							DatabaseHandler db = new DatabaseHandler(getApplicationContext());
							JSONObject json_user = json.getJSONObject("user");
							
							// Clear all previous data in database
							userFunction.logoutUser(getApplicationContext());
							db.addUser(json_user.getString(KEY_NAME), json_user.getString(KEY_EMAIL), json.getString(KEY_UID), json_user.getString(KEY_CREATED_AT));						
							
							// Launch Dashboard Screen
							Intent dashboard = new Intent(getApplicationContext(), DashboardActivity.class);
							
							// Close all views before launching Dashboard
							dashboard.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
							startActivity(dashboard);
							
							// Close Login Screen
							finish();
						}else{
							// Error in login
							loginErrorMsg.setText("Incorrect username/password");
						}
					}
				} catch (JSONException e) {
					e.printStackTrace();
				}
			}
		});

		// Link to Register Screen
		btnLinkToRegister.setOnClickListener(new View.OnClickListener() {

			public void onClick(View view) {
				Intent i = new Intent(getApplicationContext(),
						RegisterActivity.class);
				startActivity(i);
				finish();
			}
		});
	}
}

RegisterActivity.java – Activity to handle registration event

package com.example.androidhive;

import org.json.JSONException;
import org.json.JSONObject;

import com.example.androidhive.library.DatabaseHandler;
import com.example.androidhive.library.UserFunctions;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class RegisterActivity extends Activity {
	Button btnRegister;
	Button btnLinkToLogin;
	EditText inputFullName;
	EditText inputEmail;
	EditText inputPassword;
	TextView registerErrorMsg;
	
	// JSON Response node names
	private static String KEY_SUCCESS = "success";
	private static String KEY_ERROR = "error";
	private static String KEY_ERROR_MSG = "error_msg";
	private static String KEY_UID = "uid";
	private static String KEY_NAME = "name";
	private static String KEY_EMAIL = "email";
	private static String KEY_CREATED_AT = "created_at";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.register);

		// Importing all assets like buttons, text fields
		inputFullName = (EditText) findViewById(R.id.registerName);
		inputEmail = (EditText) findViewById(R.id.registerEmail);
		inputPassword = (EditText) findViewById(R.id.registerPassword);
		btnRegister = (Button) findViewById(R.id.btnRegister);
		btnLinkToLogin = (Button) findViewById(R.id.btnLinkToLoginScreen);
		registerErrorMsg = (TextView) findViewById(R.id.register_error);
		
		// Register Button Click event
		btnRegister.setOnClickListener(new View.OnClickListener() {			
			public void onClick(View view) {
				String name = inputFullName.getText().toString();
				String email = inputEmail.getText().toString();
				String password = inputPassword.getText().toString();
				UserFunctions userFunction = new UserFunctions();
				JSONObject json = userFunction.registerUser(name, email, password);
				
				// check for login response
				try {
					if (json.getString(KEY_SUCCESS) != null) {
						registerErrorMsg.setText("");
						String res = json.getString(KEY_SUCCESS); 
						if(Integer.parseInt(res) == 1){
							// user successfully registred
							// Store user details in SQLite Database
							DatabaseHandler db = new DatabaseHandler(getApplicationContext());
							JSONObject json_user = json.getJSONObject("user");
							
							// Clear all previous data in database
							userFunction.logoutUser(getApplicationContext());
							db.addUser(json_user.getString(KEY_NAME), json_user.getString(KEY_EMAIL), json.getString(KEY_UID), json_user.getString(KEY_CREATED_AT));						
							// Launch Dashboard Screen
							Intent dashboard = new Intent(getApplicationContext(), DashboardActivity.class);
							// Close all views before launching Dashboard
							dashboard.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
							startActivity(dashboard);
							// Close Registration Screen
							finish();
						}else{
							// Error in registration
							registerErrorMsg.setText("Error occured in registration");
						}
					}
				} catch (JSONException e) {
					e.printStackTrace();
				}
			}
		});

		// Link to Login Screen
		btnLinkToLogin.setOnClickListener(new View.OnClickListener() {

			public void onClick(View view) {
				Intent i = new Intent(getApplicationContext(),
						LoginActivity.class);
				startActivity(i);
				// Close Registration View
				finish();
			}
		});
	}
}

DashboardActivity.java – Activity to handle dashboard event

package com.example.androidhive;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.example.androidhive.library.UserFunctions;

public class DashboardActivity extends Activity {
	UserFunctions userFunctions;
	Button btnLogout;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        /**
         * Dashboard Screen for the application
         * */        
        // Check login status in database
        userFunctions = new UserFunctions();
        if(userFunctions.isUserLoggedIn(getApplicationContext())){
       // user already logged in show databoard
        	setContentView(R.layout.dashboard);
        	btnLogout = (Button) findViewById(R.id.btnLogout);
        	
        	btnLogout.setOnClickListener(new View.OnClickListener() {
    			
    			public void onClick(View arg0) {
    				// TODO Auto-generated method stub
    				userFunctions.logoutUser(getApplicationContext());
    				Intent login = new Intent(getApplicationContext(), LoginActivity.class);
    	        	login.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    	        	startActivity(login);
    	        	// Closing dashboard screen
    	        	finish();
    			}
    		});
        	
        }else{
        	// user is not logged in show login screen
        	Intent login = new Intent(getApplicationContext(), LoginActivity.class);
        	login.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        	startActivity(login);
        	// Closing dashboard screen
        	finish();
        }        
    }
}

Finally Updating AndroidManifest.xml

Don’t forget to update you AndroidManifest.xml file. Change following modifications
⇒ Add Internet Persmissions
⇒ Add Entries of each Activity

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidhive"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".DashboardActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <!--  Login Activity -->
        <activity
            android:label="Login Account" 
            android:name=".LoginActivity"></activity>

        <!--  Register Activity -->
        <activity
            android:label="Register New Account" 
            android:name=".RegisterActivity"></activity>
    </application>
    
    <!-- Allow to connect with internet -->
	<uses-permission android:name="android.permission.INTERNET" />

</manifest>

8. Make sure that you have the files placed as in the following image

android app directory structor

Run your project by right clicking on your project folder ⇒ Run As ⇒ 1 Android Application.

This is image is for thumbnail purpose
android connecting to php, mysql and sqlite

How to connect Android with PHP, MySQL

$
0
0

We are going see how to make a very simple Android app (in our case, a product inventory app) that will call a PHP script to perform basic CRUD(Create, Read, Update, Delete) operations. To brief you on the architecture, this is how it works. First your android app calls a PHP script in order to perform a data operation, lets say “create”. The PHP script then connects to your MySQL database to perform the operation.
So the data flows from your Android app to PHP script then finally is stored in your MySQL database. Allright, lets dig deeper.

Download Code

Please note that the purpose of the code that I have provided here is to, ease you (beginner level) into connecting an Android app with PHP, MYSQL. You should not take this as a standard or secure coding practice. In production environment, you ideally need to avoid any code that will potentially inject vulnerabilities (like MYSQL Injection). MySQL injection itself is a huge topic and cannot be covered in this single post and that is not the agenda of this post either.

1. What is WAMP Server

WAMP is acronym for Windows, Apache, MySQL and PHP, Perl, Python. WAMP software is one click installer which creates an environment for developing PHP, MySQL web application. By installing this software you will be installing Apache, MySQL and PHP. Alternatively you can use XAMP Server also.

wamp server installation

2. Installing and Running WAMP Server

Download & Install WAMP server from www.wampserver.com/en/. Once you have installed wamp server, launch the program from Start -> All Programs -> WampServer -> StartWampServer.

You can test your server by opening the address http://localhost/ in your browser.
Also you can check phpmyadmin by opening http://localhost/phpmyadmin

Following is a screen cast of Downloading and Installing WAMP Server.

3. Creating and Running PHP Project

Now you have the environment ready to develop a PHP & MySQL project. Go to the location where you installed WAMP server (In my case i installed in C:\wamp\) and go to www folder and create a new folder for your project. You have to place all your project files inside this folder.

Create a folder called android_connect and create a new php file called test.php and try out simple php code. After placing following code try to open http://localhost/android_connect/test.php and you should see a message called “Welcome, I am connecting Android to PHP, MySQL“.

test.php

<?php
    echo "Welcome, I am connecting Android to PHP, MySQL";
?>

Following is a screen cast of Creating and Running a simple PHP project.

4. Creating MySQL Database and Tables

In this tutorial i am creating a simple database with one table. Through out this tutorial i am using same table to perform example operations. Now open phpmyadmin by opening the address http://localhost/phpmyadmin/ in your browser. You can use the PhpMyAdmin tool to create a database and a table.

I am creating a database named androidhive and a table called products.

CREATE DATABASE androidhive;
CREATE TABLE products(
pid int(11) primary key auto_increment,
name varchar(100) not null,
price decimal(10,2) not null,
description text,
created_at timestamp default now(),
updated_at timestamp
);

Following is a screen cast of Creating database and tables in phpmyadmin

5. Connecting to MySQL database using PHP

Now the actual server side coding starts. Create a PHP class to connect to MySQL database. The main purpose of this class is to open a connection to database and close the connection whenever its not needed. So create two files called db_config.php and db_connect.php

db_config.php – will have database connection variables
db_connect.php – a class file to connect to database

Following is code for two php files

db_config.php

<?php

/*
 * All database connection variables
 */

define('DB_USER', "root"); // db user
define('DB_PASSWORD', ""); // db password (mention your db password here)
define('DB_DATABASE', "androidhive"); // database name
define('DB_SERVER', "localhost"); // db server
?>

db_connect.php

<?php

/**
 * A class file to connect to database
 */
class DB_CONNECT {

    // constructor
    function __construct() {
        // connecting to database
        $this->connect();
    }

    // destructor
    function __destruct() {
        // closing db connection
        $this->close();
    }

    /**
     * Function to connect with database
     */
    function connect() {
        // import database connection variables
        require_once __DIR__ . '/db_config.php';

        // Connecting to mysql database
        $con = mysql_connect(DB_SERVER, DB_USER, DB_PASSWORD) or die(mysql_error());

        // Selecing database
        $db = mysql_select_db(DB_DATABASE) or die(mysql_error()) or die(mysql_error());

        // returing connection cursor
        return $con;
    }

    /**
     * Function to close db connection
     */
    function close() {
        // closing db connection
        mysql_close();
    }

}

?>

Usage: When ever you want to connect to MySQL database and do some operations use the db_connect.php class like this

$db = new DB_CONNECT(); // creating class object(will open database connection)

6. Basic MySQL CRUD Operations using PHP

In this tutorial i am covering basic CRUD (Create, Read, Update, Delete) operations on MySQL database using PHP.
If you are a novice about PHP and MySQL i suggest, you to learn basic PHP and SQL here.

6.a) Creating a row in MySQL (Creating a new product row)

In your PHP project create a new php file called create_product.php and place the following code. This file is mainly for creating a new product in products table.

In the following code i am reading product data via POST and storing them in products table. At the end i am echoing appropriate JSON as response.

<?php

/*
 * Following code will create a new product row
 * All product details are read from HTTP Post Request
 */

// array for JSON response
$response = array();

// check for required fields
if (isset($_POST['name']) && isset($_POST['price']) && isset($_POST['description'])) {

    $name = $_POST['name'];
    $price = $_POST['price'];
    $description = $_POST['description'];

    // include db connect class
    require_once __DIR__ . '/db_connect.php';

    // connecting to db
    $db = new DB_CONNECT();

    // mysql inserting a new row
    $result = mysql_query("INSERT INTO products(name, price, description) VALUES('$name', '$price', '$description')");

    // check if row inserted or not
    if ($result) {
        // successfully inserted into database
        $response["success"] = 1;
        $response["message"] = "Product successfully created.";

        // echoing JSON response
        echo json_encode($response);
    } else {
        // failed to insert row
        $response["success"] = 0;
        $response["message"] = "Oops! An error occurred.";

        // echoing JSON response
        echo json_encode($response);
    }
} else {
    // required field is missing
    $response["success"] = 0;
    $response["message"] = "Required field(s) is missing";

    // echoing JSON response
    echo json_encode($response);
}
?>

For the above code JSON response will be like

When POST param(s) is missing

{
    "success": 0,
    "message": "Required field(s) is missing"
}

When product is successfully created

{
    "success": 1,
    "message": "Product successfully created."
}

When error occurred while inserting data

{
    "success": 0,
    "message": "Oops! An error occurred."
}

6.b) Reading a Row from MySQL (Reading product details)

Create a new php file called get_product_details.php and write the following code. This file will get single product details by taking product id (pid) as post parameter.

<?php

/*
 * Following code will get single product details
 * A product is identified by product id (pid)
 */

// array for JSON response
$response = array();

// include db connect class
require_once __DIR__ . '/db_connect.php';

// connecting to db
$db = new DB_CONNECT();

// check for post data
if (isset($_GET["pid"])) {
    $pid = $_GET['pid'];

    // get a product from products table
    $result = mysql_query("SELECT *FROM products WHERE pid = $pid");

    if (!empty($result)) {
        // check for empty result
        if (mysql_num_rows($result) > 0) {

            $result = mysql_fetch_array($result);

            $product = array();
            $product["pid"] = $result["pid"];
            $product["name"] = $result["name"];
            $product["price"] = $result["price"];
            $product["description"] = $result["description"];
            $product["created_at"] = $result["created_at"];
            $product["updated_at"] = $result["updated_at"];
            // success
            $response["success"] = 1;

            // user node
            $response["product"] = array();

            array_push($response["product"], $product);

            // echoing JSON response
            echo json_encode($response);
        } else {
            // no product found
            $response["success"] = 0;
            $response["message"] = "No product found";

            // echo no users JSON
            echo json_encode($response);
        }
    } else {
        // no product found
        $response["success"] = 0;
        $response["message"] = "No product found";

        // echo no users JSON
        echo json_encode($response);
    }
} else {
    // required field is missing
    $response["success"] = 0;
    $response["message"] = "Required field(s) is missing";

    // echoing JSON response
    echo json_encode($response);
}
?>

The json response for the above file will be

When successfully getting product details

{
    "success": 1,
    "product": [
        {
            "pid": "1",
            "name": "iPHone 4S",
            "price": "300.00",
            "description": "iPhone 4S white",
            "created_at": "2012-04-29 01:41:42",
            "updated_at": "0000-00-00 00:00:00"
        }
    ]
}

When no product found with matched pid

{
    "success": 0,
    "message": "No product found"
}

6.c) Reading All Rows from MySQL (Reading all products)

We need a json to list all the products on android device. So create a new php file named get_all_products.php and write following code.

<?php

/*
 * Following code will list all the products
 */

// array for JSON response
$response = array();

// include db connect class
require_once __DIR__ . '/db_connect.php';

// connecting to db
$db = new DB_CONNECT();

// get all products from products table
$result = mysql_query("SELECT *FROM products") or die(mysql_error());

// check for empty result
if (mysql_num_rows($result) > 0) {
    // looping through all results
    // products node
    $response["products"] = array();

    while ($row = mysql_fetch_array($result)) {
        // temp user array
        $product = array();
        $product["pid"] = $row["pid"];
        $product["name"] = $row["name"];
        $product["price"] = $row["price"];
        $product["created_at"] = $row["created_at"];
        $product["updated_at"] = $row["updated_at"];

        // push single product into final response array
        array_push($response["products"], $product);
    }
    // success
    $response["success"] = 1;

    // echoing JSON response
    echo json_encode($response);
} else {
    // no products found
    $response["success"] = 0;
    $response["message"] = "No products found";

    // echo no users JSON
    echo json_encode($response);
}
?>

And the JSON response for above code

Listing all Products

{
    "products": [
        {
            "pid": "1",
            "name": "iPhone 4S",
            "price": "300.00",
            "created_at": "2012-04-29 02:04:02",
            "updated_at": "0000-00-00 00:00:00"
        },
        {
            "pid": "2",
            "name": "Macbook Pro",
            "price": "600.00",
            "created_at": "2012-04-29 02:04:51",
            "updated_at": "0000-00-00 00:00:00"
        },
        {
            "pid": "3",
            "name": "Macbook Air",
            "price": "800.00",
            "created_at": "2012-04-29 02:05:57",
            "updated_at": "0000-00-00 00:00:00"
        },
        {
            "pid": "4",
            "name": "OS X Lion",
            "price": "100.00",
            "created_at": "2012-04-29 02:07:14",
            "updated_at": "0000-00-00 00:00:00"
        }
    ],
    "success": 1
}

When products not found

{
    "success": 0,
    "message": "No products found"
}

6.d) Updating a Row in MySQL (Updating product details)

Create a php file named update_product.php to update product details. Each product is identified by pid.

<?php

/*
 * Following code will update a product information
 * A product is identified by product id (pid)
 */

// array for JSON response
$response = array();

// check for required fields
if (isset($_POST['pid']) && isset($_POST['name']) && isset($_POST['price']) && isset($_POST['description'])) {

    $pid = $_POST['pid'];
    $name = $_POST['name'];
    $price = $_POST['price'];
    $description = $_POST['description'];

    // include db connect class
    require_once __DIR__ . '/db_connect.php';

    // connecting to db
    $db = new DB_CONNECT();

    // mysql update row with matched pid
    $result = mysql_query("UPDATE products SET name = '$name', price = '$price', description = '$description' WHERE pid = $pid");

    // check if row inserted or not
    if ($result) {
        // successfully updated
        $response["success"] = 1;
        $response["message"] = "Product successfully updated.";

        // echoing JSON response
        echo json_encode($response);
    } else {

    }
} else {
    // required field is missing
    $response["success"] = 0;
    $response["message"] = "Required field(s) is missing";

    // echoing JSON response
    echo json_encode($response);
}
?>

The json reponse of above code, when product is updated successfully

{
    "success": 1,
    "message": "Product successfully updated."
}

6.e) Deleting a Row in MySQL (Deleting a product)

The last operation is deletion on database. Create a new php file called delete_product.php and paste the following code. The main functionality of this file is to delete a product from database.

<?php

/*
 * Following code will delete a product from table
 * A product is identified by product id (pid)
 */

// array for JSON response
$response = array();

// check for required fields
if (isset($_POST['pid'])) {
    $pid = $_POST['pid'];

    // include db connect class
    require_once __DIR__ . '/db_connect.php';

    // connecting to db
    $db = new DB_CONNECT();

    // mysql update row with matched pid
    $result = mysql_query("DELETE FROM products WHERE pid = $pid");

    // check if row deleted or not
    if (mysql_affected_rows() > 0) {
        // successfully updated
        $response["success"] = 1;
        $response["message"] = "Product successfully deleted";

        // echoing JSON response
        echo json_encode($response);
    } else {
        // no product found
        $response["success"] = 0;
        $response["message"] = "No product found";

        // echo no users JSON
        echo json_encode($response);
    }
} else {
    // required field is missing
    $response["success"] = 0;
    $response["message"] = "Required field(s) is missing";

    // echoing JSON response
    echo json_encode($response);
}
?>

When product successfully deleted

{
    "success": 1,
    "message": "Product successfully deleted"
}

When product not found

{
    "success": 0,
    "message": "No product found"
}

Until now, we built a simple api for our products table. We are now done with the server side coding (PHP) and its time to take a break and start our actual android application coding.

7. Creating Android Application

Create a new project in your Eclipse IDE by filling the required details.

1. Create new project in Eclipse IDE by going to File ⇒ New ⇒ Android Project and name the Activity class name as MainScreenActivity.

2. Open your AndroidManifest.xml file and add following code. First i am adding all the classes i am creating to manifest file. Also i am adding INTERNET Connect permission.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidhive"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:configChanges="keyboardHidden|orientation"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >

        <activity
            android:name=".MainScreenActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- All Product Activity -->
        <activity
            android:name=".AllProductsActivity"
            android:label="All Products" >
        </activity>

        <!-- Add Product Activity -->
        <activity
            android:name=".NewProductActivity"
            android:label="Add New Product" >
        </activity>

        <!-- Edit Product Activity -->
        <activity
            android:name=".EditProductActivity"
            android:label="Edit Product" >
        </activity>
    </application>

    <!--  Internet Permissions -->
    <uses-permission android:name="android.permission.INTERNET" />

</manifest>

3. Now create a new xml file under res ⇒ layout folder and name it as main_screen.xml This layout file contains two simple buttons to view all products and add a new product.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal">

    <!--  Sample Dashboard screen with Two buttons -->
    <!--  Button to view all products screen -->
    <Button android:id="@+id/btnViewProducts"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="View Products"
        android:layout_marginTop="25dip"/>

    <!--  Button to create a new product screen -->
    <Button android:id="@+id/btnCreateProduct"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Add New Products"
        android:layout_marginTop="25dip"/>

</LinearLayout>
main screen

4. Open you main activity class which is MainScreenActivity.java and write click events for two button which are mentioned in main_screen.xml layout.

package com.example.androidhive;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainScreenActivity extends Activity{

	Button btnViewProducts;
	Button btnNewProduct;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main_screen);

		// Buttons
		btnViewProducts = (Button) findViewById(R.id.btnViewProducts);
		btnNewProduct = (Button) findViewById(R.id.btnCreateProduct);

		// view products click event
		btnViewProducts.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View view) {
				// Launching All products Activity
				Intent i = new Intent(getApplicationContext(), AllProductsActivity.class);
				startActivity(i);

			}
		});

		// view products click event
		btnNewProduct.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View view) {
				// Launching create new product activity
				Intent i = new Intent(getApplicationContext(), NewProductActivity.class);
				startActivity(i);

			}
		});
	}
}

Displaying All Products in ListView (Read)

5. Now we need an Activity display all the products in list view format. As we know list view needs two xml files, one for listview and other is for single list row. Create two xml files under res ⇒ layout folder and name it as all_products.xml and list_item.xml

all_products.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
	<!-- Main ListView
		 Always give id value as list(@android:id/list)
	-->
    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <!-- Product id (pid) - will be HIDDEN - used to pass to other activity -->
    <TextView
        android:id="@+id/pid"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:visibility="gone" />

    <!-- Name Label -->
    <TextView
        android:id="@+id/name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingTop="6dip"
        android:paddingLeft="6dip"
        android:textSize="17dip"
        android:textStyle="bold" />

</LinearLayout>

6. Create a new class file and name it as AllProductsActivity.java. In the following code

-> First a request is send to get_all_products.php file using a Background Async task thread.
-> After getting JSON from get_all_products.php, i parsed it and displayed in a listview.
-> If there are no products found AddNewProductAcivity is launched.

package com.example.androidhive;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.http.NameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;

public class AllProductsActivity extends ListActivity {

	// Progress Dialog
	private ProgressDialog pDialog;

	// Creating JSON Parser object
	JSONParser jParser = new JSONParser();

	ArrayList<HashMap<String, String>> productsList;

	// url to get all products list
	private static String url_all_products = "http://api.androidhive.info/android_connect/get_all_products.php";

	// JSON Node names
	private static final String TAG_SUCCESS = "success";
	private static final String TAG_PRODUCTS = "products";
	private static final String TAG_PID = "pid";
	private static final String TAG_NAME = "name";

	// products JSONArray
	JSONArray products = null;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.all_products);

		// Hashmap for ListView
		productsList = new ArrayList<HashMap<String, String>>();

		// Loading products in Background Thread
		new LoadAllProducts().execute();

		// Get listview
		ListView lv = getListView();

		// on seleting single product
		// launching Edit Product Screen
		lv.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				// getting values from selected ListItem
				String pid = ((TextView) view.findViewById(R.id.pid)).getText()
						.toString();

				// Starting new intent
				Intent in = new Intent(getApplicationContext(),
						EditProductActivity.class);
				// sending pid to next activity
				in.putExtra(TAG_PID, pid);

				// starting new activity and expecting some response back
				startActivityForResult(in, 100);
			}
		});

	}

	// Response from Edit Product Activity
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		// if result code 100
		if (resultCode == 100) {
			// if result code 100 is received
			// means user edited/deleted product
			// reload this screen again
			Intent intent = getIntent();
			finish();
			startActivity(intent);
		}

	}

	/**
	 * Background Async Task to Load all product by making HTTP Request
	 * */
	class LoadAllProducts extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(AllProductsActivity.this);
			pDialog.setMessage("Loading products. Please wait...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		/**
		 * getting All products from url
		 * */
		protected String doInBackground(String... args) {
			// Building Parameters
			List<NameValuePair> params = new ArrayList<NameValuePair>();
			// getting JSON string from URL
			JSONObject json = jParser.makeHttpRequest(url_all_products, "GET", params);

			// Check your log cat for JSON reponse
			Log.d("All Products: ", json.toString());

			try {
				// Checking for SUCCESS TAG
				int success = json.getInt(TAG_SUCCESS);

				if (success == 1) {
					// products found
					// Getting Array of Products
					products = json.getJSONArray(TAG_PRODUCTS);

					// looping through All Products
					for (int i = 0; i < products.length(); i++) {
						JSONObject c = products.getJSONObject(i);

						// Storing each json item in variable
						String id = c.getString(TAG_PID);
						String name = c.getString(TAG_NAME);

						// creating new HashMap
						HashMap<String, String> map = new HashMap<String, String>();

						// adding each child node to HashMap key => value
						map.put(TAG_PID, id);
						map.put(TAG_NAME, name);

						// adding HashList to ArrayList
						productsList.add(map);
					}
				} else {
					// no products found
					// Launch Add New product Activity
					Intent i = new Intent(getApplicationContext(),
							NewProductActivity.class);
					// Closing all previous activities
					i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
					startActivity(i);
				}
			} catch (JSONException e) {
				e.printStackTrace();
			}

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog after getting all products
			pDialog.dismiss();
			// updating UI from Background Thread
			runOnUiThread(new Runnable() {
				public void run() {
					/**
					 * Updating parsed JSON data into ListView
					 * */
					ListAdapter adapter = new SimpleAdapter(
							AllProductsActivity.this, productsList,
							R.layout.list_item, new String[] { TAG_PID,
									TAG_NAME},
							new int[] { R.id.pid, R.id.name });
					// updating listview
					setListAdapter(adapter);
				}
			});

		}

	}
}
android list products

Adding a New Product (Write)

7. Create a new view and acivity to add a new product into mysql database. Create a simple form which contains EditText for product name, price and description.

Create a new xml file and name it as add_product.xml and paste the following code to create a simple form.

add_product.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- Name Label -->
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Product Name"
        android:paddingLeft="10dip"
        android:paddingRight="10dip"
        android:paddingTop="10dip"
        android:textSize="17dip"/>

    <!-- Input Name -->
	<EditText android:id="@+id/inputName"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:layout_margin="5dip"
	    android:layout_marginBottom="15dip"
	    android:singleLine="true"/>

	<!-- Price Label -->
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Price"
        android:paddingLeft="10dip"
        android:paddingRight="10dip"
        android:paddingTop="10dip"
        android:textSize="17dip"/>

    <!-- Input Price -->
	<EditText android:id="@+id/inputPrice"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:layout_margin="5dip"
	    android:layout_marginBottom="15dip"
	    android:singleLine="true"
	    android:inputType="numberDecimal"/>

	<!-- Description Label -->
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Description"
        android:paddingLeft="10dip"
        android:paddingRight="10dip"
        android:paddingTop="10dip"
        android:textSize="17dip"/>

    <!-- Input description -->
	<EditText android:id="@+id/inputDesc"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:layout_margin="5dip"
	    android:layout_marginBottom="15dip"
	    android:lines="4"
	    android:gravity="top"/>

	<!-- Button Create Product -->
	<Button android:id="@+id/btnCreateProduct"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="Create Product"/>

</LinearLayout>
android adding new product

8. Now create new Activity to insert a new product into mysql database. Create a class file and name it as NewProductActivity.java and type the following code. In the following code

-> First new product data is read from the EditText form and formatted into a basic params.
-> A request is made to create_product.php to create a new product through HTTP post.
-> After getting json response from create_product.php, If success bit is 1 then list view is refreshed with newly added product.

package com.example.androidhive;

import java.util.ArrayList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class NewProductActivity extends Activity {

	// Progress Dialog
	private ProgressDialog pDialog;

	JSONParser jsonParser = new JSONParser();
	EditText inputName;
	EditText inputPrice;
	EditText inputDesc;

	// url to create new product
	private static String url_create_product = "http://api.androidhive.info/android_connect/create_product.php";

	// JSON Node names
	private static final String TAG_SUCCESS = "success";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.add_product);

		// Edit Text
		inputName = (EditText) findViewById(R.id.inputName);
		inputPrice = (EditText) findViewById(R.id.inputPrice);
		inputDesc = (EditText) findViewById(R.id.inputDesc);

		// Create button
		Button btnCreateProduct = (Button) findViewById(R.id.btnCreateProduct);

		// button click event
		btnCreateProduct.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View view) {
				// creating new product in background thread
				new CreateNewProduct().execute();
			}
		});
	}

	/**
	 * Background Async Task to Create new product
	 * */
	class CreateNewProduct extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(NewProductActivity.this);
			pDialog.setMessage("Creating Product..");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(true);
			pDialog.show();
		}

		/**
		 * Creating product
		 * */
		protected String doInBackground(String... args) {
			String name = inputName.getText().toString();
			String price = inputPrice.getText().toString();
			String description = inputDesc.getText().toString();

			// Building Parameters
			List<NameValuePair> params = new ArrayList<NameValuePair>();
			params.add(new BasicNameValuePair("name", name));
			params.add(new BasicNameValuePair("price", price));
			params.add(new BasicNameValuePair("description", description));

			// getting JSON Object
			// Note that create product url accepts POST method
			JSONObject json = jsonParser.makeHttpRequest(url_create_product,
					"POST", params);

			// check log cat fro response
			Log.d("Create Response", json.toString());

			// check for success tag
			try {
				int success = json.getInt(TAG_SUCCESS);

				if (success == 1) {
					// successfully created product
					Intent i = new Intent(getApplicationContext(), AllProductsActivity.class);
					startActivity(i);

					// closing this screen
					finish();
				} else {
					// failed to create product
				}
			} catch (JSONException e) {
				e.printStackTrace();
			}

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog once done
			pDialog.dismiss();
		}

	}
}

Reading, Updating and Deleting a Single Product

9. If you notice the AllProductsActivity.java, In listview i am launching EditProductAcivity.java once a single list item is selected. So create xml file called edit_product.xml and create a form which is same as create_product.xml.

edit_product.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- Name Label -->
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Product Name"
        android:paddingLeft="10dip"
        android:paddingRight="10dip"
        android:paddingTop="10dip"
        android:textSize="17dip"/>

    <!-- Input Name -->
	<EditText android:id="@+id/inputName"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:layout_margin="5dip"
	    android:layout_marginBottom="15dip"
	    android:singleLine="true"/>

	<!-- Price Label -->
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Price"
        android:paddingLeft="10dip"
        android:paddingRight="10dip"
        android:paddingTop="10dip"
        android:textSize="17dip"/>

    <!-- Input Price -->
	<EditText android:id="@+id/inputPrice"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:layout_margin="5dip"
	    android:layout_marginBottom="15dip"
	    android:singleLine="true"
	    android:inputType="numberDecimal"/>

	<!-- Description Label -->
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Description"
        android:paddingLeft="10dip"
        android:paddingRight="10dip"
        android:paddingTop="10dip"
        android:textSize="17dip"/>

    <!-- Input description -->
	<EditText android:id="@+id/inputDesc"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:layout_margin="5dip"
	    android:layout_marginBottom="15dip"
	    android:lines="4"
	    android:gravity="top"/>

	<LinearLayout android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:orientation="horizontal">
	    <!-- Button Create Product -->
	<Button android:id="@+id/btnSave"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="Save Changes"
	    android:layout_weight="1"/>

	<!-- Button Create Product -->
	<Button android:id="@+id/btnDelete"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:text="Delete"
	    android:layout_weight="1"/>
	</LinearLayout>

</LinearLayout>

10. Create a class file for edit_product.xml and name it as EditProductActivity.java and fill it with following code. In the following code

-> First product id (pid) is read from the intent which is sent from listview.
-> A request is made to get_product_details.php and after getting product details in json format, I parsed the json and displayed in EditText.
-> After displaying product data in the form if user clicks on Save Changes Button, another HTTP request is made to update_product.php to store updated product data.
-> If the user selected Delete Product Button, HTTP request is made to delete_product.php and product is deleted from mysql database, and listview is refreshed with new product list.

package com.example.androidhive;

import java.util.ArrayList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class EditProductActivity extends Activity {

	EditText txtName;
	EditText txtPrice;
	EditText txtDesc;
	EditText txtCreatedAt;
	Button btnSave;
	Button btnDelete;

	String pid;

	// Progress Dialog
	private ProgressDialog pDialog;

	// JSON parser class
	JSONParser jsonParser = new JSONParser();

	// single product url
	private static final String url_product_detials = "http://api.androidhive.info/android_connect/get_product_details.php";

	// url to update product
	private static final String url_update_product = "http://api.androidhive.info/android_connect/update_product.php";

	// url to delete product
	private static final String url_delete_product = "http://api.androidhive.info/android_connect/delete_product.php";

	// JSON Node names
	private static final String TAG_SUCCESS = "success";
	private static final String TAG_PRODUCT = "product";
	private static final String TAG_PID = "pid";
	private static final String TAG_NAME = "name";
	private static final String TAG_PRICE = "price";
	private static final String TAG_DESCRIPTION = "description";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.edit_product);

		// save button
		btnSave = (Button) findViewById(R.id.btnSave);
		btnDelete = (Button) findViewById(R.id.btnDelete);

		// getting product details from intent
		Intent i = getIntent();

		// getting product id (pid) from intent
		pid = i.getStringExtra(TAG_PID);

		// Getting complete product details in background thread
		new GetProductDetails().execute();

		// save button click event
		btnSave.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// starting background task to update product
				new SaveProductDetails().execute();
			}
		});

		// Delete button click event
		btnDelete.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// deleting product in background thread
				new DeleteProduct().execute();
			}
		});

	}

	/**
	 * Background Async Task to Get complete product details
	 * */
	class GetProductDetails extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(EditProductActivity.this);
			pDialog.setMessage("Loading product details. Please wait...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(true);
			pDialog.show();
		}

		/**
		 * Getting product details in background thread
		 * */
		protected String doInBackground(String... params) {

			// updating UI from Background Thread
			runOnUiThread(new Runnable() {
				public void run() {
					// Check for success tag
					int success;
					try {
						// Building Parameters
						List<NameValuePair> params = new ArrayList<NameValuePair>();
						params.add(new BasicNameValuePair("pid", pid));

						// getting product details by making HTTP request
						// Note that product details url will use GET request
						JSONObject json = jsonParser.makeHttpRequest(
								url_product_detials, "GET", params);

						// check your log for json response
						Log.d("Single Product Details", json.toString());

						// json success tag
						success = json.getInt(TAG_SUCCESS);
						if (success == 1) {
							// successfully received product details
							JSONArray productObj = json
									.getJSONArray(TAG_PRODUCT); // JSON Array

							// get first product object from JSON Array
							JSONObject product = productObj.getJSONObject(0);

							// product with this pid found
							// Edit Text
							txtName = (EditText) findViewById(R.id.inputName);
							txtPrice = (EditText) findViewById(R.id.inputPrice);
							txtDesc = (EditText) findViewById(R.id.inputDesc);

							// display product data in EditText
							txtName.setText(product.getString(TAG_NAME));
							txtPrice.setText(product.getString(TAG_PRICE));
							txtDesc.setText(product.getString(TAG_DESCRIPTION));

						}else{
							// product with pid not found
						}
					} catch (JSONException e) {
						e.printStackTrace();
					}
				}
			});

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog once got all details
			pDialog.dismiss();
		}
	}

	/**
	 * Background Async Task to  Save product Details
	 * */
	class SaveProductDetails extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(EditProductActivity.this);
			pDialog.setMessage("Saving product ...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(true);
			pDialog.show();
		}

		/**
		 * Saving product
		 * */
		protected String doInBackground(String... args) {

			// getting updated data from EditTexts
			String name = txtName.getText().toString();
			String price = txtPrice.getText().toString();
			String description = txtDesc.getText().toString();

			// Building Parameters
			List<NameValuePair> params = new ArrayList<NameValuePair>();
			params.add(new BasicNameValuePair(TAG_PID, pid));
			params.add(new BasicNameValuePair(TAG_NAME, name));
			params.add(new BasicNameValuePair(TAG_PRICE, price));
			params.add(new BasicNameValuePair(TAG_DESCRIPTION, description));

			// sending modified data through http request
			// Notice that update product url accepts POST method
			JSONObject json = jsonParser.makeHttpRequest(url_update_product,
					"POST", params);

			// check json success tag
			try {
				int success = json.getInt(TAG_SUCCESS);

				if (success == 1) {
					// successfully updated
					Intent i = getIntent();
					// send result code 100 to notify about product update
					setResult(100, i);
					finish();
				} else {
					// failed to update product
				}
			} catch (JSONException e) {
				e.printStackTrace();
			}

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog once product uupdated
			pDialog.dismiss();
		}
	}

	/*****************************************************************
	 * Background Async Task to Delete Product
	 * */
	class DeleteProduct extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(EditProductActivity.this);
			pDialog.setMessage("Deleting Product...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(true);
			pDialog.show();
		}

		/**
		 * Deleting product
		 * */
		protected String doInBackground(String... args) {

			// Check for success tag
			int success;
			try {
				// Building Parameters
				List<NameValuePair> params = new ArrayList<NameValuePair>();
				params.add(new BasicNameValuePair("pid", pid));

				// getting product details by making HTTP request
				JSONObject json = jsonParser.makeHttpRequest(
						url_delete_product, "POST", params);

				// check your log for json response
				Log.d("Delete Product", json.toString());

				// json success tag
				success = json.getInt(TAG_SUCCESS);
				if (success == 1) {
					// product successfully deleted
					// notify previous activity by sending code 100
					Intent i = getIntent();
					// send result code 100 to notify about product deletion
					setResult(100, i);
					finish();
				}
			} catch (JSONException e) {
				e.printStackTrace();
			}

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog once product deleted
			pDialog.dismiss();

		}

	}
}
android edit product
android delete product

JSON Parser Class

I used a JSON Parser class to get JSON from URL. This class supports two http request methods GET and POST to get json from url.

package com.example.androidhive;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

public class JSONParser {

	static InputStream is = null;
	static JSONObject jObj = null;
	static String json = "";

	// constructor
	public JSONParser() {

	}

	// function get json from url
	// by making HTTP POST or GET mehtod
	public JSONObject makeHttpRequest(String url, String method,
			List<NameValuePair> params) {

		// Making HTTP request
		try {

			// check for request method
			if(method == "POST"){
				// request method is POST
				// defaultHttpClient
				DefaultHttpClient httpClient = new DefaultHttpClient();
				HttpPost httpPost = new HttpPost(url);
				httpPost.setEntity(new UrlEncodedFormEntity(params));

				HttpResponse httpResponse = httpClient.execute(httpPost);
				HttpEntity httpEntity = httpResponse.getEntity();
				is = httpEntity.getContent();

			}else if(method == "GET"){
				// request method is GET
				DefaultHttpClient httpClient = new DefaultHttpClient();
				String paramString = URLEncodedUtils.format(params, "utf-8");
				url += "?" + paramString;
				HttpGet httpGet = new HttpGet(url);

				HttpResponse httpResponse = httpClient.execute(httpGet);
				HttpEntity httpEntity = httpResponse.getEntity();
				is = httpEntity.getContent();
			}			

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(
					is, "iso-8859-1"), 8);
			StringBuilder sb = new StringBuilder();
			String line = null;
			while ((line = reader.readLine()) != null) {
				sb.append(line + "\n");
			}
			is.close();
			json = sb.toString();
		} catch (Exception e) {
			Log.e("Buffer Error", "Error converting result " + e.toString());
		}

		// try parse the string to a JSON object
		try {
			jObj = new JSONObject(json);
		} catch (JSONException e) {
			Log.e("JSON Parser", "Error parsing data " + e.toString());
		}

		// return JSON String
		return jObj;

	}
}

Run your project and test the application. You might get lot of errors. Always use Log Cat to debug your application, and if you couldn’t solve your errors please do comment here.

This image is for thumbnail purpose
android connecting to mysql php

Android Combining Tab Layout and List View

$
0
0


In Android Tab Layout Tutorial i explained how to implement a tab view. This tutorial is about implementing list view inside a tab layout. Everything is same except tabs will have listview inside it. List data is displayed by fetching json by making http request. I took an example of simple mailbox which will contains two list views for inbox, outbox messages and a profile tab.

Download Code

In this example i am displaying list view in each tab where the list data is fetched from a JSON.


Get Inbox JSON here

{
    "messages": [
        {
                "id": "1",
                "from": "Android Hive",
                "email": "androidhive@gmail.com",
                "subject": "New Subscriber..",
                "message" : "Tutorial about combing Android Tabview and ListView",
                "date": "May 9"
        },
		{
                "id": "2",
                "from": "Pay Pal",
                "email": "paypal@paypal.com",
                "subject": "Payment Notification",
                "message" : "Tutorial about combing Android Tabview and ListView",
                "date": "May 7"
        },
        .
        .

    ]
}

Get Outbox JSON here.

{
    "messages": [
        {
                "id": "1",
                "to": "dine@gmail.com",
                "subject": "Movie Tickets Confirmation..",
                "message" : "Tutorial about combing Android Tabview and ListView",
                "date": "May 22"
        },
		{
                "id": "2",
                "to": "william@gmail.com",
                "subject": "Project estimataion details",
                "message" : "Tutorial about combing Android Tabview and ListView",
                "date": "May 21"
        },
        .
        .
        .
    ]
}

And get Profile JSON here

{
    "profile": {
        "id": "1",
        "name": "Ravi Tamada",
        "email": "ravi8x@gmail.com",
        "mobile": "+91 0000000000",
        "address": "xxx - xxx -xxxxxx"
    }
}


Create new Project

So let’s start with creation of new project in Eclipse IDE.

1. Create a new project File -> New -> Android Project and fill out required details.
2. Open your Main Activity and extend the class from TabActivity.

public class AndroidTabAndListView extends TabActivity {

3. Now open your main.xml under res -> layout folder and type the following code to create a tabview.

<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"/>
    </LinearLayout>
</TabHost>

4. Create three activities for 3 tabs. I am creating 3 activities as InboxActivity.java, OutboxActvity.java and ProfileActivity.java and extend the classes from ListActivity.

package com.example.androidhive;

import android.app.ListActivity;
import android.os.Bundle;

public class InboxActivity extends ListActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.inbox_list);
    }
}
package com.example.androidhive;

import android.app.ListActivity;
import android.os.Bundle;

public class OutboxActivity extends ListActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.outbox_list);
    }
}

Note that ProfileActivity.java in not ListActivity.

package com.example.androidhive;

import android.app.Activity;
import android.os.Bundle;

public class ProfileActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.profile);
    }
}

5. Now create xml layouts for the list views. Here we have two list views and a normal layout. So create totally 5 xml layouts. inbox_list.xml, outbox_list.xml, profile.xml, inbox_list_item.xml (inbox listview single row item), outbox_list_item.xml (outbox listview single row item)

inbox_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

inbox_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <!-- From Label -->
    <TextView
        android:id="@+id/from"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingTop="8dip"
        android:paddingLeft="8dip"
        android:paddingBottom="4dip"
        android:textSize="20dip"
        android:textStyle="bold" />
    
    <!-- Mail Subject -->
    <TextView android:id="@+id/subject"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:paddingLeft="8dip"
        android:paddingBottom="6dip"
        android:textSize="15dip"
        android:layout_below="@id/from"/>
    
    <!-- Mail date -->
    <TextView android:id="@+id/date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:padding="8dip"/>/
 
</RelativeLayout>

outbox_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

outbox_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <!-- Subject Label -->
    <TextView
        android:id="@+id/subject"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingTop="8dip"
        android:paddingLeft="8dip"
        android:paddingBottom="4dip"
        android:textSize="18dip"
        android:textStyle="bold" />
    
    <!-- To email -->
    <TextView android:id="@+id/to"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:paddingLeft="8dip"
        android:paddingBottom="6dip"
        android:textSize="15dip"
        android:layout_below="@id/subject"/>
    
    <!-- Mail date -->
    <TextView android:id="@+id/date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:padding="8dip"/>
 
</RelativeLayout>

profile.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    
    <!-- Name -->
    <TextView android:id="@+id/name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="28dip"
        android:paddingLeft="15dip"
        android:layout_marginTop="15dip"/>
    
    <TextView android:id="@+id/email"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="20dip"
        android:paddingLeft="15dip"/>
    
    <TextView android:id="@+id/mobile"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="21dip"
        android:paddingLeft="15dip"/>
    
    <TextView android:id="@+id/address"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="20dip"
        android:paddingLeft="15dip"/>
 
</LinearLayout>

6. Each and every tab needs an icon so design icons for each tab. We need three dimensions of each icon. Design each icon in 48 x 48 px, 32 x 32 px and 24 x 24 px and place them in drawable-hdpi, drawable-mdpi and drawable-ldpi respectively. See following diagram for your guidance

android tab layout icon sizes

7. Android icon states will be define in xml files with default and hover state configurations. For three icons we need the icon state configuration files. So create three 3 xml files under drawable-hdpi directory. Type the following code for icon states.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- When selected, use grey -->
    <item android:drawable="@drawable/inbox_gray"
          android:state_selected="true" />
    <!-- When not selected, use white-->
    <item android:drawable="@drawable/inbox_white" />
</selector>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- When selected, use grey -->
    <item android:drawable="@drawable/outbox_gray"
          android:state_selected="true" />
    <!-- When not selected, use white-->
    <item android:drawable="@drawable/outbox_white" />
</selector>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- When selected, use grey -->
    <item android:drawable="@drawable/profile_gray"
          android:state_selected="true" />
    <!-- When not selected, use white-->
    <item android:drawable="@drawable/profile_white" />
</selector>

8. Now open AndroidTabAndListView.java which is Main Activity and type the following code. In the following code we are creating three TabSepcs and adding them to TabHost.

package com.example.androidhive;

import android.app.TabActivity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;

public class AndroidTabAndListView extends TabActivity {
	// TabSpec Names
	private static final String INBOX_SPEC = "Inbox";
	private static final String OUTBOX_SPEC = "Outbox";
	private static final String PROFILE_SPEC = "Profile";
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        TabHost tabHost = getTabHost();
        
        // Inbox Tab
        TabSpec inboxSpec = tabHost.newTabSpec(INBOX_SPEC);
        // Tab Icon
        inboxSpec.setIndicator(INBOX_SPEC, getResources().getDrawable(R.drawable.icon_inbox));
        Intent inboxIntent = new Intent(this, InboxActivity.class);
        // Tab Content
        inboxSpec.setContent(inboxIntent);
        
        // Outbox Tab
        TabSpec outboxSpec = tabHost.newTabSpec(OUTBOX_SPEC);
        outboxSpec.setIndicator(OUTBOX_SPEC, getResources().getDrawable(R.drawable.icon_outbox));
        Intent outboxIntent = new Intent(this, OutboxActivity.class);
        outboxSpec.setContent(outboxIntent);
        
        // Profile Tab
        TabSpec profileSpec = tabHost.newTabSpec(PROFILE_SPEC);
        profileSpec.setIndicator(PROFILE_SPEC, getResources().getDrawable(R.drawable.icon_profile));
        Intent profileIntent = new Intent(this, ProfileActivity.class);
        profileSpec.setContent(profileIntent);
        
        // Adding all TabSpec to TabHost
        tabHost.addTab(inboxSpec); // Adding Inbox tab
        tabHost.addTab(outboxSpec); // Adding Outbox tab
        tabHost.addTab(profileSpec); // Adding Profile tab
    }
}

9. Now open your project make sure that you an entry of new activity name in AndroidManifest.xml file. Open you AndroidManifest.xml file and modify the code as below. Also don’t forgot to add INTERNET Permissions as we are getting JSON by making http request.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidhive"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:configChanges="keyboardHidden|orientation"
            android:name=".AndroidTabAndListView"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Inbox Activity -->
        <activity android:name=".InboxActivity" />

        <!-- Outbox Activity -->
        <activity android:name=".OutboxActivity" />

        <!-- Profile Activity -->
        <activity android:name=".ProfileActivity" />
    </application>

    <!-- Internet Permissions -->
    <uses-permission android:name="android.permission.INTERNET" />

</manifest>

10. If you run your project now you will see tabs running without listviews. So all we need to do is implement listview in each tab activity. So open you individual tab activity classes and try to implement listview. If you are new to ListView i suggest you to go through Android ListView Tutorial once.

InboxActivity.java

package com.example.androidhive;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.http.NameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.ListActivity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.ListAdapter;
import android.widget.SimpleAdapter;

public class InboxActivity extends ListActivity {
	// Progress Dialog
	private ProgressDialog pDialog;

	// Creating JSON Parser object
	JSONParser jsonParser = new JSONParser();

	ArrayList<HashMap<String, String>> inboxList;

	// products JSONArray
	JSONArray inbox = null;

	// Inbox JSON url
	private static final String INBOX_URL = "http://api.androidhive.info/mail/inbox.json";
	
	// ALL JSON node names
	private static final String TAG_MESSAGES = "messages";
	private static final String TAG_ID = "id";
	private static final String TAG_FROM = "from";
	private static final String TAG_EMAIL = "email";
	private static final String TAG_SUBJECT = "subject";
	private static final String TAG_DATE = "date";
	

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.inbox_list);
		
		// Hashmap for ListView
        inboxList = new ArrayList<HashMap<String, String>>();
 
        // Loading INBOX in Background Thread
        new LoadInbox().execute();
	}

	/**
	 * Background Async Task to Load all INBOX messages by making HTTP Request
	 * */
	class LoadInbox extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(InboxActivity.this);
			pDialog.setMessage("Loading Inbox ...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		/**
		 * getting Inbox JSON
		 * */
		protected String doInBackground(String... args) {
			// Building Parameters
			List<NameValuePair> params = new ArrayList<NameValuePair>();
			
			// getting JSON string from URL
			JSONObject json = jsonParser.makeHttpRequest(INBOX_URL, "GET",
					params);

			// Check your log cat for JSON reponse
			Log.d("Inbox JSON: ", json.toString());

			try {
				inbox = json.getJSONArray(TAG_MESSAGES);
				// looping through All messages
				for (int i = 0; i < inbox.length(); i++) {
					JSONObject c = inbox.getJSONObject(i);

					// Storing each json item in variable
					String id = c.getString(TAG_ID);
					String from = c.getString(TAG_FROM);
					String subject = c.getString(TAG_SUBJECT);
					String date = c.getString(TAG_DATE);

					// creating new HashMap
					HashMap<String, String> map = new HashMap<String, String>();

					// adding each child node to HashMap key => value
					map.put(TAG_ID, id);
					map.put(TAG_FROM, from);
					map.put(TAG_SUBJECT, subject);
					map.put(TAG_DATE, date);

					// adding HashList to ArrayList
					inboxList.add(map);
				}

			} catch (JSONException e) {
				e.printStackTrace();
			}

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog after getting all products
			pDialog.dismiss();
			// updating UI from Background Thread
			runOnUiThread(new Runnable() {
				public void run() {
					/**
					 * Updating parsed JSON data into ListView
					 * */
					ListAdapter adapter = new SimpleAdapter(
							InboxActivity.this, inboxList,
							R.layout.inbox_list_item, new String[] { TAG_FROM, TAG_SUBJECT, TAG_DATE },
							new int[] { R.id.from, R.id.subject, R.id.date });
					// updating listview
					setListAdapter(adapter);
				}
			});

		}

	}
}

OutboxActivity.java

package com.example.androidhive;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.http.NameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.ListActivity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.ListAdapter;
import android.widget.SimpleAdapter;

public class OutboxActivity extends ListActivity {
	// Progress Dialog
	private ProgressDialog pDialog;

	// Creating JSON Parser object
	JSONParser jsonParser = new JSONParser();

	ArrayList<HashMap<String, String>> outboxList;

	// products JSONArray
	JSONArray outbox = null;

	// Outbox JSON url
	private static final String OUTBOX_URL = "http://api.androidhive.info/mail/outbox.json";
	
	// ALL JSON node names
	private static final String TAG_MESSAGES = "messages";
	private static final String TAG_ID = "id";
	private static final String TAG_TO = "to";
	private static final String TAG_SUBJECT = "subject";
	private static final String TAG_DATE = "date";
	

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.outbox_list);
		
		// Hashmap for ListView
        outboxList = new ArrayList<HashMap<String, String>>();
 
        // Loading OUTBOX in Background Thread
        new LoadOutbox().execute();
	}

	/**
	 * Background Async Task to Load all OUTBOX messages by making HTTP Request
	 * */
	class LoadOutbox extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(OutboxActivity.this);
			pDialog.setMessage("Loading Outbox ...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		/**
		 * getting Outbox JSON
		 * */
		protected String doInBackground(String... args) {
			// Building Parameters
			List<NameValuePair> params = new ArrayList<NameValuePair>();
			
			// getting JSON string from URL
			JSONObject json = jsonParser.makeHttpRequest(OUTBOX_URL, "GET",
					params);

			// Check your log cat for JSON reponse
			Log.d("Outbox JSON: ", json.toString());

			try {
				outbox = json.getJSONArray(TAG_MESSAGES);
				// looping through All messages
				for (int i = 0; i < outbox.length(); i++) {
					JSONObject c = outbox.getJSONObject(i);

					// Storing each json item in variable
					String id = c.getString(TAG_ID);
					String to = c.getString(TAG_TO);
					String subject = c.getString(TAG_SUBJECT);
					String date = c.getString(TAG_DATE);
					
					// subject taking only first 23 chars
					// to fit into screen
					if(subject.length() > 23){
						subject = subject.substring(0, 22) + "..";
					}

					// creating new HashMap
					HashMap<String, String> map = new HashMap<String, String>();

					// adding each child node to HashMap key => value
					map.put(TAG_ID, id);
					map.put(TAG_TO, to);
					map.put(TAG_SUBJECT, subject);
					map.put(TAG_DATE, date);

					// adding HashList to ArrayList
					outboxList.add(map);
				}

			} catch (JSONException e) {
				e.printStackTrace();
			}

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog after getting all products
			pDialog.dismiss();
			// updating UI from Background Thread
			runOnUiThread(new Runnable() {
				public void run() {
					/**
					 * Updating parsed JSON data into ListView
					 * */
					ListAdapter adapter = new SimpleAdapter(
							OutboxActivity.this, outboxList,
							R.layout.outbox_list_item, new String[] { TAG_SUBJECT, TAG_TO, TAG_DATE },
							new int[] { R.id.subject, R.id.to, R.id.date });
					// updating listview
					setListAdapter(adapter);
				}
			});

		}

	}
}

ProfileActivity.java

package com.example.androidhive;

import java.util.ArrayList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class ProfileActivity extends Activity {
	// All xml labels
	TextView txtName;
	TextView txtEmail;
	TextView txtMobile;
	TextView txtAddress;
	
	// Progress Dialog
	private ProgressDialog pDialog;

	// Creating JSON Parser object
	JSONParser jsonParser = new JSONParser();
	
	// Profile json object
	JSONObject profile;
	
	// Profile JSON url
	private static final String PROFILE_URL = "http://api.androidhive.info/mail/profile.json";
	
	// ALL JSON node names
	private static final String TAG_PROFILE = "profile";
	private static final String TAG_ID = "id";
	private static final String TAG_NAME = "name";
	private static final String TAG_EMAIL = "email";
	private static final String TAG_MOBILE = "mobile";
	private static final String TAG_ADDRESS = "address";
	

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.profile);
		
		txtName = (TextView) findViewById(R.id.name);
		txtEmail = (TextView) findViewById(R.id.email);
		txtMobile = (TextView) findViewById(R.id.mobile);
		txtAddress = (TextView) findViewById(R.id.address);
		
        // Loading Profile in Background Thread
        new LoadProfile().execute();
	}

	/**
	 * Background Async Task to Load profile by making HTTP Request
	 * */
	class LoadProfile extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(ProfileActivity.this);
			pDialog.setMessage("Loading profile ...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		/**
		 * getting Profile JSON
		 * */
		protected String doInBackground(String... args) {
			// Building Parameters
			List<NameValuePair> params = new ArrayList<NameValuePair>();
			
			// getting JSON string from URL
			JSONObject json = jsonParser.makeHttpRequest(PROFILE_URL, "GET",
					params);

			// Check your log cat for JSON reponse
			Log.d("Profile JSON: ", json.toString());

			try {
				// profile json object
				profile = json.getJSONObject(TAG_PROFILE);
			} catch (JSONException e) {
				e.printStackTrace();
			}

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog after getting all products
			pDialog.dismiss();
			// updating UI from Background Thread
			runOnUiThread(new Runnable() {
				public void run() {
					/**
					 * Updating parsed JSON data into ListView
					 * */
					// Storing each json item in variable
					try {
						String id = profile.getString(TAG_ID);
						String name = profile.getString(TAG_NAME);
						String email = profile.getString(TAG_EMAIL);
						String mobile = profile.getString(TAG_MOBILE);
						String address = profile.getString(TAG_ADDRESS);
						
						// displaying all data in textview
						txtName.setText(name);
						txtEmail.setText("Email: " + email);
						txtMobile.setText("Mobile: " + mobile);
						txtAddress.setText("Add: " + address);
						
					} catch (JSONException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
				}
			});

		}

	}
}

11. Finally create a class called JSONParser.java and paste the following code as we need a parser class to parse the json.

package com.example.androidhive;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;
 
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONException;
import org.json.JSONObject;
 
import android.util.Log;
 
public class JSONParser {
 
    static InputStream is = null;
    static JSONObject jObj = null;
    static String json = "";
 
    // constructor
    public JSONParser() {
 
    }
 
    // function get json from url
    // by making HTTP POST or GET mehtod
    public JSONObject makeHttpRequest(String url, String method,
            List<NameValuePair> params) {
 
        // Making HTTP request
        try {
 
            // check for request method
            if(method == "POST"){
                // request method is POST
                // defaultHttpClient
                DefaultHttpClient httpClient = new DefaultHttpClient();
                HttpPost httpPost = new HttpPost(url);
                httpPost.setEntity(new UrlEncodedFormEntity(params));
 
                HttpResponse httpResponse = httpClient.execute(httpPost);
                HttpEntity httpEntity = httpResponse.getEntity();
                is = httpEntity.getContent();
 
            }else if(method == "GET"){
                // request method is GET
                DefaultHttpClient httpClient = new DefaultHttpClient();
                String paramString = URLEncodedUtils.format(params, "utf-8");
                url += "?" + paramString;
                HttpGet httpGet = new HttpGet(url);
 
                HttpResponse httpResponse = httpClient.execute(httpGet);
                HttpEntity httpEntity = httpResponse.getEntity();
                is = httpEntity.getContent();
            }            
 
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    is, "iso-8859-1"), 8);
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
            is.close();
            json = sb.toString();
        } catch (Exception e) {
            Log.e("Buffer Error", "Error converting result " + e.toString());
        }
 
        // try parse the string to a JSON object
        try {
            jObj = new JSONObject(json);
        } catch (JSONException e) {
            Log.e("JSON Parser", "Error parsing data " + e.toString());
        }
 
        // return JSON String
        return jObj;
 
    }
}

12. Now Run & Test your project.

android tab layout and list view
android tab layout and list view
android tab layout and list view
This image is for thumbnail purpose
android combining tab layout and list view

Android Working with Google Places and Maps Tutorial

$
0
0


This tutorial is very helpful if you are building any application which deals with user locations. In this article i explained how to integrate Google Places and Maps in your android application.

Download Code


About Google Places

Google Places API is a service provided by Google which returns information about your nearest places by considering the latitude, longitude and radius of area. You can add or remove a place from their places service too. Everything is done just by sending an HTTP request with required parameters. Check their official documentation for more information.

Prerequisite

In this tutorial i used lot of my previous tutorials. If you are new to any of the following concepts, i recommend you learn that particular topic just to make this tutorial easy.
> Android Working with Google Maps
> Android GPS, Location Manager Tutorial
> Android Detect Internet Connection Status

Obtaining Google API Key

In order to make requests to Google Places API you need to provide your API key along with the other parameters. You can get your API key by going to Google Places APIs console. The same key can be used for all other google services. Check the following video to know how to get a api key.


New Project / Downloading required libraries

Open your Eclipse IDE and create a new android project and fill all the required details. Also make sure that you have the updated ADT (Android Development Tool) and Eclipse to latest version.

1. Create a new project File ? New ? Android Application Project.

For this project i downloaded lot of external jar files and added to my project. These libraries are used to parse the Google API response.

Go to http://code.google.com/p/google-api-java-client/wiki/Setup and download google-api-client-android2. Extract the files and place them (Not every file) inside libs folder in your project. The following are required files you need to paste in your project.

1. google-api-client-1.10.3-beta.jar
2. google-api-client-android2-1.10.3-beta.jar (only for SDK >= 2.1)
3. google-oauth-client-1.10.3-beta.jar
4. google-http-client-1.10.3-beta.jar
5. google-http-client-android2-1.10.3-beta.jar (only for SDK >= 2.1)
6. google-http-client-android3-1.10.3-beta.jar (only for SDK >= 3.0)
7. gson-2.1.jar
8. guava-11.0.1.jar
9. jackson-core-asl-1.9.4.jar
10. jsr305-1.3.9.jar
11. protobuf-java-2.2.0.jar

3. As this application needs internet connection, we need to detect whether user has working internet connection or not. For this i am creating a new class called ConnectionDetector.java and pasted the following code.

Refer Android Detect Internet Connection Status to know more about using this class.

package com.androidhive.googleplacesandmaps;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
 
public class ConnectionDetector {
 
    private Context _context;
 
    public ConnectionDetector(Context context){
        this._context = context;
    }
 
    /**
     * Checking for all possible internet providers
     * **/
    public boolean isConnectingToInternet(){
        ConnectivityManager connectivity = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
          if (connectivity != null)
          {
              NetworkInfo[] info = connectivity.getAllNetworkInfo();
              if (info != null)
                  for (int i = 0; i < info.length; i++)
                      if (info[i].getState() == NetworkInfo.State.CONNECTED)
                      {
                          return true;
                      }
 
          }
          return false;
    }
}

4. Just to show messages in Alert Dialog i am creating a reusable alert class so that you don’t have to write the alert code in all the activities. Create a new class and name it as AlertDialogManager.java

package com.androidhive.googleplacesandmaps;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;

public class AlertDialogManager {
	/**
	 * Function to display simple Alert Dialog
	 * @param context - application context
	 * @param title - alert dialog title
	 * @param message - alert message
	 * @param status - success/failure (used to set icon)
	 * 				 - pass null if you don't want icon
	 * */
	public void showAlertDialog(Context context, String title, String message,
			Boolean status) {
		AlertDialog alertDialog = new AlertDialog.Builder(context).create();

		// Setting Dialog Title
		alertDialog.setTitle(title);

		// Setting Dialog Message
		alertDialog.setMessage(message);

		if(status != null)
			// Setting alert dialog icon
			alertDialog.setIcon((status) ? R.drawable.success : R.drawable.fail);

		// Setting OK Button
		alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
			}
		});

		// Showing Alert Message
		alertDialog.show();
	}
}

5. In this tutorial as everything is based on user’s current location, we need to get user’s current location using GPS. Create a new class called GPSTracker.java and paste the following code. To know more about this class usage follow this Android GPS, Location Manager Tutorial

package com.androidhive.googleplacesandmaps;

import android.app.AlertDialog;
import android.app.Service;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.Settings;
import android.util.Log;

public class GPSTracker extends Service implements LocationListener {

	private final Context mContext;

	// flag for GPS status
	boolean isGPSEnabled = false;

	// flag for network status
	boolean isNetworkEnabled = false;

	// flag for GPS status
	boolean canGetLocation = false;

	Location location = null; // location
	double latitude; // latitude
	double longitude; // longitude

	// The minimum distance to change Updates in meters
	private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters

	// The minimum time between updates in milliseconds
	private static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1; // 1 minute

	// Declaring a Location Manager
	protected LocationManager locationManager;

	public GPSTracker(Context context) {
		this.mContext = context;
		getLocation();
	}

	public Location getLocation() {
		try {
			locationManager = (LocationManager) mContext
					.getSystemService(LOCATION_SERVICE);

			// getting GPS status
			isGPSEnabled = locationManager
					.isProviderEnabled(LocationManager.GPS_PROVIDER);

			// getting network status
			isNetworkEnabled = locationManager
					.isProviderEnabled(LocationManager.NETWORK_PROVIDER);

			if (!isGPSEnabled && !isNetworkEnabled) {
				// no network provider is enabled
			} else {
				this.canGetLocation = true;
				if (isNetworkEnabled) {
					locationManager.requestLocationUpdates(
							LocationManager.NETWORK_PROVIDER,
							MIN_TIME_BW_UPDATES,
							MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
					Log.d("Network", "Network Enabled");
					if (locationManager != null) {
						location = locationManager
								.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
						if (location != null) {
							latitude = location.getLatitude();
							longitude = location.getLongitude();
						}
					}
				}
				// if GPS Enabled get lat/long using GPS Services
				if (isGPSEnabled) {
					if (location == null) {
						locationManager.requestLocationUpdates(
								LocationManager.GPS_PROVIDER,
								MIN_TIME_BW_UPDATES,
								MIN_DISTANCE_CHANGE_FOR_UPDATES, this);
						Log.d("GPS", "GPS Enabled");
						if (locationManager != null) {
							location = locationManager
									.getLastKnownLocation(LocationManager.GPS_PROVIDER);
							if (location != null) {
								latitude = location.getLatitude();
								longitude = location.getLongitude();
							}
						}
					}
				}
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		return location;
	}

	/**
	 * Stop using GPS listener Calling this function will stop using GPS in your
	 * app
	 * */
	public void stopUsingGPS() {
		if (locationManager != null) {
			locationManager.removeUpdates(GPSTracker.this);
		}
	}

	/**
	 * Function to get latitude
	 * */
	public double getLatitude() {
		if (location != null) {
			latitude = location.getLatitude();
		}

		// return latitude
		return latitude;
	}

	/**
	 * Function to get longitude
	 * */
	public double getLongitude() {
		if (location != null) {
			longitude = location.getLongitude();
		}

		// return longitude
		return longitude;
	}

	/**
	 * Function to check GPS/wifi enabled
	 * 
	 * @return boolean
	 * */
	public boolean canGetLocation() {
		return this.canGetLocation;
	}

	/**
	 * Function to show settings alert dialog On pressing Settings button will
	 * lauch Settings Options
	 * */
	public void showSettingsAlert() {
		AlertDialog.Builder alertDialog = new AlertDialog.Builder(mContext);

		// Setting Dialog Title
		alertDialog.setTitle("GPS is settings");

		// Setting Dialog Message
		alertDialog
				.setMessage("GPS is not enabled. Do you want to go to settings menu?");

		// On pressing Settings button
		alertDialog.setPositiveButton("Settings",
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int which) {
						Intent intent = new Intent(
								Settings.ACTION_LOCATION_SOURCE_SETTINGS);
						mContext.startActivity(intent);
					}
				});

		// on pressing cancel button
		alertDialog.setNegativeButton("Cancel",
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int which) {
						dialog.cancel();
					}
				});

		// Showing Alert Message
		alertDialog.show();
	}

	@Override
	public void onLocationChanged(Location location) {
	}

	@Override
	public void onProviderDisabled(String provider) {
	}

	@Override
	public void onProviderEnabled(String provider) {
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
	}

	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}
}

6. While parsing google places api response, it is better to make every place as an object to make it reusable component. So create the following classes.

Place.java – Single place as object

package com.androidhive.googleplacesandmaps;

import java.io.Serializable;

import com.google.api.client.util.Key;

/** Implement this class from "Serializable"
* So that you can pass this class Object to another using Intents
* Otherwise you can't pass to another actitivy
* */
public class Place implements Serializable {

	@Key
	public String id;
	
	@Key
	public String name;
	
	@Key
	public String reference;
	
	@Key
	public String icon;
	
	@Key
	public String vicinity;
	
	@Key
	public Geometry geometry;
	
	@Key
	public String formatted_address;
	
	@Key
	public String formatted_phone_number;

	@Override
	public String toString() {
		return name + " - " + id + " - " + reference;
	}
	
	public static class Geometry implements Serializable
	{
		@Key
		public Location location;
	}
	
	public static class Location implements Serializable
	{
		@Key
		public double lat;
		
		@Key
		public double lng;
	}
	
}

PlacesList.java – List of places

package com.androidhive.googleplacesandmaps;

import java.io.Serializable;
import java.util.List;

import com.google.api.client.util.Key;

/** Implement this class from "Serializable"
* So that you can pass this class Object to another using Intents
* Otherwise you can't pass to another actitivy
* */
public class PlacesList implements Serializable {

	@Key
	public String status;

	@Key
	public List<Place> results;

}

PlaceDetails.java – Single place full details as object

package com.androidhive.googleplacesandmaps;

import java.io.Serializable;

import com.google.api.client.util.Key;

/** Implement this class from "Serializable"
* So that you can pass this class Object to another using Intents
* Otherwise you can't pass to another actitivy
* */
public class PlaceDetails implements Serializable {

	@Key
	public String status;
	
	@Key
	public Place result;

	@Override
	public String toString() {
		if (result!=null) {
			return result.toString();
		}
		return super.toString();
	}
}


Getting Google Places

7. The actual part of getting google places is sarted now. Create a new class called GooglePlaces.java and write the following code. This class has function which are used to make request to Google Places API. While searching places your can search for particular type of places like cafe, restaurants etc,. Check list of supported types of places.

In the following class we have two functions

// This function is used to search your nearest places
public PlacesList search(double latitude, double longitude, double radius, 
         String types)...
// This function is used to get full details of a particular place
public PlaceDetails getPlaceDetails(String reference)...

Final code

package com.androidhive.googleplacesandmaps;

import org.apache.http.client.HttpResponseException;

import android.util.Log;

import com.google.api.client.googleapis.GoogleHeaders;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.http.json.JsonHttpParser;
import com.google.api.client.json.jackson.JacksonFactory;

@SuppressWarnings("deprecation")
public class GooglePlaces {

	/** Global instance of the HTTP transport. */
	private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();

	// Google API Key
	private static final String API_KEY = "AIzaSyCRLa4LQZWNQBcjCYcIVYA45i9i8zfClqc";

	// Google Places serach url's
	private static final String PLACES_SEARCH_URL = "https://maps.googleapis.com/maps/api/place/search/json?";
	private static final String PLACES_TEXT_SEARCH_URL = "https://maps.googleapis.com/maps/api/place/search/json?";
	private static final String PLACES_DETAILS_URL = "https://maps.googleapis.com/maps/api/place/details/json?";

	private double _latitude;
	private double _longitude;
	private double _radius;

	/**
	 * Searching places
	 * @param latitude - latitude of place
	 * @params longitude - longitude of place
	 * @param radius - radius of searchable area
	 * @param types - type of place to search
	 * @return list of places
	 * */
	public PlacesList search(double latitude, double longitude, double radius, String types)
			throws Exception {

		this._latitude = latitude;
		this._longitude = longitude;
		this._radius = radius;

		try {

			HttpRequestFactory httpRequestFactory = createRequestFactory(HTTP_TRANSPORT);
			HttpRequest request = httpRequestFactory
					.buildGetRequest(new GenericUrl(PLACES_SEARCH_URL));
			request.getUrl().put("key", API_KEY);
			request.getUrl().put("location", _latitude + "," + _longitude);
			request.getUrl().put("radius", _radius); // in meters
			request.getUrl().put("sensor", "false");
			if(types != null)
				request.getUrl().put("types", types);

			PlacesList list = request.execute().parseAs(PlacesList.class);
			// Check log cat for places response status
			Log.d("Places Status", "" + list.status);
			return list;

		} catch (HttpResponseException e) {
			Log.e("Error:", e.getMessage());
			return null;
		}

	}

	/**
	 * Searching single place full details
	 * @param refrence - reference id of place
	 * 				   - which you will get in search api request
	 * */
	public PlaceDetails getPlaceDetails(String reference) throws Exception {
		try {

			HttpRequestFactory httpRequestFactory = createRequestFactory(HTTP_TRANSPORT);
			HttpRequest request = httpRequestFactory
					.buildGetRequest(new GenericUrl(PLACES_DETAILS_URL));
			request.getUrl().put("key", API_KEY);
			request.getUrl().put("reference", reference);
			request.getUrl().put("sensor", "false");

			PlaceDetails place = request.execute().parseAs(PlaceDetails.class);
			
			return place;

		} catch (HttpResponseException e) {
			Log.e("Error in Perform Details", e.getMessage());
			throw e;
		}
	}

	/**
	 * Creating http request Factory
	 * */
	public static HttpRequestFactory createRequestFactory(
			final HttpTransport transport) {
		return transport.createRequestFactory(new HttpRequestInitializer() {
			public void initialize(HttpRequest request) {
				GoogleHeaders headers = new GoogleHeaders();
				headers.setApplicationName("AndroidHive-Places-Test");
				request.setHeaders(headers);
				JsonHttpParser parser = new JsonHttpParser(new JacksonFactory());
				request.addParser(parser);
			}
		});
	}

}


Showing Places in ListView

8. Create two xml files under layout folder required for creating a listview. In the following i am creating a listview and a button above the listview. Another file is for list item. Name the two files as activity_main.xml and list_item.xml

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"> 
    
    <!-- Show on Map button -->
    <Button android:id="@+id/btn_show_map"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Show Places on Map"
        android:layout_alignParentTop="true"
        android:layout_marginTop="10dip"/>
    
    <!--  List view -->
    <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_show_map"/>
    
    
 
</RelativeLayout>

list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <TextView android:id="@+id/reference"
        android:visibility="gone"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
    
    <TextView android:id="@+id/name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:padding="10dip"
        android:textStyle="bold"
        android:textSize="16dip"/>
</LinearLayout>

9. Open your main acitivity (my main activity name is MainActivity.java) and add following required variables.

public class MainActivity extends Activity {

	// flag for Internet connection status
	Boolean isInternetPresent = false;

	// Connection detector class
	ConnectionDetector cd;
	
	// Alert Dialog Manager
	AlertDialogManager alert = new AlertDialogManager();

	// Google Places
	GooglePlaces googlePlaces;

	// Places List
	PlacesList nearPlaces;

	// GPS Location
	GPSTracker gps;

	// Button
	Button btnShowOnMap;

	// Progress dialog
	ProgressDialog pDialog;
	
	// Places Listview
	ListView lv;
	
	// ListItems data
	ArrayList<HashMap<String, String>> placesListItems = new ArrayList<HashMap<String,String>>();
	
	
	// KEY Strings
	public static String KEY_REFERENCE = "reference"; // id of the place
	public static String KEY_NAME = "name"; // name of the place
	public static String KEY_VICINITY = "vicinity"; // Place area name

	@Override
	public void onCreate(Bundle savedInstanceState) {

In the following code
> First i am checking if user has internet connection or not. If not an alert is shown to user asking to connect to internet

> Second i am checking if user’s current location can be retrieved by GPS Location Manger. If not an alert is shown asking user to turn on GPS or Wifi.

> Third if user has both internet and GPS turned on an Async task is called to get Google places using new LoadPlaces().execute()

> In LoadPlaces() method once getting places is done all the data is attached in a ListView.

> Above the listview a button is placed which launches another activity to show all places on map.

package com.androidhive.googleplacesandmaps;

import java.util.ArrayList;
import java.util.HashMap;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Html;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;


public class MainActivity extends Activity {

	// flag for Internet connection status
	Boolean isInternetPresent = false;

	// Connection detector class
	ConnectionDetector cd;
	
	// Alert Dialog Manager
	AlertDialogManager alert = new AlertDialogManager();

	// Google Places
	GooglePlaces googlePlaces;

	// Places List
	PlacesList nearPlaces;

	// GPS Location
	GPSTracker gps;

	// Button
	Button btnShowOnMap;

	// Progress dialog
	ProgressDialog pDialog;
	
	// Places Listview
	ListView lv;
	
	// ListItems data
	ArrayList<HashMap<String, String>> placesListItems = new ArrayList<HashMap<String,String>>();
	
	
	// KEY Strings
	public static String KEY_REFERENCE = "reference"; // id of the place
	public static String KEY_NAME = "name"; // name of the place
	public static String KEY_VICINITY = "vicinity"; // Place area name

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		cd = new ConnectionDetector(getApplicationContext());

		// Check if Internet present
		isInternetPresent = cd.isConnectingToInternet();
		if (!isInternetPresent) {
			// Internet Connection is not present
			alert.showAlertDialog(MainActivity.this, "Internet Connection Error",
					"Please connect to working Internet connection", false);
			// stop executing code by return
			return;
		}

		// creating GPS Class object
		gps = new GPSTracker(this);

		// check if GPS location can get
		if (gps.canGetLocation()) {
			Log.d("Your Location", "latitude:" + gps.getLatitude() + ", longitude: " + gps.getLongitude());
		} else {
			// Can't get user's current location
			alert.showAlertDialog(MainActivity.this, "GPS Status",
					"Couldn't get location information. Please enable GPS",
					false);
			// stop executing code by return
			return;
		}

		// Getting listview
		lv = (ListView) findViewById(R.id.list);
		
		// button show on map
		btnShowOnMap = (Button) findViewById(R.id.btn_show_map);

		// calling background Async task to load Google Places
		// After getting places from Google all the data is shown in listview
		new LoadPlaces().execute();

		/** Button click event for shown on map */
		btnShowOnMap.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View arg0) {
				Intent i = new Intent(getApplicationContext(),
						PlacesMapActivity.class);
				// Sending user current geo location
				i.putExtra("user_latitude", Double.toString(gps.getLatitude()));
				i.putExtra("user_longitude", Double.toString(gps.getLongitude()));
				
				// passing near places to map activity
				i.putExtra("near_places", nearPlaces);
				// staring activity
				startActivity(i);
			}
		});
		
		
		/**
		 * ListItem click event
		 * On selecting a listitem SinglePlaceActivity is launched
		 * */
		lv.setOnItemClickListener(new OnItemClickListener() {
 
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
            	// getting values from selected ListItem
                String reference = ((TextView) view.findViewById(R.id.reference)).getText().toString();
                
                // Starting new intent
                Intent in = new Intent(getApplicationContext(),
                        SinglePlaceActivity.class);
                
                // Sending place refrence id to single place activity
                // place refrence id used to get "Place full details"
                in.putExtra(KEY_REFERENCE, reference);
                startActivity(in);
            }
        });
	}

	/**
	 * Background Async Task to Load Google places
	 * */
	class LoadPlaces extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(MainActivity.this);
			pDialog.setMessage(Html.fromHtml("<b>Search</b><br/>Loading Places..."));
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		/**
		 * getting Places JSON
		 * */
		protected String doInBackground(String... args) {
			// creating Places class object
			googlePlaces = new GooglePlaces();
			
			try {
				// Separeate your place types by PIPE symbol "|"
				// If you want all types places make it as null
				// Check list of types supported by google
				// 
				String types = "cafe|restaurant"; // Listing places only cafes, restaurants
				
				// Radius in meters - increase this value if you don't find any places
				double radius = 1000; // 1000 meters 
				
				// get nearest places
				nearPlaces = googlePlaces.search(gps.getLatitude(),
						gps.getLongitude(), radius, types);
				

			} catch (Exception e) {
				e.printStackTrace();
			}
			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * and show the data in UI
		 * Always use runOnUiThread(new Runnable()) to update UI from background
		 * thread, otherwise you will get error
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog after getting all products
			pDialog.dismiss();
			// updating UI from Background Thread
			runOnUiThread(new Runnable() {
				public void run() {
					/**
					 * Updating parsed Places into LISTVIEW
					 * */
					// Get json response status
					String status = nearPlaces.status;
					
					// Check for all possible status
					if(status.equals("OK")){
						// Successfully got places details
						if (nearPlaces.results != null) {
							// loop through each place
							for (Place p : nearPlaces.results) {
								HashMap<String, String> map = new HashMap<String, String>();
								
								// Place reference won't display in listview - it will be hidden
								// Place reference is used to get "place full details"
								map.put(KEY_REFERENCE, p.reference);
								
								// Place name
								map.put(KEY_NAME, p.name);
								
								
								// adding HashMap to ArrayList
								placesListItems.add(map);
							}
							// list adapter
							ListAdapter adapter = new SimpleAdapter(MainActivity.this, placesListItems,
					                R.layout.list_item,
					                new String[] { KEY_REFERENCE, KEY_NAME}, new int[] {
					                        R.id.reference, R.id.name });
							
							// Adding data into listview
							lv.setAdapter(adapter);
						}
					}
					else if(status.equals("ZERO_RESULTS")){
						// Zero results found
						alert.showAlertDialog(MainActivity.this, "Near Places",
								"Sorry no places found. Try to change the types of places",
								false);
					}
					else if(status.equals("UNKNOWN_ERROR"))
					{
						alert.showAlertDialog(MainActivity.this, "Places Error",
								"Sorry unknown error occured.",
								false);
					}
					else if(status.equals("OVER_QUERY_LIMIT"))
					{
						alert.showAlertDialog(MainActivity.this, "Places Error",
								"Sorry query limit to google places is reached",
								false);
					}
					else if(status.equals("REQUEST_DENIED"))
					{
						alert.showAlertDialog(MainActivity.this, "Places Error",
								"Sorry error occured. Request is denied",
								false);
					}
					else if(status.equals("INVALID_REQUEST"))
					{
						alert.showAlertDialog(MainActivity.this, "Places Error",
								"Sorry error occured. Invalid Request",
								false);
					}
					else
					{
						alert.showAlertDialog(MainActivity.this, "Places Error",
								"Sorry error occured.",
								false);
					}
				}
			});

		}

	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.activity_main, menu);
		return true;
	}

	

}
android detecting internet connection
Android google places api


Getting single place full details

11. If you see the api json response from google, each place has a unique reference id which is used to get place complete details.

In your project create a new Activity class and name it as SinglePlaceActivity.java and respected layout file and name it as single_place.xml

single_place.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" 
    android:padding="10dip">
    
    <!-- Name Label -->
    <TextView
        android:text="Name:"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dip"
        android:layout_marginBottom="5dip"
        android:textStyle="bold"/>
    
    <!--  Name Value -->
    <TextView
        android:id="@+id/name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
    
    <!-- Address Label -->
    <TextView
        android:text="Address:"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dip"
        android:layout_marginBottom="5dip"
        android:textStyle="bold"/>
    
    <!--  Address Value -->
    <TextView
        android:id="@+id/address"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
    
    <!-- Phone Number Label -->
    <TextView
        android:id="@+id/phone"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dip"/>
    
    
    <!-- Location Value -->
    <TextView
        android:id="@+id/location"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dip"
        android:layout_marginBottom="5dip"/>


</LinearLayout>

And below is the code for SinglePlaceActivity.java class. In the following code

> First the place reference id is received from MainActivity.java activity

> Second using the place reference id a background Async thread LoadSinglePlaceDetails() is called to get full details of a place

package com.androidhive.googleplacesandmaps;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Html;
import android.util.Log;
import android.widget.TextView;

public class SinglePlaceActivity extends Activity {
	// flag for Internet connection status
	Boolean isInternetPresent = false;

	// Connection detector class
	ConnectionDetector cd;
	
	// Alert Dialog Manager
	AlertDialogManager alert = new AlertDialogManager();

	// Google Places
	GooglePlaces googlePlaces;
	
	// Place Details
	PlaceDetails placeDetails;
	
	// Progress dialog
	ProgressDialog pDialog;
	
	// KEY Strings
	public static String KEY_REFERENCE = "reference"; // id of the place

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.single_place);
		
		Intent i = getIntent();
		
		// Place referece id
		String reference = i.getStringExtra(KEY_REFERENCE);
		
		// Calling a Async Background thread
		new LoadSinglePlaceDetails().execute(reference);
	}
	
	
	/**
	 * Background Async Task to Load Google places
	 * */
	class LoadSinglePlaceDetails extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(SinglePlaceActivity.this);
			pDialog.setMessage("Loading profile ...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		/**
		 * getting Profile JSON
		 * */
		protected String doInBackground(String... args) {
			String reference = args[0];
			
			// creating Places class object
			googlePlaces = new GooglePlaces();

			// Check if used is connected to Internet
			try {
				placeDetails = googlePlaces.getPlaceDetails(reference);

			} catch (Exception e) {
				e.printStackTrace();
			}
			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog after getting all products
			pDialog.dismiss();
			// updating UI from Background Thread
			runOnUiThread(new Runnable() {
				public void run() {
					/**
					 * Updating parsed Places into LISTVIEW
					 * */
					if(placeDetails != null){
						String status = placeDetails.status;
						
						// check place deatils status
						// Check for all possible status
						if(status.equals("OK")){
							if (placeDetails.result != null) {
								String name = placeDetails.result.name;
								String address = placeDetails.result.formatted_address;
								String phone = placeDetails.result.formatted_phone_number;
								String latitude = Double.toString(placeDetails.result.geometry.location.lat);
								String longitude = Double.toString(placeDetails.result.geometry.location.lng);
								
								Log.d("Place ", name + address + phone + latitude + longitude);
								
								// Displaying all the details in the view
								// single_place.xml
								TextView lbl_name = (TextView) findViewById(R.id.name);
								TextView lbl_address = (TextView) findViewById(R.id.address);
								TextView lbl_phone = (TextView) findViewById(R.id.phone);
								TextView lbl_location = (TextView) findViewById(R.id.location);
								
								// Check for null data from google
								// Sometimes place details might missing
								name = name == null ? "Not present" : name; // if name is null display as "Not present"
								address = address == null ? "Not present" : address;
								phone = phone == null ? "Not present" : phone;
								latitude = latitude == null ? "Not present" : latitude;
								longitude = longitude == null ? "Not present" : longitude;
								
								lbl_name.setText(name);
								lbl_address.setText(address);
								lbl_phone.setText(Html.fromHtml("<b>Phone:</b> " + phone));
								lbl_location.setText(Html.fromHtml("<b>Latitude:</b> " + latitude + ", <b>Longitude:</b> " + longitude));
							}
						}
						else if(status.equals("ZERO_RESULTS")){
							alert.showAlertDialog(SinglePlaceActivity.this, "Near Places",
									"Sorry no place found.",
									false);
						}
						else if(status.equals("UNKNOWN_ERROR"))
						{
							alert.showAlertDialog(SinglePlaceActivity.this, "Places Error",
									"Sorry unknown error occured.",
									false);
						}
						else if(status.equals("OVER_QUERY_LIMIT"))
						{
							alert.showAlertDialog(SinglePlaceActivity.this, "Places Error",
									"Sorry query limit to google places is reached",
									false);
						}
						else if(status.equals("REQUEST_DENIED"))
						{
							alert.showAlertDialog(SinglePlaceActivity.this, "Places Error",
									"Sorry error occured. Request is denied",
									false);
						}
						else if(status.equals("INVALID_REQUEST"))
						{
							alert.showAlertDialog(SinglePlaceActivity.this, "Places Error",
									"Sorry error occured. Invalid Request",
									false);
						}
						else
						{
							alert.showAlertDialog(SinglePlaceActivity.this, "Places Error",
									"Sorry error occured.",
									false);
						}
					}else{
						alert.showAlertDialog(SinglePlaceActivity.this, "Places Error",
								"Sorry error occured.",
								false);
					}
					
					
				}
			});

		}

	}

}
android google place full details


Showing all the places on the Map

If you haven’t worked with maps yet, go through Android Working with Google Maps to get an idea about implementing google maps in your android application.

In our MainActivity you can find a button called Show On Map to show all the places on the map. This the code to call map activity by passing all the places information to PlacesMapActivity.java

This code is implemented in MainActivity.java class

/** Button click event for shown on map */
		btnShowOnMap.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View arg0) {
				Intent i = new Intent(getApplicationContext(),
						PlacesMapActivity.class);
				// Sending user current geo location
				i.putExtra("user_latitude", Double.toString(gps.getLatitude()));
				i.putExtra("user_longitude", Double.toString(gps.getLongitude()));
				
				// passing near places to map activity
				i.putExtra("near_places", nearPlaces);
				// staring activity
				startActivity(i);
			}
		});

12. Create a new class called AddItemizedOverlay.java which is helper class for map activity used to display markers, geopoints on a map.

package com.androidhive.googleplacesandmaps;

import java.util.ArrayList;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapView;
import com.google.android.maps.OverlayItem;

/**
 * Class used to place marker or any overlay items on Map
 * */
public class AddItemizedOverlay extends ItemizedOverlay<OverlayItem> {
 
       private ArrayList<OverlayItem> mapOverlays = new ArrayList<OverlayItem>();
 
       private Context context;
 
       public AddItemizedOverlay(Drawable defaultMarker) {
            super(boundCenterBottom(defaultMarker));
       }
 
       public AddItemizedOverlay(Drawable defaultMarker, Context context) {
            this(defaultMarker);
            this.context = context;
       }
       
       @Override
       public boolean onTouchEvent(MotionEvent event, MapView mapView)
       {   
 
           if (event.getAction() == 1) {
               GeoPoint geopoint = mapView.getProjection().fromPixels(
                   (int) event.getX(),
                   (int) event.getY());
               // latitude
               double lat = geopoint.getLatitudeE6() / 1E6;
               // longitude
               double lon = geopoint.getLongitudeE6() / 1E6;
               //Toast.makeText(context, "Lat: " + lat + ", Lon: "+lon, Toast.LENGTH_SHORT).show();
           }
           return false;
       } 
 
       @Override
       protected OverlayItem createItem(int i) {
          return mapOverlays.get(i);
       }
 
       @Override
       public int size() {
          return mapOverlays.size();
       }
 
       @Override
       protected boolean onTap(int index) {
         OverlayItem item = mapOverlays.get(index);
         AlertDialog.Builder dialog = new AlertDialog.Builder(this.context);
         dialog.setTitle(item.getTitle());
         dialog.setMessage(item.getSnippet());
         dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
 			public void onClick(DialogInterface dialog, int which) {
 			}
 		});
         dialog.show();
         return true;
       }
 
       public void addOverlay(OverlayItem overlay) {
          mapOverlays.add(overlay);
       }
       
       public void populateNow(){
    	   this.populate();
       }
 
    }

13. Create a new class called PlacesMapActivity.java which is the activity to display all the places on the map.

In the following code
> User current location is showed on map in a red marker.

> Remaining all the places are shown in blue markers.

package com.androidhive.googleplacesandmaps;

import java.util.List;

import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.OverlayItem;

public class PlacesMapActivity extends MapActivity {
	// Nearest places
	PlacesList nearPlaces;

	// Map view
	MapView mapView;

	// Map overlay items
	List<Overlay> mapOverlays;

	AddItemizedOverlay itemizedOverlay;

	GeoPoint geoPoint;
	// Map controllers
	MapController mc;
	
	double latitude;
	double longitude;
	OverlayItem overlayitem;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.map_places);

		// Getting intent data
		Intent i = getIntent();
		
		// Users current geo location
		String user_latitude = i.getStringExtra("user_latitude");
		String user_longitude = i.getStringExtra("user_longitude");
		
		// Nearplaces list
		nearPlaces = (PlacesList) i.getSerializableExtra("near_places");

		mapView = (MapView) findViewById(R.id.mapView);
		mapView.setBuiltInZoomControls(true);

		mapOverlays = mapView.getOverlays();
		
		// Geopoint to place on map
		geoPoint = new GeoPoint((int) (Double.parseDouble(user_latitude) * 1E6),
				(int) (Double.parseDouble(user_longitude) * 1E6));
		
		// Drawable marker icon
		Drawable drawable_user = this.getResources()
				.getDrawable(R.drawable.mark_red);
		
		itemizedOverlay = new AddItemizedOverlay(drawable_user, this);
		
		// Map overlay item
		overlayitem = new OverlayItem(geoPoint, "Your Location",
				"That is you!");

		itemizedOverlay.addOverlay(overlayitem);
		
		mapOverlays.add(itemizedOverlay);
		itemizedOverlay.populateNow();
		
		// Drawable marker icon
		Drawable drawable = this.getResources()
				.getDrawable(R.drawable.mark_blue);
		
		itemizedOverlay = new AddItemizedOverlay(drawable, this);

		mc = mapView.getController();

		// These values are used to get map boundary area
		// The area where you can see all the markers on screen
		int minLat = Integer.MAX_VALUE;
		int minLong = Integer.MAX_VALUE;
		int maxLat = Integer.MIN_VALUE;
		int maxLong = Integer.MIN_VALUE;

		// check for null in case it is null
		if (nearPlaces.results != null) {
			// loop through all the places
			for (Place place : nearPlaces.results) {
				latitude = place.geometry.location.lat; // latitude
				longitude = place.geometry.location.lng; // longitude
				
				// Geopoint to place on map
				geoPoint = new GeoPoint((int) (latitude * 1E6),
						(int) (longitude * 1E6));
				
				// Map overlay item
				overlayitem = new OverlayItem(geoPoint, place.name,
						place.vicinity);

				itemizedOverlay.addOverlay(overlayitem);
				
				
				// calculating map boundary area
				minLat  = (int) Math.min( geoPoint.getLatitudeE6(), minLat );
			    minLong = (int) Math.min( geoPoint.getLongitudeE6(), minLong);
			    maxLat  = (int) Math.max( geoPoint.getLatitudeE6(), maxLat );
			    maxLong = (int) Math.max( geoPoint.getLongitudeE6(), maxLong );
			}
			mapOverlays.add(itemizedOverlay);
			
			// showing all overlay items
			itemizedOverlay.populateNow();
		}
		
		// Adjusting the zoom level so that you can see all the markers on map
		mapView.getController().zoomToSpan(Math.abs( minLat - maxLat ), Math.abs( minLong - maxLong ));
		
		// Showing the center of the map
		mc.animateTo(new GeoPoint((maxLat + minLat)/2, (maxLong + minLong)/2 ));
		mapView.postInvalidate();

	}

	@Override
	protected boolean isRouteDisplayed() {
		return false;
	}

}
android showing google places on map
android google places on map popup
This is image for thumbnail purpose.
android working with google places and maps

Finally if you downloaded the code provided in this tutorial, don’t forget to replace the Google API key with your own key.

Android Multilevel ListView Tutorial

$
0
0


This tutorial is about implementing multilevel listviews in your project. I explained by talking an example of music albums where listviews are displayed for list of albums, list of songs under an album.

Download Code



The first screen will be displayed with list of albums in ListView and the second screen will be displayed with list of songs under an album. Third screen will be displayed with a single song information.

JSON used in this project

I created following sample json files for this project.

> http://api.androidhive.info/songs/albums.php – gives you list of albums

> http://api.androidhive.info/songs/album_tracks.php?id=1 – will give your list of songs under album id = 1. You should pass parameter id as GET parameter. (The id of an album can be fetched from the previous url)

> http://api.androidhive.info/songs/track.php?album=3&song=1 – gives you single song information. album and song ids should be posted as GET parameters

I generated sample json using PHP. You can get php project files along with source code of this project.

Creating new Project

1. Create a new project in your Eclipse IDE by navigating to File ⇒ New ⇒ Android Application Project and fill the required details. Name your main activity as AlbumsActivity.java

2. Create a new package for placing helpers classes. You can create new package by right clicking on project src folder new -> package and naming it as your_package.helper (replace your_package with your actual package name)

3. As our application is connecting to internet, add the required permission in your AndroidManifest.xml file. Also i am adding two more class files (TrackListActivity, SingleTrackActivity) in manifest file.

INTERNET – Required to use internet services
ACCESS_NETWORK_STATE – Required to detect internet connection status

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidhive"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AlbumsActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity android:name=".TrackListActivity" />
        
        <activity android:name=".SingleTrackActivity" />
    </application>
    
    <!-- Permission - Internet Connect -->
    <uses-permission android:name="android.permission.INTERNET" />
 
    <!-- Network State Permissions -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

</manifest>

4. Create a new class file named AlertDialogManager.java under your helper package and paste the following code. This file used to show alert dialog in the application.

package com.example.androidhive.helper;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;

import com.example.androidhive.R;

public class AlertDialogManager {
	/**
	 * Function to display simple Alert Dialog
	 * @param context - application context
	 * @param title - alert dialog title
	 * @param message - alert message
	 * @param status - success/failure (used to set icon)
	 * 				 - pass null if you don't want icon
	 * */
	public void showAlertDialog(Context context, String title, String message,
			Boolean status) {
		AlertDialog alertDialog = new AlertDialog.Builder(context).create();

		// Setting Dialog Title
		alertDialog.setTitle(title);

		// Setting Dialog Message
		alertDialog.setMessage(message);

		if(status != null)
			// Setting alert dialog icon
			alertDialog.setIcon((status) ? R.drawable.success : R.drawable.fail);

		// Setting OK Button
		alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
			}
		});

		// Showing Alert Message
		alertDialog.show();
	}
}

5. Create a new file under helper package named ConnectionDetector.java an paste the following code. This file used to detect internet connection.

package com.example.androidhive.helper;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
 
public class ConnectionDetector {
 
    private Context _context;
 
    public ConnectionDetector(Context context){
        this._context = context;
    }
 
    /**
     * Checking for all possible internet providers
     * **/
    public boolean isConnectingToInternet(){
        ConnectivityManager connectivity = (ConnectivityManager) _context.getSystemService(Context.CONNECTIVITY_SERVICE);
          if (connectivity != null)
          {
              NetworkInfo[] info = connectivity.getAllNetworkInfo();
              if (info != null)
                  for (int i = 0; i < info.length; i++)
                      if (info[i].getState() == NetworkInfo.State.CONNECTED)
                      {
                          return true;
                      }
 
          }
          return false;
    }
}

6. Create a new file called JSONParser.java and paste the following code. This class file used to get json data by making http request. (Please note that this class file only fetches the json from http, it won’t parse the json)

package com.example.androidhive.helper;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONObject;

import android.util.Log;

public class JSONParser {

	static InputStream is = null;
	static JSONObject jObj = null;
	static String json = "";

	// constructor
	public JSONParser() {

	}

	// function get json from url
	// by making HTTP POST or GET mehtod
	public String makeHttpRequest(String url, String method,
			List<NameValuePair> params) {

		// Making HTTP request
		try {

			// check for request method
			if (method == "POST") {
				// request method is POST
				// defaultHttpClient
				DefaultHttpClient httpClient = new DefaultHttpClient();
				HttpPost httpPost = new HttpPost(url);
				httpPost.setEntity(new UrlEncodedFormEntity(params));

				HttpResponse httpResponse = httpClient.execute(httpPost);
				HttpEntity httpEntity = httpResponse.getEntity();
				is = httpEntity.getContent();

			} else if (method == "GET") {
				// request method is GET
				DefaultHttpClient httpClient = new DefaultHttpClient();
				String paramString = URLEncodedUtils.format(params, "utf-8");
				url += "?" + paramString;
				HttpGet httpGet = new HttpGet(url);

				HttpResponse httpResponse = httpClient.execute(httpGet);
				HttpEntity httpEntity = httpResponse.getEntity();
				is = httpEntity.getContent();
			}

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(
					is, "iso-8859-1"), 8);
			StringBuilder sb = new StringBuilder();
			String line = null;
			while ((line = reader.readLine()) != null) {
				sb.append(line + "\n");
			}
			is.close();
			json = sb.toString();
		} catch (Exception e) {
			Log.e("Buffer Error", "Error converting result " + e.toString());
		}

		// return JSON String
		return json;

	}
}

First Level ListView

7. Create new xml file under res / layout folder and name it as albums_activity.xml (or rename you existing unused main activity xml file)

8. Open your albums_activity.xml and create a list view.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:background="#ffffff">

    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
    	android:divider="#b5b5b5"
        android:dividerHeight="1dp"
        android:cacheColorHint="#00000000"/>

</LinearLayout>

9. Now create a xml file named list_item_albums.xml for single album listview item and paste the following code. The following list item has album name and songs count of an album.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
 
    <!-- Album id / Hidden by default -->
    <TextView
        android:id="@+id/album_id"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:visibility="gone" />
    
    <!-- Album Name -->
    <TextView
        android:id="@+id/album_name"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="16dip"
        android:textColor="#000000"
        android:paddingTop="15dip"
        android:paddingBottom="15dip"
        android:paddingLeft="10dip"
        android:textStyle="bold"/>
 
    <!-- Album Songs count -->
    <TextView android:id="@+id/songs_count"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:textColor="#ffffff"
        android:textStyle="bold"
        android:background="#9ed321"
        android:paddingRight="3dip"
        android:paddingLeft="3dip"/>
    
 
</RelativeLayout>

10. Extend your AlbumsActivity.java file from ListActivity and add the required variables and class instances.

public class AlbumsActivity extends ListActivity {
public class AlbumsActivity extends ListActivity {
	// Connection detector
	ConnectionDetector cd;
	
	// Alert dialog manager
	AlertDialogManager alert = new AlertDialogManager();
	
	// Progress Dialog
	private ProgressDialog pDialog;

	// Creating JSON Parser object
	JSONParser jsonParser = new JSONParser();

	ArrayList<HashMap<String, String>> albumsList;

	// albums JSONArray
	JSONArray albums = null;

	// albums JSON url
	private static final String URL_ALBUMS = "http://api.androidhive.info/songs/albums.php";

	// ALL JSON node names
	private static final String TAG_ID = "id";
	private static final String TAG_NAME = "name";
	private static final String TAG_SONGS_COUNT = "songs_count";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_albums);
		
		cd = new ConnectionDetector(getApplicationContext());
		 
        // Check for internet connection
        if (!cd.isConnectingToInternet()) {
            // Internet Connection is not present
            alert.showAlertDialog(AlbumsActivity.this, "Internet Connection Error",
                    "Please connect to working Internet connection", false);
            // stop executing code by return
            return;
        }

		// Hashmap for ListView
		albumsList = new ArrayList<HashMap<String, String>>();

11. Now i am adding a new Async background thread to get the albums json by making HTTP request. Once getting json is done, I parsed and displayed the json data in lisview.

Also on selecting a single list item (album) i am launching TrackListActivity.class by passing album id to it. Using album id in TrackListActivity.class i will fetch complete songs list.

Following is the complete code for AlbumsActivity.java file

package com.example.androidhive;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.http.NameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;

import com.example.androidhive.helper.AlertDialogManager;
import com.example.androidhive.helper.ConnectionDetector;
import com.example.androidhive.helper.JSONParser;

public class AlbumsActivity extends ListActivity {
	// Connection detector
	ConnectionDetector cd;
	
	// Alert dialog manager
	AlertDialogManager alert = new AlertDialogManager();
	
	// Progress Dialog
	private ProgressDialog pDialog;

	// Creating JSON Parser object
	JSONParser jsonParser = new JSONParser();

	ArrayList<HashMap<String, String>> albumsList;

	// albums JSONArray
	JSONArray albums = null;

	// albums JSON url
	private static final String URL_ALBUMS = "http://api.androidhive.info/songs/albums.php";

	// ALL JSON node names
	private static final String TAG_ID = "id";
	private static final String TAG_NAME = "name";
	private static final String TAG_SONGS_COUNT = "songs_count";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_albums);
		
		cd = new ConnectionDetector(getApplicationContext());
		 
        // Check for internet connection
        if (!cd.isConnectingToInternet()) {
            // Internet Connection is not present
            alert.showAlertDialog(AlbumsActivity.this, "Internet Connection Error",
                    "Please connect to working Internet connection", false);
            // stop executing code by return
            return;
        }

		// Hashmap for ListView
		albumsList = new ArrayList<HashMap<String, String>>();

		// Loading Albums JSON in Background Thread
		new LoadAlbums().execute();
		
		// get listview
		ListView lv = getListView();
		
		/**
		 * Listview item click listener
		 * TrackListActivity will be lauched by passing album id
		 * */
		lv.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> arg0, View view, int arg2,
					long arg3) {
				// on selecting a single album
				// TrackListActivity will be launched to show tracks inside the album
				Intent i = new Intent(getApplicationContext(), TrackListActivity.class);
				
				// send album id to tracklist activity to get list of songs under that album
				String album_id = ((TextView) view.findViewById(R.id.album_id)).getText().toString();
				i.putExtra("album_id", album_id);				
				
				startActivity(i);
			}
		});		
	}

	/**
	 * Background Async Task to Load all Albums by making http request
	 * */
	class LoadAlbums extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(AlbumsActivity.this);
			pDialog.setMessage("Listing Albums ...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		/**
		 * getting Albums JSON
		 * */
		protected String doInBackground(String... args) {
			// Building Parameters
			List<NameValuePair> params = new ArrayList<NameValuePair>();

			// getting JSON string from URL
			String json = jsonParser.makeHttpRequest(URL_ALBUMS, "GET",
					params);

			// Check your log cat for JSON reponse
			Log.d("Albums JSON: ", "> " + json);

			try {				
				albums = new JSONArray(json);
				
				if (albums != null) {
					// looping through All albums
					for (int i = 0; i < albums.length(); i++) {
						JSONObject c = albums.getJSONObject(i);

						// Storing each json item values in variable
						String id = c.getString(TAG_ID);
						String name = c.getString(TAG_NAME);
						String songs_count = c.getString(TAG_SONGS_COUNT);

						// creating new HashMap
						HashMap<String, String> map = new HashMap<String, String>();

						// adding each child node to HashMap key => value
						map.put(TAG_ID, id);
						map.put(TAG_NAME, name);
						map.put(TAG_SONGS_COUNT, songs_count);

						// adding HashList to ArrayList
						albumsList.add(map);
					}
				}else{
					Log.d("Albums: ", "null");
				}

			} catch (JSONException e) {
				e.printStackTrace();
			}

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog after getting all albums
			pDialog.dismiss();
			// updating UI from Background Thread
			runOnUiThread(new Runnable() {
				public void run() {
					/**
					 * Updating parsed JSON data into ListView
					 * */
					ListAdapter adapter = new SimpleAdapter(
							AlbumsActivity.this, albumsList,
							R.layout.list_item_albums, new String[] { TAG_ID,
									TAG_NAME, TAG_SONGS_COUNT }, new int[] {
									R.id.album_id, R.id.album_name, R.id.songs_count });
					
					// updating listview
					setListAdapter(adapter);
				}
			});

		}

	}
}

And the output for the above listview

android multilevel listiview


Second Level ListView

12. Now create a new class file called TrackListActivity.java and extend it from ListActivity.

public class TrackListActivity extends ListActivity {

13. Create a xml file for TrackListActivity.java and create a listview in it. Name your xml file as activity_tracks.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:background="#ffffff">

    <ListView
        android:id="@android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
    	android:divider="#b5b5b5"
        android:dividerHeight="1dp"
        android:cacheColorHint="#00000000"/>

</LinearLayout>

14. Also create a xml file named list_item_tracks.xml for single list item and type the following code. In the following code each list item has song title and the duration of the song.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

    <!-- Album id / Hidden by default -->
    <TextView
        android:id="@+id/album_id"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:visibility="gone" />

    <!-- Song id / Hidden by default -->
    <TextView
        android:id="@+id/song_id"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:visibility="gone" />
    
    <!-- Track serial no -->
    <TextView
        android:id="@+id/track_no"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="15dip"
        android:paddingLeft="5dip"
        android:paddingTop="15dip"
        android:textColor="#000000"
        android:textSize="16dip"
        android:layout_alignParentLeft="true"/>

    <!-- Song Name -->
    <TextView
        android:id="@+id/album_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingBottom="15dip"
        android:paddingLeft="5dip"
        android:paddingTop="15dip"
        android:textColor="#000000"
        android:textSize="16dip"
        android:layout_toRightOf="@+id/track_no"/>

    <!-- Song duration -->
    <TextView
        android:id="@+id/song_duration"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:paddingLeft="3dip"
        android:paddingRight="6dip"
        android:textColor="#9ed321" />

</RelativeLayout>

15. Open your TrackListActivity.java and add the following code. In the following code

> First i am receiving album id from the previous activity (which is AlbumsActivity.java)
> I am calling a new async background thread called LoadTracks().execute() which will send http request and get songs list under an album (using album id)
> Once the data is received i parsed it and displayed in listiview.
> Also on selecting a single list item (song) i am launching a new activity named SingleTrackActivity.java by passing album id and song id to it.

package com.example.androidhive;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.example.androidhive.helper.AlertDialogManager;
import com.example.androidhive.helper.ConnectionDetector;
import com.example.androidhive.helper.JSONParser;

import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;

public class TrackListActivity extends ListActivity {
	// Connection detector
	ConnectionDetector cd;
	
	// Alert dialog manager
	AlertDialogManager alert = new AlertDialogManager();
	
	// Progress Dialog
	private ProgressDialog pDialog;

	// Creating JSON Parser object
	JSONParser jsonParser = new JSONParser();

	ArrayList<HashMap<String, String>> tracksList;

	// tracks JSONArray
	JSONArray albums = null;
	
	// Album id
	String album_id, album_name;

	// tracks JSON url
	// id - should be posted as GET params to get track list (ex: id = 5)
	private static final String URL_ALBUMS = "http://api.androidhive.info/songs/album_tracks.php";

	// ALL JSON node names
	private static final String TAG_SONGS = "songs";
	private static final String TAG_ID = "id";
	private static final String TAG_NAME = "name";
	private static final String TAG_ALBUM = "album";
	private static final String TAG_DURATION = "duration";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_tracks);
		
		cd = new ConnectionDetector(getApplicationContext());
		 
        // Check if Internet present
        if (!cd.isConnectingToInternet()) {
            // Internet Connection is not present
            alert.showAlertDialog(TrackListActivity.this, "Internet Connection Error",
                    "Please connect to working Internet connection", false);
            // stop executing code by return
            return;
        }
        
        // Get album id
        Intent i = getIntent();
        album_id = i.getStringExtra("album_id");

		// Hashmap for ListView
		tracksList = new ArrayList<HashMap<String, String>>();

		// Loading tracks in Background Thread
		new LoadTracks().execute();
		
		// get listview
		ListView lv = getListView();
		
		/**
		 * Listview on item click listener
		 * SingleTrackActivity will be lauched by passing album id, song id
		 * */
		lv.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> arg0, View view, int arg2,
					long arg3) {
				// On selecting single track get song information
				Intent i = new Intent(getApplicationContext(), SingleTrackActivity.class);
				
				// to get song information
				// both album id and song is needed
				String album_id = ((TextView) view.findViewById(R.id.album_id)).getText().toString();
				String song_id = ((TextView) view.findViewById(R.id.song_id)).getText().toString();
				
				Toast.makeText(getApplicationContext(), "Album Id: " + album_id  + ", Song Id: " + song_id, Toast.LENGTH_SHORT).show();
				
				i.putExtra("album_id", album_id);
				i.putExtra("song_id", song_id);
				
				startActivity(i);
			}
		});	

	}

	/**
	 * Background Async Task to Load all tracks under one album
	 * */
	class LoadTracks extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(TrackListActivity.this);
			pDialog.setMessage("Loading songs ...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		/**
		 * getting tracks json and parsing
		 * */
		protected String doInBackground(String... args) {
			// Building Parameters
			List<NameValuePair> params = new ArrayList<NameValuePair>();
			
			// post album id as GET parameter
			params.add(new BasicNameValuePair(TAG_ID, album_id));

			// getting JSON string from URL
			String json = jsonParser.makeHttpRequest(URL_ALBUMS, "GET",
					params);

			// Check your log cat for JSON reponse
			Log.d("Track List JSON: ", json);

			try {
				JSONObject jObj = new JSONObject(json);
				if (jObj != null) {
					String album_id = jObj.getString(TAG_ID);
					album_name = jObj.getString(TAG_ALBUM);
					albums = jObj.getJSONArray(TAG_SONGS);

					if (albums != null) {
						// looping through All songs
						for (int i = 0; i < albums.length(); i++) {
							JSONObject c = albums.getJSONObject(i);

							// Storing each json item in variable
							String song_id = c.getString(TAG_ID);
							// track no - increment i value
							String track_no = String.valueOf(i + 1);
							String name = c.getString(TAG_NAME);
							String duration = c.getString(TAG_DURATION);

							// creating new HashMap
							HashMap<String, String> map = new HashMap<String, String>();

							// adding each child node to HashMap key => value
							map.put("album_id", album_id);
							map.put(TAG_ID, song_id);
							map.put("track_no", track_no + ".");
							map.put(TAG_NAME, name);
							map.put(TAG_DURATION, duration);

							// adding HashList to ArrayList
							tracksList.add(map);
						}
					} else {
						Log.d("Albums: ", "null");
					}
				}

			} catch (JSONException e) {
				e.printStackTrace();
			}

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog after getting all tracks
			pDialog.dismiss();
			// updating UI from Background Thread
			runOnUiThread(new Runnable() {
				public void run() {
					/**
					 * Updating parsed JSON data into ListView
					 * */
					ListAdapter adapter = new SimpleAdapter(
							TrackListActivity.this, tracksList,
							R.layout.list_item_tracks, new String[] { "album_id", TAG_ID, "track_no",
									TAG_NAME, TAG_DURATION }, new int[] {
									R.id.album_id, R.id.song_id, R.id.track_no, R.id.album_name, R.id.song_duration });
					// updating listview
					setListAdapter(adapter);
					
					// Change Activity Title with Album name
					setTitle(album_name);
				}
			});

		}

	}
}
android multilevel listview two


Third Level View

16. Create a class file called SingleTrackActivity.java and an xml file called activity_single_track.xml

17. Open activity_single_track.xml file and paste the following code. In the following layout file i added TextViews for song title, album and duration.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#ffffff" >
    
    <!-- Song Title -->
    <TextView android:id="@+id/song_title"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="25dip"
        android:padding="10dip"
        android:textColor="#444444"
        android:textStyle="bold"/>
    
    <!-- Album Name -->
	<TextView android:id="@+id/album_name"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:paddingLeft="10dip"
	    android:textSize="15dip"
	    android:textColor="#000000"
	    android:paddingBottom="10dip"/>
	
	<!-- Song Duration -->
	<TextView android:id="@+id/duration"
	    android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:paddingLeft="10dip"
	    android:textSize="15dip"
	    android:textColor="#000000"
	    android:paddingBottom="10dip"/>
	
</LinearLayout>

18. Open SingleTrackActivity.java file and try following code. In the following code

> From the previous activity TracksListActivity.java, album id and song id is received
> a new asyc background thread LoadSingleTrack().execute() is called to get single song information. To get single song information album id and song id is posted as GET parameters.
> After fetching json from http, json data is parsed and displayed in view.

Complete code for SingleTrackActivity.java

package com.example.androidhive;

import java.util.ArrayList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.Html;
import android.util.Log;
import android.widget.TextView;

import com.example.androidhive.helper.AlertDialogManager;
import com.example.androidhive.helper.ConnectionDetector;
import com.example.androidhive.helper.JSONParser;

public class SingleTrackActivity extends Activity {
	// Connection detector
	ConnectionDetector cd;
	
	// Alert dialog manager
	AlertDialogManager alert = new AlertDialogManager();
	
	// Progress Dialog
	private ProgressDialog pDialog;

	// Creating JSON Parser object
	JSONParser jsonParser = new JSONParser();

	// tracks JSONArray
	JSONArray albums = null;
	
	// Album id
	String album_id = null;
	String song_id = null;
	
	String album_name, song_name, duration;

	// single song JSON url
	// GET parameters album, song
	private static final String URL_SONG = "http://api.androidhive.info/songs/track.php";

	// ALL JSON node names
	private static final String TAG_NAME = "name";
	private static final String TAG_DURATION = "duration";
	private static final String TAG_ALBUM = "album";
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_single_track);
		
		cd = new ConnectionDetector(getApplicationContext());
		 
        // Check if Internet present
        if (!cd.isConnectingToInternet()) {
            // Internet Connection is not present
            alert.showAlertDialog(SingleTrackActivity.this, "Internet Connection Error",
                    "Please connect to working Internet connection", false);
            // stop executing code by return
            return;
        }
        
        // Get album id, song id
        Intent i = getIntent();
        album_id = i.getStringExtra("album_id");
        song_id = i.getStringExtra("song_id");
        
        // calling background thread
        new LoadSingleTrack().execute();
	}
	
	/**
	 * Background Async Task to get single song information
	 * */
	class LoadSingleTrack extends AsyncTask<String, String, String> {

		/**
		 * Before starting background thread Show Progress Dialog
		 * */
		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(SingleTrackActivity.this);
			pDialog.setMessage("Loading song ...");
			pDialog.setIndeterminate(false);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		/**
		 * getting song json and parsing
		 * */
		protected String doInBackground(String... args) {
			// Building Parameters
			List<NameValuePair> params = new ArrayList<NameValuePair>();
			
			// post album id, song id as GET parameters
			params.add(new BasicNameValuePair("album", album_id));
			params.add(new BasicNameValuePair("song", song_id));

			// getting JSON string from URL
			String json = jsonParser.makeHttpRequest(URL_SONG, "GET",
					params);

			// Check your log cat for JSON reponse
			Log.d("Single Track JSON: ", json);

			try {
				JSONObject jObj = new JSONObject(json);
				if(jObj != null){
					song_name = jObj.getString(TAG_NAME);
					album_name = jObj.getString(TAG_ALBUM);
					duration = jObj.getString(TAG_DURATION);					
				}			

			} catch (JSONException e) {
				e.printStackTrace();
			}

			return null;
		}

		/**
		 * After completing background task Dismiss the progress dialog
		 * **/
		protected void onPostExecute(String file_url) {
			// dismiss the dialog after getting song information
			pDialog.dismiss();
			
			// updating UI from Background Thread
			runOnUiThread(new Runnable() {
				public void run() {
					
					TextView txt_song_name = (TextView) findViewById(R.id.song_title);
					TextView txt_album_name = (TextView) findViewById(R.id.album_name);
					TextView txt_duration = (TextView) findViewById(R.id.duration);
					
					// displaying song data in view
					txt_song_name.setText(song_name);
					txt_album_name.setText(Html.fromHtml("<b>Album:</b> " + album_name));
					txt_duration.setText(Html.fromHtml("<b>Duration:</b> " + duration));
					
					// Change Activity Title with Song title
					setTitle(song_name);
				}
			});

		}

	}
}
android multilevel listview three
This image is for thumbnail purpose
android multilevel listview tutorial

How to implement Android Splash Screen

$
0
0



Android splash screen are normally used to show user some kind of progress before the app loads completely. Some people uses splash screen just to show case their app / company logo for a couple of second. Unfortunately in android we don’t have any inbuilt mechanism to show splash screen compared to iOS. In this tutorial we are going to learn how to implement splash screen in your android application.

Download Code
This image is for thumbnail purpose only
android splash screen


Splash screen use case scenarios

The purpose of splash screen depends upon the app requirement. Check out the following diagram which gives explanation about two use case scenarios.

android splash screen use case scenarios

In this tutorial I’ll be covering implementation of splash screen in two scenarios. One is showing splash screen using a timer and second is showing splash screen when making network http calls which takes some time to fetch required information. Both the tutorial are same except the splash screen activity.

In order to implement splash screen we are going to create a separate activity for splash and once it closes we launch our main activity.

So let’s get started by creating a new project


1. Android Splash Screen Using Timer

1. Create a new project in Eclipse by navigating to File ⇒ New Android ⇒ Application Project and fill required details. (I kept my main activity name as MainActivity.java)

2. For Splash Screen we are creating a separate activity. Create a new class in your package and name it as SplashScreen.java

3. Open your your AndroidManifest.xml file and make your splash screen activity as Launcher activity.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.androidsplashscreentimer"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <!-- Splash screen -->
        <activity
            android:name="info.androidhive.androidsplashscreentimer.SplashScreen"
            android:label="@string/app_name"
            android:screenOrientation="portrait"
            android:theme="@android:style/Theme.Black.NoTitleBar" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <!-- Main activity -->
        <activity
            android:name="info.androidhive.androidsplashscreentimer.MainActivity"
            android:label="@string/app_name" >
        </activity>
    </application>

</manifest>



4. Create a layout file for splash screen under res ⇒ layout folder. I named the layout file as activity_splash.xml. This layout normally contains your app logo or company logo.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/gradient_background" >

    <ImageView
        android:id="@+id/imgLogo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:src="@drawable/wwe_logo" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:textSize="12dp"
        android:textColor="#454545"
        android:gravity="center_horizontal"
        android:layout_alignParentBottom="true"
        android:text="www.androidhive.info" />

</RelativeLayout>



5. Add the following code in SplashScreen.java activity. In this following code a handler is used to wait for specific time and once the timer is out we launched main activity.

package info.androidhive.androidsplashscreentimer;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;

public class SplashScreen extends Activity {

	// Splash screen timer
	private static int SPLASH_TIME_OUT = 3000;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_splash);

		new Handler().postDelayed(new Runnable() {

			/*
			 * Showing splash screen with a timer. This will be useful when you
			 * want to show case your app logo / company
			 */

			@Override
			public void run() {
				// This method will be executed once the timer is over
				// Start your app main activity
				Intent i = new Intent(SplashScreen.this, MainActivity.class);
				startActivity(i);

				// close this activity
				finish();
			}
		}, SPLASH_TIME_OUT);
	}

}


android splash screen output

Run the application, you will see the splash screen for 3 sec and then your main activity will be launched.



Following is the second scenario where our app is going to make some network calls before entering into app. Here every thing is same as previous case except the SplashScreen.java code. In splash screen activity in onCreate method we will calls a AsyncTask method which fetch required information by making http call. Once the http call terminates we launch main activity in onPostExecute() method.


2. Android Splash Screen When Making Network (http) Calls

1. Follow the same steps as above until creation of SplashScreen.java

2. After that the first step is add INTERNET permission in the manifest file as this app going to use internet. Open your AndroidManifest.xml file and add INTERNET permission above <application> tag

<uses-permission android:name="android.permission.INTERNET"/>

3. Open your SplashScreen.java and add AsyncTask to make http calls. In this tutorial i am making an http call to get json and displayed on the log in screen. After fetching the json the data will be sent to MainActivity.java using Intents.

Following is the json i am going to fetch. You can access this json from http://api.androidhive.info/game/game_stats.json

{
	"game_stat":{
		"now_playing" : "49500",
		"earned" : "$65000"
	}
	.
	.
	.

Paste the following code in SplashScreen.java

package info.androidhive.androidsplashscreentimer;

import info.androidhive.androidsplashscreennetwork.R;
import info.androidhive.network.JsonParser;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;

public class SplashScreen extends Activity {

	String now_playing, earned;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_splash);

		/**
		 * Showing splashscreen while making network calls to download necessary
		 * data before launching the app Will use AsyncTask to make http call
		 */
		new PrefetchData().execute();

	}

	/**
	 * Async Task to make http call
	 */
	private class PrefetchData extends AsyncTask<Void, Void, Void> {

		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			// before making http calls			

		}

		@Override
		protected Void doInBackground(Void... arg0) {
			/*
			 * Will make http call here This call will download required data
			 * before launching the app 
			 * example: 
			 * 1. Downloading and storing in SQLite 
			 * 2. Downloading images 
			 * 3. Fetching and parsing the xml / json 
			 * 4. Sending device information to server 
			 * 5. etc.,
			 */
			JsonParser jsonParser = new JsonParser();
			String json = jsonParser
					.getJSONFromUrl("http://api.androidhive.info/game/game_stats.json");

			Log.e("Response: ", "> " + json);

			if (json != null) {
				try {
					JSONObject jObj = new JSONObject(json)
							.getJSONObject("game_stat");
					now_playing = jObj.getString("now_playing");
					earned = jObj.getString("earned");

					Log.e("JSON", "> " + now_playing + earned);

				} catch (JSONException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

			}

			return null;
		}

		@Override
		protected void onPostExecute(Void result) {
			super.onPostExecute(result);
			// After completing http call
			// will close this activity and lauch main activity
			Intent i = new Intent(SplashScreen.this, MainActivity.class);
			i.putExtra("now_playing", now_playing);
			i.putExtra("earned", earned);
			startActivity(i);

			// close this activity
			finish();
		}

	}

}

4. On your MainActivity.java take appropriate action using parsed json data. I just displayed on log in screen.

package info.androidhive.androidsplashscreentimer;

import info.androidhive.androidsplashscreennetwork.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends Activity {

	LinearLayout llStats;
	TextView txtPlayCount, txtEarned;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		llStats = (LinearLayout) findViewById(R.id.llStats);
		txtPlayCount = (TextView) findViewById(R.id.txtNowPlaying);
		txtEarned = (TextView) findViewById(R.id.txtEarned);

		// layout background transparent
		llStats.getBackground().setAlpha(150);
		llStats.setVisibility(View.VISIBLE);

		Intent i = getIntent();
		String now_playing = i.getStringExtra("now_playing");
		String earned = i.getStringExtra("earned");

		// Diplaying the text
		txtPlayCount.setText(now_playing);
		txtEarned.setText(earned);
	}
}


android splash screen when making http

I provided two separate project in the source code of this tutorial, download and go through the code to get clear picture of the concept.

Android Populating Spinner data from MySQL Database

$
0
0

My previous tutorial Android Populating Spinner data from SQLite Database explained how to pupulate spinner data from SQLite database. In this tutorial we are going to learn how to do the same, except the spinner data is being loaded from MySQL database. I choose PHP as server side technology which acts as a connecting layer between android app and mysql database.

If you haven’t tried connecting android with PHP and MySQL, How to connect Android with PHP, MySQL will give you complete overview of communicating android app with MySQL.

android populating spinner mysql database


Installing WAMP Server

WAMP lets you install Apache, PHP and MySQL with a single installer which reduces burden of installing & configuring them separately. Alternatively you can use XAMP, LAMP (on Linux) and MAMP (on MAC). WAMP also provides you phpmyadmin to easily interact with MySQL database.

Download & install WAMP from http://www.wampserver.com/en/. Choose the correct version which suits your operating system (32bit or 64bit). Once you have installed it, open the program from Start -> All Programs -> Wamp Server -> Start WampServer.

Open http://localhost/ and http://localhost/phpmyadmin/ to verify WAMP is installed successfully or not.


Creating MySQL Database

To demonstrate this tutorial I have taken an example of Food Categories API where we do two operations. Once is getting list of food categories and other is creating new food category. So let’s start creating a database for this.

Open phpmyadmin from http://localhost/phpmyadmin/ and create a database and tables using following SQL queries. Also you can use phpmyadmin GUI to create database and tables.

Creating database

CREATE DATABASE food;

Creating categories table & inserting some data.

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `categories` (`name`) VALUES
('Beverages'),
('Bread'),
('Cereals'),
('Cheese'),
('Citrus Fruits');


Creating PHP Project

On windows PC default location of the WAMP installation is C:\wamp. Go to this directory and open www directory. All the PHP projects will go into this directory.

The PHP project we are going to create will have following list of files.

Config.php – All the configuration variables like database name, username, password and other things goes here.
DbConnect.php – A class to open and close the database connection.
get_categories.php – To fetch list of food categories
new_category – To create a new food category in database.

I have used Netbeans IDE to develop my php project. This is optional, you can use your favorite IDE.

1. Create a new folder named food_api under C:\wamp\www directory for our project.

2. Create a new file named Config.php and write following code. Here we mentioned database name, username, password and host name. If your mysql database is having a password, don’t forget to define it here.

<?php
/**
 * Database configuration
 */
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_HOST', 'localhost');
define('DB_NAME', 'food');
?>

3. The first API call is listing all food categories. So create a file named get_categories.php with following content. Here we simply open the database connection, run a select query and build a response array and echo the json.

<?php
include_once './DbConnect.php';

function getCategories(){
    $db = new DbConnect();
    // array for json response
    $response = array();
    $response["categories"] = array();
    
    // Mysql select query
    $result = mysql_query("SELECT * FROM categories");
    
    while($row = mysql_fetch_array($result)){
        // temporary array to create single category
        $tmp = array();
        $tmp["id"] = $row["id"];
        $tmp["name"] = $row["name"];
        
        // push category to final json array
        array_push($response["categories"], $tmp);
    }
    
    // keeping response header to json
    header('Content-Type: application/json');
    
    // echoing json result
    echo json_encode($response);
}

getCategories();
?>

You can see the output of this file by opening http://localhost/food_api/get_categories.php

4. The second API call is creating a new food category. So create a file named new_category with following content. This api call accepts POST method and check for parameter name which is name of the new category to be created in database.

<?php

include_once './DbConnect.php';

function createNewCategory() {
    if (isset($_POST["name"]) && $_POST["name"] != "") {
        // response array for json
        $response = array();
        $category = $_POST["name"];
        
        $db = new DbConnect();

        // mysql query
        $query = "INSERT INTO categories(name) VALUES('$category')";
        $result = mysql_query($query) or die(mysql_error());
        if ($result) {
            $response["error"] = false;
            $response["message"] = "Category created successfully!";
        } else {
            $response["error"] = true;
            $response["message"] = "Failed to create category!";
        }
    } else {
        $response["error"] = true;
        $response["message"] = "Category name is missing!";
    }
    
    // echo json response
    echo json_encode($response);
}

createNewCategory();
?>

The url for this api call is http://localhost/food_api/new_category.php.

Until now we are done with server side part. It’s time to move on to android part.

Creating Android Project

1. Create a new project in Eclipse from File ⇒ New ⇒ Android Application Project. I had left my main activity name as MainActivity.java and gave the package name as info.androidhive.spinnermysql

2. As this application is going to use internet, we need to add INTERNET permission in AndroidManifest.xml file. Open your AndroidManifest.xml file and add following permission.

<uses-permission android:name=”android.permission.INTERNET”/>

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.spinnermysql"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />
    
    <!-- Internet Permissions -->
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="info.androidhive.spinnermysql.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

3. Create a class file named Category.php. This model class will be useful to convert json data into objects.

package info.androidhive.spinnermysql;

public class Category {
	
	private int id;
	private String name;
	
	public Category(){}
	
	public Category(int id, String name){
		this.id = id;
		this.name = name;
	}
	
	public void setId(int id){
		this.id = id;
	}
	
	public void setName(String name){
		this.name = name;
	}
	
	public int getId(){
		return this.id;
	}
	
	public String getName(){
		return this.name;
	}

}

4. I am creating another class to take care of making API calls and handling the response. Create a class named ServiceHandler.java and write the following code. makeServiceCall() method should be called to make http calls.

package info.androidhive.spinnermysql;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;

import android.util.Log;

public class ServiceHandler {

	static InputStream is = null;
	static String response = null;
	public final static int GET = 1;
	public final static int POST = 2;

	public ServiceHandler() {

	}

	/**
	 * Making service call
	 * @url - url to make request
	 * @method - http request method
	 * */
	public String makeServiceCall(String url, int method) {
		return this.makeServiceCall(url, method, null);
	}

	/**
	 * Making service call
	 * @url - url to make request
	 * @method - http request method
	 * @params - http request params
	 * */
	public String makeServiceCall(String url, int method,
			List<NameValuePair> params) {
		try {
			// http client
			DefaultHttpClient httpClient = new DefaultHttpClient();
			HttpEntity httpEntity = null;
			HttpResponse httpResponse = null;
			
			// Checking http request method type
			if (method == POST) {
				HttpPost httpPost = new HttpPost(url);
				// adding post params
				if (params != null) {
					httpPost.setEntity(new UrlEncodedFormEntity(params));
				}

				httpResponse = httpClient.execute(httpPost);

			} else if (method == GET) {
				// appending params to url
				if (params != null) {
					String paramString = URLEncodedUtils
							.format(params, "utf-8");
					url += "?" + paramString;
				}
				HttpGet httpGet = new HttpGet(url);

				httpResponse = httpClient.execute(httpGet);

			}
			httpEntity = httpResponse.getEntity();
			is = httpEntity.getContent();

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(
					is, "UTF-8"), 8);
			StringBuilder sb = new StringBuilder();
			String line = null;
			while ((line = reader.readLine()) != null) {
				sb.append(line + "\n");
			}
			is.close();
			response = sb.toString();
		} catch (Exception e) {
			Log.e("Buffer Error", "Error: " + e.toString());
		}

		return response;

	}
}

5. Now design the interface for your main activity. I have created simple layout to insert new category and a spinner to show the categories fetched from MySQL database. Open activity_main.xml file and insert the following code.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New food category" />
    
    <EditText android:id="@+id/txtCategory"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>
    
    <Button android:id="@+id/btnAddNewCategory"
        android:text="Create"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="40dp"/>
    
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Select food category"/>
    
    <Spinner android:id="@+id/spinFood"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

The above xml produces following UI

android spinner inserting into mysql

6. Open your MainActivity.java file add the basic code like declaring required variables, button click event, spinner listener etc.,

URL_NEW_CATEGORY – Url to create new category
URL_CATEGORIES – Url to get list of categories

public class MainActivity extends Activity implements OnItemSelectedListener {

	private Button btnAddNewCategory;
	private TextView txtCategory;
	private Spinner spinnerFood;
	// array list for spinner adapter
	private ArrayList<Category> categoriesList;
	ProgressDialog pDialog;

	// API urls
	// Url to create new category
	private String URL_NEW_CATEGORY = "http://10.0.2.2/food_api/new_category.php";
	// Url to get all categories
	private String URL_CATEGORIES = "http://10.0.2.2/food_api/get_categories.php";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		btnAddNewCategory = (Button) findViewById(R.id.btnAddNewCategory);
		spinnerFood = (Spinner) findViewById(R.id.spinFood);
		txtCategory = (TextView) findViewById(R.id.txtCategory);
		
		categoriesList = new ArrayList<Category>();

		// spinner item select listener
		spinnerFood.setOnItemSelectedListener(this);

		// Add new category click event
		btnAddNewCategory.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				if (txtCategory.getText().toString().trim().length() > 0) {
					
					// new category name
					String newCategory = txtCategory.getText().toString();

					// Call Async task to create new category
					new AddNewCategory().execute(newCategory);
				} else {
					Toast.makeText(getApplicationContext(),
							"Please enter category name", Toast.LENGTH_SHORT)
							.show();
				}
			}
		});

		new GetCategories().execute();		
	}
}



Getting list of categories and showing them in Spinner

7. I defined an Async method to fetch list of categories from MySQL and showing them in spinner. Add the following code after onCreate block in MainActivity.java. I also added another method populateSpinner() to takes care of loading the data into spinner. This async method should be called in onCreate method like new GetCategories().execute()

	/**
	 * Async task to get all food categories
	 * */
	private class GetCategories extends AsyncTask<Void, Void, Void> {

		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(MainActivity.this);
			pDialog.setMessage("Fetching food categories..");
			pDialog.setCancelable(false);
			pDialog.show();

		}

		@Override
		protected Void doInBackground(Void... arg0) {
			ServiceHandler jsonParser = new ServiceHandler();
			String json = jsonParser.makeServiceCall(URL_CATEGORIES, ServiceHandler.GET);

			Log.e("Response: ", "> " + json);

			if (json != null) {
				try {
					JSONObject jsonObj = new JSONObject(json);
					if (jsonObj != null) {
						JSONArray categories = jsonObj
								.getJSONArray("categories");						

						for (int i = 0; i < categories.length(); i++) {
							JSONObject catObj = (JSONObject) categories.get(i);
							Category cat = new Category(catObj.getInt("id"),
									catObj.getString("name"));
							categoriesList.add(cat);
						}
					}

				} catch (JSONException e) {
					e.printStackTrace();
				}

			} else {
				Log.e("JSON Data", "Didn't receive any data from server!");
			}

			return null;
		}

		@Override
		protected void onPostExecute(Void result) {
			super.onPostExecute(result);
			if (pDialog.isShowing())
				pDialog.dismiss();
			//populateSpinner();
		}

	}

	/**
	 * Adding spinner data
	 * */
	private void populateSpinner() {
		List<String> lables = new ArrayList<String>();
		
		txtCategory.setText("");

		for (int i = 0; i < categoriesList.size(); i++) {
			lables.add(categoriesList.get(i).getName());
		}

		// Creating adapter for spinner
		ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(this,
				android.R.layout.simple_spinner_item, lables);

		// Drop down layout style - list view with radio button
		spinnerAdapter
				.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

		// attaching data adapter to spinner
		spinnerFood.setAdapter(spinnerAdapter);
	}

If you run the project now, you should see the mysql data loaded into spinner. Use LogCat to debug the errors.

android populating spinner data from mysql database



Inserting a new food category into MySQL

8. I added another async method to insert a new food category into MySQL database. This method is called in create new button click event using new AddNewCategory().execute(newCategory)

/**
	 * Async task to create a new food category
	 * */
	private class AddNewCategory extends AsyncTask<String, Void, Void> {

		boolean isNewCategoryCreated = false;

		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(MainActivity.this);
			pDialog.setMessage("Creating new category..");
			pDialog.setCancelable(false);
			pDialog.show();

		}

		@Override
		protected Void doInBackground(String... arg) {

			String newCategory = arg[0];

			// Preparing post params
			List<NameValuePair> params = new ArrayList<NameValuePair>();
			params.add(new BasicNameValuePair("name", newCategory));

			ServiceHandler serviceClient = new ServiceHandler();

			String json = serviceClient.makeServiceCall(URL_NEW_CATEGORY,
					ServiceHandler.POST, params);

			Log.d("Create Response: ", "> " + json);

			if (json != null) {
				try {
					JSONObject jsonObj = new JSONObject(json);
					boolean error = jsonObj.getBoolean("error");
					// checking for error node in json
					if (!error) {	
						// new category created successfully
						isNewCategoryCreated = true;
					} else {
						Log.e("Create Category Error: ", "> " + jsonObj.getString("message"));
					}

				} catch (JSONException e) {
					e.printStackTrace();
				}

			} else {
				Log.e("JSON Data", "Didn't receive any data from server!");
			}

			return null;
		}

		@Override
		protected void onPostExecute(Void result) {
			super.onPostExecute(result);
			if (pDialog.isShowing())
				pDialog.dismiss();
			if (isNewCategoryCreated) {
				runOnUiThread(new Runnable() {
					@Override
					public void run() {
						// fetching all categories
						new GetCategories().execute();
					}
				});
			}
		}

	}

Run and the test the app again to check insertion.

Complete Code

Following is the complete code of MainActivity.java

package info.androidhive.spinnermysql;

import java.util.ArrayList;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity implements OnItemSelectedListener {

	private Button btnAddNewCategory;
	private TextView txtCategory;
	private Spinner spinnerFood;
	// array list for spinner adapter
	private ArrayList<Category> categoriesList;
	ProgressDialog pDialog;

	// API urls
	// Url to create new category
	private String URL_NEW_CATEGORY = "http://10.0.2.2/food_api/new_category.php";
	// Url to get all categories
	private String URL_CATEGORIES = "http://10.0.2.2/food_api/get_categories.php";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		btnAddNewCategory = (Button) findViewById(R.id.btnAddNewCategory);
		spinnerFood = (Spinner) findViewById(R.id.spinFood);
		txtCategory = (TextView) findViewById(R.id.txtCategory);
		
		categoriesList = new ArrayList<Category>();

		// spinner item select listener
		spinnerFood.setOnItemSelectedListener(this);

		// Add new category click event
		btnAddNewCategory.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				if (txtCategory.getText().toString().trim().length() > 0) {
					
					// new category name
					String newCategory = txtCategory.getText().toString();

					// Call Async task to create new category
					new AddNewCategory().execute(newCategory);
				} else {
					Toast.makeText(getApplicationContext(),
							"Please enter category name", Toast.LENGTH_SHORT)
							.show();
				}
			}
		});

		new GetCategories().execute();

	}

	/**
	 * Adding spinner data
	 * */
	private void populateSpinner() {
		List<String> lables = new ArrayList<String>();
		
		txtCategory.setText("");

		for (int i = 0; i < categoriesList.size(); i++) {
			lables.add(categoriesList.get(i).getName());
		}

		// Creating adapter for spinner
		ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(this,
				android.R.layout.simple_spinner_item, lables);

		// Drop down layout style - list view with radio button
		spinnerAdapter
				.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

		// attaching data adapter to spinner
		spinnerFood.setAdapter(spinnerAdapter);
	}

	/**
	 * Async task to get all food categories
	 * */
	private class GetCategories extends AsyncTask<Void, Void, Void> {

		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(MainActivity.this);
			pDialog.setMessage("Fetching food categories..");
			pDialog.setCancelable(false);
			pDialog.show();

		}

		@Override
		protected Void doInBackground(Void... arg0) {
			ServiceHandler jsonParser = new ServiceHandler();
			String json = jsonParser.makeServiceCall(URL_CATEGORIES, ServiceHandler.GET);

			Log.e("Response: ", "> " + json);

			if (json != null) {
				try {
					JSONObject jsonObj = new JSONObject(json);
					if (jsonObj != null) {
						JSONArray categories = jsonObj
								.getJSONArray("categories");						

						for (int i = 0; i < categories.length(); i++) {
							JSONObject catObj = (JSONObject) categories.get(i);
							Category cat = new Category(catObj.getInt("id"),
									catObj.getString("name"));
							categoriesList.add(cat);
						}
					}

				} catch (JSONException e) {
					e.printStackTrace();
				}

			} else {
				Log.e("JSON Data", "Didn't receive any data from server!");
			}

			return null;
		}

		@Override
		protected void onPostExecute(Void result) {
			super.onPostExecute(result);
			if (pDialog.isShowing())
				pDialog.dismiss();
			//populateSpinner();
		}

	}

	/**
	 * Async task to create a new food category
	 * */
	private class AddNewCategory extends AsyncTask<String, Void, Void> {

		boolean isNewCategoryCreated = false;

		@Override
		protected void onPreExecute() {
			super.onPreExecute();
			pDialog = new ProgressDialog(MainActivity.this);
			pDialog.setMessage("Creating new category..");
			pDialog.setCancelable(false);
			pDialog.show();

		}

		@Override
		protected Void doInBackground(String... arg) {

			String newCategory = arg[0];

			// Preparing post params
			List<NameValuePair> params = new ArrayList<NameValuePair>();
			params.add(new BasicNameValuePair("name", newCategory));

			ServiceHandler serviceClient = new ServiceHandler();

			String json = serviceClient.makeServiceCall(URL_NEW_CATEGORY,
					ServiceHandler.POST, params);

			Log.d("Create Response: ", "> " + json);

			if (json != null) {
				try {
					JSONObject jsonObj = new JSONObject(json);
					boolean error = jsonObj.getBoolean("error");
					// checking for error node in json
					if (!error) {	
						// new category created successfully
						isNewCategoryCreated = true;
					} else {
						Log.e("Create Category Error: ", "> " + jsonObj.getString("message"));
					}

				} catch (JSONException e) {
					e.printStackTrace();
				}

			} else {
				Log.e("JSON Data", "Didn't receive any data from server!");
			}

			return null;
		}

		@Override
		protected void onPostExecute(Void result) {
			super.onPostExecute(result);
			if (pDialog.isShowing())
				pDialog.dismiss();
			if (isNewCategoryCreated) {
				runOnUiThread(new Runnable() {
					@Override
					public void run() {
						// fetching all categories
						new GetCategories().execute();
					}
				});
			}
		}

	}

	@Override
	public void onItemSelected(AdapterView<?> parent, View view, int position,
			long id) {
		Toast.makeText(
				getApplicationContext(),
						parent.getItemAtPosition(position).toString() + " Selected" ,
				Toast.LENGTH_LONG).show();

	}

	@Override
	public void onNothingSelected(AdapterView<?> arg0) {		
	}
}

How to create REST API for Android app using PHP, Slim and MySQL – Day 1/3

$
0
0

If you are going to build an android application (it can be any other mobile platform or web too) that manages all the user data on a central database, REST API will be good architectural option to do the communication between the app and the server.

If you consider Evernote, Wunderlist apps, these apps can uninstalled at anytime and once we install them back and login, all our data will be restored. This is because all the data will stored in a cloud database and communication b/w app and database will be done using a REST API.

This tutorial gives enough knowledge about building a REST API for very beginners. As this tutorial seems lengthy, I had divided it into 3 parts. In the 1st part we learn fundamental concepts of REST and do the required setup. In the 2nd part building actual API (writing PHP & MySQL code) is covered and the 3rd part covers the process of hosting the services online i.e taking the services from localhost to a centralized server.

android rest api php mysql slim


1. Basics of REST API Design

REST architecture will be useful to build client/server network applications. REST represents Representational State Transfer. Implementing REST is very simple compared to other methods like SOAP, CORBA, WSDL etc., It basically works on HTTP protocol.

android rest api php mysql architecture

Following are the list of things should be considered while building a REST api.

» HTTP Methods
A well-designed RESTful API should support most commonly used HTTP methods (GET, POST, PUT and DELETE). There are other HTTP methods like OPTIONS, HEAD but these are used most often. Each method should be used depending on the type of operation you are performing.

GETTo fetch a resource
POSTTo create a new resource
PUTTo update existing resource
DELETETo delete a resource

» HTTP Status Code
HTTP status codes in the response body tells client application what action should be taken with the response. For an example if the response code 200, it means on the server side the request is processed successfully and you can expect updated data in the response. As well if the status code is 401, the request is not authorized. An example cause for 401 could be api key is invalid.

It is not necessary to support all HTTP status codes, but supporting at least the following codes should be good enough. Check out list of http codes from restapitutorial.com and Wikipedia

200OK
201Created
304Not Modified
400Bad Request
401Unauthorized
403Forbidden
404Not Found
422Unprocessable Entity
500Internal Server Error

» URL Structure
In REST design the URL endpoints should be well formed and should be easily understandable. Every URL for a resource should be uniquely identified. If your API needs an API key to access, the api key should be kept in HTTP headers instead of including it in URL.

For an example:
GET http://abc.com/v1/tasks/11 – Will give the details of a task whose id is 11

POST http://abc.com/v1/tasks – Will create a new task


» API Versioning
There is a huge discussion on API versioning whether to maintain api version in the URL or in the HTTP request headers. Even though it is recommended that version should be included in the request headers, I feel comfortable to maintain it in the URL itself as it is very convenient on the client side to migrate from one version to another.

Example:

http://abc.com/v1/tasks

http://abc.com/v2/tasks



» Content Type
The Content Type in HTTP headers specifies the kind of the data should be transferred between server and client. Depending upon the data your API supporting you need to set the content type.

For an example, JSON Mime type should be Content-Type: application/json, for XML Content-Type: application/xml. You can find list of supported MIME Types here


» API Key
If you are building a private API where you want to restrict the access or limit to a private access, the best approach is to secure your API using an API key. This article Designing a Secure REST (Web) API without OAuth by Riyad Kalla covers the best way to secure you rest api. But as this article aims at very beginners I am not going with any complex model. So for now we can go with generating a random api key for every user. The user is identified by the api key and all the actions can be performed only on the resources belongs to him.

The API key should be kept in request header Authorization filed instead of passing via url.

Authorization: bf45c093e542f057caee68c47787e7d6



More Knowledge on REST API Design
Following links will explains you the best practices of REST and other principles.
1. RESTful Web services: The basics
2. Stackoverflow discussion
3. A video presentation about REST+JSON API Design – Best Practices for Developers by Les Hazlewood, Stormpath


2. Prerequisite

Before diving deep into this article, it is recommended that you have basic knowledge on PHP, MySQL, JSON parsing and Android PHP, MySQL communication. Go through following links to get basic knowledge.

1. PHP Basics
2. MySQL Prepared Statements
3. Android JSON Parsing
4. How to connect Android with PHP, MySQL


3. Slim PHP Micro Framework

Instead of start developing a fresh REST framework from scratch, it is better go with a already proven framework. Then I came across Slim framework and selected it for the following reasons.

1. It is very light weight, clean and a beginner can easily understand the framework.
2. Supports all HTTP methods GET, POST, PUT and DELETE which are necessary for a REST API.
3. More importantly it provides a middle layer architecture which will be useful to filter the requests. In our case we can use it for verifying the API Key.

Downloading Slim Framework
Download the Slim framework from here (download the stable release) and keep it aside. We are gonna need this some point later after doing required setup.

4. Installing WAMP Server (Apache, PHP and MySQL)

WAMP lets you install Apache, PHP and MySQL with a single installer which reduces burden of installing & configuring them separately. Alternatively you can use XAMP, LAMP (on Linux) and MAMP (on MAC). WAMP also provides you phpmyadmin to easily interact with MySQL database.

Download & install WAMP from http://www.wampserver.com/en/. Choose the correct version which suits your operating system (32bit or 64bit). Once you have installed it, open the program from Start -> All Programs -> Wamp Server -> Start WampServer.

Open http://localhost/ and http://localhost/phpmyadmin/ to verify WAMP is installed successfully or not.


5. Installing Chrome Advanced REST client extension for Testing

Chrome Advanced REST client extension provides an easy way to test the REST API. It provides lot of options like adding request headers, adding request parameters, changing HTTP method while hitting an url. Install Advanced REST client extension in chrome browser. Once you installed it you can find it in chrome Apps or an icon at the top right corner.

Alternatively if you prefer using firefox, you can go for Poster add-on to test the API.


6. REST API for Task Manager App

To demonstrate REST API I am considering an example of Task Manager App with very minimal functionalities.
1. User related operations like registration and login
2. Task related operations like creating, reading, updating and deleting task. All task related API calls should include API key in Authorization header field.

Following are the list of API calls we are going to build in this tutorial. You can notice that same url endpoint is used for multiple api calls, but the difference is the type of HTTP method we use to hit the url. Suppose if we hit /tasks with POST method, a newer task will be created. As well if we hit /tasks with GET method, all the tasks will be listed.

API Url Structure
URLMethodParametersDescription
/registerPOSTname, email, passwordUser registration
/loginPOSTemail, passwordUser login
/tasksPOSTtaskTo create new task
/tasksGETFetching all tasks
/tasks/:idGETFetching single task
/tasks/:idPUTUpdating single task
/tasks/:idDELETEtask, statusDeleting single task


7. Creating MySQL Database

For this app we don’t need a complex database design. All we need at this stage is only three tables. You can always add few more tables if you want to extend the functionality. I have created three tables users, tasks and user_tasks.
users – All user related data will be stored here. A row will inserted when a new user register in our app.
tasks – All user tasks data will be stored in this table
user_tasks – Table used to store the relation between user and his tasks. Basically we store users id and task id in this table.

android rest api mysql database design

Open the phpmyadmin from http://localhost/phpmyadmin and execute the following SQL queries. As well if you are familiar with phpmyadmin, you can use phpmyadmin graphical interface to create tables.

CREATE DATABASE task_manager;

USE task_manager;

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(250) DEFAULT NULL,
  `email` varchar(255) NOT NULL,
  `password_hash` text NOT NULL,
  `api_key` varchar(32) NOT NULL,
  `status` int(1) NOT NULL DEFAULT '1',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`)
);

CREATE TABLE IF NOT EXISTS `tasks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `task` text NOT NULL,
  `status` int(1) NOT NULL DEFAULT '0',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
);

CREATE TABLE IF NOT EXISTS `user_tasks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `task_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`),
  KEY `task_id` (`task_id`)
);

ALTER TABLE  `user_tasks` ADD FOREIGN KEY (  `user_id` ) REFERENCES  `task_manager`.`users` (
`id`
) ON DELETE CASCADE ON UPDATE CASCADE ;

ALTER TABLE  `user_tasks` ADD FOREIGN KEY (  `task_id` ) REFERENCES  `task_manager`.`tasks` (
`id`
) ON DELETE CASCADE ON UPDATE CASCADE ;

After executing these queries go through each tables and make sure that everything created correctly.

Until now we are done with getting your system ready for development. The next article How to create REST API for Android app using PHP, Slim and MySQL – Day &frac23; covers the overall process of starting and finishing the PHP and MySQL project.

Android working with Volley Library

$
0
0

Android volley is a networking library was introduced to make networking calls much easier, faster without writing tons of code. By default all the volley network calls works asynchronously, so we don’t have to worry about using asynctask anymore.

Volley comes with lot of features. Some of them are

1. Request queuing and prioritization
2. Effective request cache and memory management
3. Extensibility and customization of the library to our needs
4. Cancelling the requests

Before getting into this tutorial, I suggested you to view the below presentation by Ficus Kirkpatrick at Google I/O to get an overview of volley.

android volley library tutorial


1. Downloading & making volley.jar

We will start with installing tools required to clone and build volley project. For this we need git (to clone the project) and ant (to build) tools.

1.1 Installing Git
Git software is used to clone git projects into your local workspace. Download & install git, once installed run git command in terminal just to make sure that it is accessible via command line. If you are getting git command not found error, add the git installation directory to environmental variables.

1.2 Installing apache ant
Apache ant is a command-line tool used to build the source code. Download ant from https://ant.apache.org/bindownload.cgi and add the bin path to environmental variables. You should able to execute ant command too in terminal.

1.3 Cloning volley library
Open command prompt, navigate to a location where you want to clone volley and execute following command. This will download a copy of volley library into your local drive.

git clone https://android.googlesource.com/platform/frameworks/volley
android cloning volley library

1.4 Making volley.jar
You can use the volley as a library project to your main project or you can simply generate volley.jar and paste it in project libs folder. To generate volley.jar, move into volley dir (cd volley) and execute below commands.

android update project -p .

ant jar

You can find generated volley.jar in volley bin folder.

android apache ant building volley
android building volley.jar

The following video will show you the process of building volley project.

If you are unsuccessful in building the volley, meanwhile you can download volley.jar here.

2. Adding volley.jar to your project

In Eclipse create a new project by navigating to File ⇒ New ⇒ Android Application Project and fill required details. Once the project is created paste the volley.jar in libs folder.


3. Creating Volley Singleton class

The best way to maintain volley core objects and request queue is, making them global by creating a singleton class which extends Application object. In your project create a class name AppController.java and extend the class from Application and add the following code.

package info.androidhive.volleyexamples.app;

import info.androidhive.volleyexamples.volley.utils.LruBitmapCache;
import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

public class AppController extends Application {

	public static final String TAG = AppController.class
			.getSimpleName();

	private RequestQueue mRequestQueue;
	private ImageLoader mImageLoader;

	private static AppController mInstance;

	@Override
	public void onCreate() {
		super.onCreate();
		mInstance = this;
	}

	public static synchronized AppController getInstance() {
		return mInstance;
	}

	public RequestQueue getRequestQueue() {
		if (mRequestQueue == null) {
			mRequestQueue = Volley.newRequestQueue(getApplicationContext());
		}

		return mRequestQueue;
	}

	public ImageLoader getImageLoader() {
		getRequestQueue();
		if (mImageLoader == null) {
			mImageLoader = new ImageLoader(this.mRequestQueue,
					new LruBitmapCache());
		}
		return this.mImageLoader;
	}

	public <T> void addToRequestQueue(Request<T> req, String tag) {
		// set the default tag if tag is empty
		req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
		getRequestQueue().add(req);
	}

	public <T> void addToRequestQueue(Request<T> req) {
		req.setTag(TAG);
		getRequestQueue().add(req);
	}

	public void cancelPendingRequests(Object tag) {
		if (mRequestQueue != null) {
			mRequestQueue.cancelAll(tag);
		}
	}
}

Create another class named LruBitmapCache.java and paste the below code. This class is required to handle image cache.

package info.androidhive.volleyexamples.volley.utils;

import com.android.volley.toolbox.ImageLoader.ImageCache;

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class LruBitmapCache extends LruCache<String, Bitmap> implements
		ImageCache {
	public static int getDefaultLruCacheSize() {
		final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
		final int cacheSize = maxMemory / 8;

		return cacheSize;
	}

	public LruBitmapCache() {
		this(getDefaultLruCacheSize());
	}

	public LruBitmapCache(int sizeInKiloBytes) {
		super(sizeInKiloBytes);
	}

	@Override
	protected int sizeOf(String key, Bitmap value) {
		return value.getRowBytes() * value.getHeight() / 1024;
	}

	@Override
	public Bitmap getBitmap(String url) {
		return get(url);
	}

	@Override
	public void putBitmap(String url, Bitmap bitmap) {
		put(url, bitmap);
	}
}

Open AndroidManifest.xml and add this singleton class in <application> tag using android:name property to execute this class automatically whenever app launches. Also add INTERNET permission as we are going to make network calls.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.volleyexamples"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name="info.androidhive.volleyexamples.app.AppController"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <!-- all activities and other stuff -->
    </application>

</manifest>


4. Making JSON request

Volley provides an easy to make json requests. If you are expecting json object in the response, you should use JsonObjectRequest class or if the response is json array, JsonArrayRequest class should be used.

4.1 Making json object request
Following code will make a json object request where the json response will start with object notation ‘{

// Tag used to cancel the request
String tag_json_obj = "json_obj_req";

String url = "http://api.androidhive.info/volley/person_object.json";
		
ProgressDialog pDialog = new ProgressDialog(this);
pDialog.setMessage("Loading...");
pDialog.show();		
		
		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.GET,
				url, null,
				new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG, response.toString());
						pDialog.hide();
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						// hide the progress dialog
						pDialog.hide();
					}
				});

// Adding request to request queue
AppController.getInstance().addToRequestQueue(jsonObjReq, tag_json_obj);



4.2 Making json array request
Following will make json array request where the json response starts with array notation ‘[

// Tag used to cancel the request
String tag_json_arry = "json_array_req";

String url = "http://api.androidhive.info/volley/person_array.json";
		
ProgressDialog pDialog = new ProgressDialog(this);
pDialog.setMessage("Loading...");
pDialog.show();		
		
JsonArrayRequest req = new JsonArrayRequest(url,
				new Response.Listener<JSONArray>() {
					@Override
					public void onResponse(JSONArray response) {
						Log.d(TAG, response.toString());		
						pDialog.hide();				
					}
				}, new Response.ErrorListener() {
					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						pDialog.hide();
					}
				});

// Adding request to request queue
AppController.getInstance().addToRequestQueue(req, tag_json_arry);


5. Making String request

StringRequest class will be used to fetch any kind of string data. The response can be json, xml, html or plain text.

// Tag used to cancel the request
String  tag_string_req = "string_req";

String url = "http://api.androidhive.info/volley/string_response.html";
		
ProgressDialog pDialog = new ProgressDialog(this);
pDialog.setMessage("Loading...");
pDialog.show();		
		
StringRequest strReq = new StringRequest(Method.GET,
				url, new Response.Listener<String>() {

					@Override
					public void onResponse(String response) {
						Log.d(TAG, response.toString());
						pDialog.hide();

					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						pDialog.hide();
					}
				});

// Adding request to request queue
AppController.getInstance().addToRequestQueue(strReq, tag_string_req);


6. Adding post parameters

It is obvious that sometimes we need to submit request parameters while hitting the url. To do that we have to override getParams() method which should return list of parameters to be send in a key value format.

If you observe below example, I am submitting name, email and password as request parameters.

// Tag used to cancel the request
String tag_json_obj = "json_obj_req";

String url = "http://api.androidhive.info/volley/person_object.json";
		
ProgressDialog pDialog = new ProgressDialog(this);
pDialog.setMessage("Loading...");
pDialog.show();		
		
		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.POST,
				url, null,
				new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG, response.toString());
						pDialog.hide();
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						pDialog.hide();
					}
				}) {

			@Override
			protected Map<String, String> getParams() {
				Map<String, String> params = new HashMap<String, String>();
				params.put("name", "Androidhive");
				params.put("email", "abc@androidhive.info");
				params.put("password", "password123");

				return params;
			}

		};

// Adding request to request queue
AppController.getInstance().addToRequestQueue(jsonObjReq, tag_json_obj);


7. Adding request headers

Just like adding request parameters, to send request headers, we have to override getHeaders(). In below example I am sending Content-Type and apiKey in request headers.

// Tag used to cancel the request
String tag_json_obj = "json_obj_req";

String url = "http://api.androidhive.info/volley/person_object.json";
		
ProgressDialog pDialog = new ProgressDialog(this);
pDialog.setMessage("Loading...");
pDialog.show();		
		
		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.POST,
				url, null,
				new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG, response.toString());
						pDialog.hide();
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						pDialog.hide();
					}
				}) {

			/**
			 * Passing some request headers
			 * */
			@Override
			public Map<String, String> getHeaders() throws AuthFailureError {
				HashMap<String, String> headers = new HashMap<String, String>();
				headers.put("Content-Type", "application/json");
				headers.put("apiKey", "xxxxxxxxxxxxxxx");
				return headers;
			}

		};

// Adding request to request queue
AppController.getInstance().addToRequestQueue(jsonObjReq, tag_json_obj);


8. Making Image request

Volley introduced custom image view element called NetworkImageView to display the images from an URL. Previously downloading images and maintaining caches is a tough job. Now using volley this can be done with very few lines of code.

8.1 Loading image in NetworkImageView
Following will load an image from an URL into NetworkImageView.

ImageLoader imageLoader = AppController.getInstance().getImageLoader();

// If you are using NetworkImageView
imgNetWorkView.setImageUrl(Const.URL_IMAGE, imageLoader);



8.2 Loading image in ImageView
If you want to load image into ImageView instead of NetworkImageView, you can do that too as mentioned below. Here we will have success and error callbacks, you have to take appropriate action depending on the need. Below in onResponse() method using response.getBitmap() I am loading bitmap into an ImageView.

		ImageLoader imageLoader = AppController.getInstance().getImageLoader();

		// If you are using normal ImageView
		imageLoader.get(Const.URL_IMAGE, new ImageListener() {

			@Override
			public void onErrorResponse(VolleyError error) {
				Log.e(TAG, "Image Load Error: " + error.getMessage());
			}

			@Override
			public void onResponse(ImageContainer response, boolean arg1) {
				if (response.getBitmap() != null) {
					// load image into imageview
					imageView.setImageBitmap(response.getBitmap());
				}
			}
		});



8.3 Defining placeholder image and error image
Here is another way of displaying image into ImageView with the option of placeholder for loader and error. The loader placeholder will be displayed until the image gets downloaded. If the image fails to download, the error placeholder will be displayed.

// Loading image with placeholder and error image
imageLoader.get(Const.URL_IMAGE, ImageLoader.getImageListener(
				imageView, R.drawable.ico_loading, R.drawable.ico_error));


9. Handling the volley Cache

Volley comes with powerful cache mechanism to maintain request cache. This saves lot of internet bandwidth and reduces user waiting time. Following are few example of using volley cache methods.

9.1 Loading request from cache
Like below you can check for a cached response of an URL before making a network call.

Cache cache = AppController.getInstance().getRequestQueue().getCache();
Entry entry = cache.get(url);
if(entry != null){
	try {
		String data = new String(entry.data, "UTF-8");
		// handle data, like converting it to xml, json, bitmap etc.,
	} catch (UnsupportedEncodingException e) {		
		e.printStackTrace();
		}
	}
}else{
	// Cached response doesn't exists. Make network call here
}



9.2 Invalidate cache
Invalidate means we are invalidating the cached data instead of deleting it. Volley will still uses the cached object until the new data received from server. Once it receives the response from the server it will override the older cached response.

AppController.getInstance().getRequestQueue().getCache().invalidate(url, true);



9.3 Turning off cache
If you want disable the cache for a particular url, you can use setShouldCache() method as below.

// String request
StringRequest stringReq = new StringRequest(....);

// disable cache
stringReq.setShouldCache(false);



9.4 Deleting cache for particular URL
Use remove() to delete cache of an URL.

AppController.getInstance().getRequestQueue().getCache().remove(url);



9.5 Deleting all the cache
Followoing will delete the cache for all the URLs.

AppController.getInstance().getRequestQueue().getCache().clear(url);


10. Cancelling requests

If you notice addToRequestQueue(request, tag) method, it accepts two parameters. One is request object and other is request tag. This tag will be used to identify the request while cancelling it. If the tag is same for multiple requests, all the requests will be cancelled. cancellAll() method is used to cancel any request.


10.1 Cancel single request
Following will cancel all the request with the tag named “feed_request”

String tag_json_arry = "json_req";
ApplicationController.getInstance().getRequestQueue().cancelAll("feed_request");

10.2 Cancel all requests
If you don’t pass any tag to cancelAll() method, it will cancel the request in request queue.

ApplicationController.getInstance().getRequestQueue().cancelAll();


11. Request prioritization

If you are making multiple request at the same time, you can prioritize the requests those you want be executed first. The priory can be Normal, Low, Immediate and High.

private Priority priority = Priority.HIGH;

StringRequest strReq = new StringRequest(Method.GET,
				Const.URL_STRING_REQ, new Response.Listener<String>() {

					@Override
					public void onResponse(String response) {
						Log.d(TAG, response.toString());
						msgResponse.setText(response.toString());
						hideProgressDialog();

					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						hideProgressDialog();
					}
				}) {
			@Override
			public Priority getPriority() {
				return priority;
			}

		};


Missing! Making XML request

As of now volley doesn’t provided any native classes to make XML requests, but this can be achieved by building a custom xml wrapper class by utilizing volley’s customization capabilities. The part of writing xml parser using volley will be covered in upcoming tutorial.

I have given a sample project covering the scenarios explained in this tutorial. Download it and let’s discuss the queries if you have any in the comments section :)

Android Facebook like Custom ListView Feed using Volley

$
0
0

My previous tutorial Android Custom ListView covers customizing android list view with image and text. In this tutorial I am going to explain even more customized list view like Facebook, Google+ news feed where it contains multiple images and texts.

I used android volley network library to make http calls and download the images. As volley comes with powerful image caching mechanism, we don’t have to much worry about caching the requests and images.

android facebook like custom news feed view


Final Output

Following is the screenshot of final output of this tutorial.

android facebook like feed view


Example feed Json

To demonstrate this tutorial I created an example json feed which contains an array of json feed object. Each object defines single feed row where it contains information like feed id, profile pic, profile name, timestamp, status message and feed image.

You can access the json from here.

{
    "feed": [
        {
            "id": 1,
            "name": "National Geographic Channel",
            "image": "http://api.androidhive.info/feed/img/cosmos.jpg",
            "status": "\"Science is a beautiful and emotional human endeavor,\" says Brannon Braga, executive producer and director. \"And Cosmos is all about making science an experience.\"",
            "profilePic": "http://api.androidhive.info/feed/img/nat.jpg",
            "timeStamp": "1403375851930",
            "url": null
        },
        {
            "id": 2,
            "name": "TIME",
            "image": "http://api.androidhive.info/feed/img/time_best.jpg",
            "status": "30 years of Cirque du Soleil's best photos",
            "profilePic": "http://api.androidhive.info/feed/img/time.png",
            "timeStamp": "1403375851930",
            "url": "http://ti.me/1qW8MLB"
        }
    ]
}

In real world scenario, this feed json should be generated dynamically by reading a database.


1. Planning the Layout

Before start writing the code, I would like to plan the layout first. If you observe the feed view, it has information like name,timestamp, profile pic, feed image and url. For this we need TextView, ImageView and a listview to display the feed. Using the LinearLayout’s orientation property I have alligned the elements vertically and horizontally.

android facebook like list view layout


2. Downloading Volley.jar

If you are new to android volley library, I suggest you go through my previous article Android Volley Tutorial to understand what volley library is actually for. (In simple words volley is a networking library used to make HTTP calls)

Download the volley.jar and keep it aside.


Now let’s start by creating a new project.

3. Creating new project

1. Create a new project in Eclipse from File ⇒ New ⇒ Android Application Project and fill all the required information.

2. Open res ⇒ values ⇒ dimens.xml and add following dimensions. If you don’t have dimens.xml, create a new file and add these values.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    
    
    <dimen name="feed_item_margin">10dp</dimen>
    <dimen name="feed_item_padding_top_bottom">20dp</dimen>
    <dimen name="feed_item_padding_left_right">15dp</dimen>
    <dimen name="feed_item_profile_pic">50dp</dimen>
    <dimen name="feed_item_profile_info_padd">10dp</dimen>
    <dimen name="feed_item_profile_name">15dp</dimen>
    <dimen name="feed_item_timestamp">13dp</dimen>
    <dimen name="feed_item_status_pad_left_right">15dp</dimen>
    <dimen name="feed_item_status_pad_top">13dp</dimen>
    <dimen name="feed_item_corner_radius">3dp</dimen>
    <dimen name="feed_item_border_width">1dp</dimen>

</resources>

3. Also create another xml file named colors.xml under res ⇒ values and add following colors.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="white">#ffffff</color>
    <color name="feed_bg">#d3d6db</color>
    <color name="feed_item_bg">#ffffff</color>
    <color name="feed_item_border">#c2c3c8</color>
    <color name="link">#0a80d1</color>
    <color name="timestamp">#a0a3a7</color>    
</resources>

4. Under res ⇒ drawable, create a new file called bg_parent_rounded_corner.xml and paste the below code. This xml will give a rounded corner background to feed item.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <!-- view background color -->
    <solid android:color="@color/feed_item_bg" >
    </solid>

    <!-- view border color and width -->
    <stroke
        android:width="@dimen/feed_item_border_width"
        android:color="@color/feed_item_border" >
    </stroke>

    <!-- Here is the corner radius -->
    <corners android:radius="@dimen/feed_item_corner_radius" >
    </corners>

</shape>

5. To keep the project well organized, I am creating required packages first. Create 4 packages named app, adapter, data and volley. To create new package, right click on src ⇒ New ⇒ Package and give package name. Example: info.androidhive.listview.app.

Following is the project structure I am trying to achieve.

facebook_feed_eclipse_project

6. Now paste the volley.jar in project libs folder.

7. Create a class named LruBitmapCache.java under volley package and add the following code. This class takes care of caching network images on disk.

package info.androidhive.listviewfeed.volley;

import com.android.volley.toolbox.ImageLoader.ImageCache;

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class LruBitmapCache extends LruCache<String, Bitmap> implements
		ImageCache {
	public static int getDefaultLruCacheSize() {
		final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
		final int cacheSize = maxMemory / 8;

		return cacheSize;
	}

	public LruBitmapCache() {
		this(getDefaultLruCacheSize());
	}

	public LruBitmapCache(int sizeInKiloBytes) {
		super(sizeInKiloBytes);
	}

	@Override
	protected int sizeOf(String key, Bitmap value) {
		return value.getRowBytes() * value.getHeight() / 1024;
	}

	@Override
	public Bitmap getBitmap(String url) {
		return get(url);
	}

	@Override
	public void putBitmap(String url, Bitmap bitmap) {
		put(url, bitmap);
	}
}

8. Under app package create class named AppController.java and paste the following content. This is a singleton class which initializes global instances of required classes. All the objects related to volley are initialized here.

package info.androidhive.listviewfeed.app;

import info.androidhive.listviewfeed.volley.LruBitmapCache;
import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

public class AppController extends Application {

	public static final String TAG = AppController.class.getSimpleName();

	private RequestQueue mRequestQueue;
	private ImageLoader mImageLoader;
	LruBitmapCache mLruBitmapCache;

	private static AppController mInstance;

	@Override
	public void onCreate() {
		super.onCreate();
		mInstance = this;
	}

	public static synchronized AppController getInstance() {
		return mInstance;
	}

	public RequestQueue getRequestQueue() {
		if (mRequestQueue == null) {
			mRequestQueue = Volley.newRequestQueue(getApplicationContext());
		}

		return mRequestQueue;
	}

	public ImageLoader getImageLoader() {
		getRequestQueue();
		if (mImageLoader == null) {
			getLruBitmapCache();
			mImageLoader = new ImageLoader(this.mRequestQueue, mLruBitmapCache);
		}

		return this.mImageLoader;
	}

	public LruBitmapCache getLruBitmapCache() {
		if (mLruBitmapCache == null)
			mLruBitmapCache = new LruBitmapCache();
		return this.mLruBitmapCache;
	}

	public <T> void addToRequestQueue(Request<T> req, String tag) {
		req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
		getRequestQueue().add(req);
	}

	public <T> void addToRequestQueue(Request<T> req) {
		req.setTag(TAG);
		getRequestQueue().add(req);
	}

	public void cancelPendingRequests(Object tag) {
		if (mRequestQueue != null) {
			mRequestQueue.cancelAll(tag);
		}
	}
}

9. Now open your AndroidManifest.xml file and add Application.java class in <application> tag. Also we need to add INTERNET permission as this app makes network calls.

    <application
        android:name="info.androidhive.listviewfeed.app.AppController"> ....</application>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.listviewfeed"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name="info.androidhive.listviewfeed.app.AppController"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="info.androidhive.listviewfeed.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

10. The main challenge in this project is adjusting the aspect ratio of feed image once it is downloaded. Unfortunately volley doesn’t provide any callback method once the NetworkImageView is loaded. So I created a custom ImageView class with callback methods. This class automatically adjusts the image height to prevent image aspect ratio distortion.

Under your project main package create a class named FeedImageView.java and paste the below code. I took the code from this stackoverflow answer and made few tweaks.

You can use this FeedImageView in your xml layout like this

<info.androidhive.listviewfeed.FeedImageView
            android:id="@+id/feedImage1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
package info.androidhive.listviewfeed;

import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;

import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageLoader.ImageContainer;
import com.android.volley.toolbox.ImageLoader.ImageListener;

public class FeedImageView extends ImageView {

	public interface ResponseObserver {
		public void onError();

		public void onSuccess();
	}

	private ResponseObserver mObserver;

	public void setResponseObserver(ResponseObserver observer) {
		mObserver = observer;
	}

	/**
	 * The URL of the network image to load
	 */
	private String mUrl;

	/**
	 * Resource ID of the image to be used as a placeholder until the network
	 * image is loaded.
	 */
	private int mDefaultImageId;

	/**
	 * Resource ID of the image to be used if the network response fails.
	 */
	private int mErrorImageId;

	/**
	 * Local copy of the ImageLoader.
	 */
	private ImageLoader mImageLoader;

	/**
	 * Current ImageContainer. (either in-flight or finished)
	 */
	private ImageContainer mImageContainer;

	public FeedImageView(Context context) {
		this(context, null);
	}

	public FeedImageView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public FeedImageView(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
	}

	/**
	 * Sets URL of the image that should be loaded into this view. Note that
	 * calling this will immediately either set the cached image (if available)
	 * or the default image specified by
	 * {@link VolleyImageView#setDefaultImageResId(int)} on the view.
	 * 
	 * NOTE: If applicable, {@link VolleyImageView#setDefaultImageResId(int)}
	 * and {@link VolleyImageView#setErrorImageResId(int)} should be called
	 * prior to calling this function.
	 * 
	 * @param url
	 *            The URL that should be loaded into this ImageView.
	 * @param imageLoader
	 *            ImageLoader that will be used to make the request.
	 */
	public void setImageUrl(String url, ImageLoader imageLoader) {
		mUrl = url;
		mImageLoader = imageLoader;
		// The URL has potentially changed. See if we need to load it.
		loadImageIfNecessary(false);
	}

	/**
	 * Sets the default image resource ID to be used for this view until the
	 * attempt to load it completes.
	 */
	public void setDefaultImageResId(int defaultImage) {
		mDefaultImageId = defaultImage;
	}

	/**
	 * Sets the error image resource ID to be used for this view in the event
	 * that the image requested fails to load.
	 */
	public void setErrorImageResId(int errorImage) {
		mErrorImageId = errorImage;
	}

	/**
	 * Loads the image for the view if it isn't already loaded.
	 * 
	 * @param isInLayoutPass
	 *            True if this was invoked from a layout pass, false otherwise.
	 */
	private void loadImageIfNecessary(final boolean isInLayoutPass) {
		final int width = getWidth();
		int height = getHeight();

		boolean isFullyWrapContent = getLayoutParams() != null
				&& getLayoutParams().height == LayoutParams.WRAP_CONTENT
				&& getLayoutParams().width == LayoutParams.WRAP_CONTENT;
		// if the view's bounds aren't known yet, and this is not a
		// wrap-content/wrap-content
		// view, hold off on loading the image.
		if (width == 0 && height == 0 && !isFullyWrapContent) {
			return;
		}

		// if the URL to be loaded in this view is empty, cancel any old
		// requests and clear the
		// currently loaded image.
		if (TextUtils.isEmpty(mUrl)) {
			if (mImageContainer != null) {
				mImageContainer.cancelRequest();
				mImageContainer = null;
			}
			setDefaultImageOrNull();
			return;
		}

		// if there was an old request in this view, check if it needs to be
		// canceled.
		if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
			if (mImageContainer.getRequestUrl().equals(mUrl)) {
				// if the request is from the same URL, return.
				return;
			} else {
				// if there is a pre-existing request, cancel it if it's
				// fetching a different URL.
				mImageContainer.cancelRequest();
				setDefaultImageOrNull();
			}
		}

		// The pre-existing content of this view didn't match the current URL.
		// Load the new image
		// from the network.
		ImageContainer newContainer = mImageLoader.get(mUrl,
				new ImageListener() {
					@Override
					public void onErrorResponse(VolleyError error) {
						if (mErrorImageId != 0) {
							setImageResource(mErrorImageId);
						}

						if (mObserver != null) {
							mObserver.onError();
						}
					}

					@Override
					public void onResponse(final ImageContainer response,
							boolean isImmediate) {
						// If this was an immediate response that was delivered
						// inside of a layout
						// pass do not set the image immediately as it will
						// trigger a requestLayout
						// inside of a layout. Instead, defer setting the image
						// by posting back to
						// the main thread.
						if (isImmediate && isInLayoutPass) {
							post(new Runnable() {
								@Override
								public void run() {
									onResponse(response, false);
								}
							});
							return;
						}

						int bWidth = 0, bHeight = 0;
						if (response.getBitmap() != null) {

							setImageBitmap(response.getBitmap());
							bWidth = response.getBitmap().getWidth();
							bHeight = response.getBitmap().getHeight();
							adjustImageAspect(bWidth, bHeight);

						} else if (mDefaultImageId != 0) {
							setImageResource(mDefaultImageId);
						}

						if (mObserver != null) {
							mObserver.onSuccess();

						}
					}
				});

		// update the ImageContainer to be the new bitmap container.
		mImageContainer = newContainer;

	}

	private void setDefaultImageOrNull() {
		if (mDefaultImageId != 0) {
			setImageResource(mDefaultImageId);
		} else {
			setImageBitmap(null);
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		loadImageIfNecessary(true);
	}

	@Override
	protected void onDetachedFromWindow() {
		if (mImageContainer != null) {
			// If the view was bound to an image request, cancel it and clear
			// out the image from the view.
			mImageContainer.cancelRequest();
			setImageBitmap(null);
			// also clear out the container so we can reload the image if
			// necessary.
			mImageContainer = null;
		}
		super.onDetachedFromWindow();
	}

	@Override
	protected void drawableStateChanged() {
		super.drawableStateChanged();
		invalidate();
	}

	/*
	 * Adjusting imageview height
	 * */
	private void adjustImageAspect(int bWidth, int bHeight) {
		LinearLayout.LayoutParams params = (LayoutParams) getLayoutParams();

		if (bWidth == 0 || bHeight == 0)
			return;

		int swidth = getWidth();
		int new_height = 0;
		new_height = swidth * bHeight / bWidth;
		params.width = swidth;
		params.height = new_height;
		setLayoutParams(params);
	}
}

11. Open your layout for main activity (activity_main.xml) and add a list view element for the feed list.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:divider="@null" />

</LinearLayout>

12. Create another layout file named feed_item.xml under res ⇒ layout folder. This layout file represents each individual feed item row in the list view.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/feed_bg"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"

        android:layout_marginLeft="@dimen/feed_item_margin"
        android:layout_marginRight="@dimen/feed_item_margin"
        android:layout_marginTop="@dimen/feed_item_margin"
        android:background="@drawable/bg_parent_rounded_corner"
        android:orientation="vertical"
        android:paddingBottom="@dimen/feed_item_padding_top_bottom"
        android:paddingTop="@dimen/feed_item_padding_top_bottom" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:paddingLeft="@dimen/feed_item_padding_left_right"
            android:paddingRight="@dimen/feed_item_padding_left_right" >

            <com.android.volley.toolbox.NetworkImageView
                android:id="@+id/profilePic"
                android:layout_width="@dimen/feed_item_profile_pic"
                android:layout_height="@dimen/feed_item_profile_pic"
                android:scaleType="fitCenter" >
            </com.android.volley.toolbox.NetworkImageView>

            <LinearLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:paddingLeft="@dimen/feed_item_profile_info_padd" >

                <TextView
                    android:id="@+id/name"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:textSize="@dimen/feed_item_profile_name"
                    android:textStyle="bold" />

                <TextView
                    android:id="@+id/timestamp"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:textColor="@color/timestamp"
                    android:textSize="@dimen/feed_item_timestamp" />
            </LinearLayout>
        </LinearLayout>

        <TextView
            android:id="@+id/txtStatusMsg"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="5dp"
            android:paddingLeft="@dimen/feed_item_status_pad_left_right"
            android:paddingRight="@dimen/feed_item_status_pad_left_right"
            android:paddingTop="@dimen/feed_item_status_pad_top" />

        <TextView
            android:id="@+id/txtUrl"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:linksClickable="true"
            android:paddingBottom="10dp"
            android:paddingLeft="@dimen/feed_item_status_pad_left_right"
            android:paddingRight="@dimen/feed_item_status_pad_left_right"
            android:textColorLink="@color/link" />

        <info.androidhive.listviewfeed.FeedImageView
            android:id="@+id/feedImage1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white"
            android:scaleType="fitXY"
            android:visibility="visible" />
    </LinearLayout>

</LinearLayout>

13. Under data package, create a class named FeedItem.java. This is a POJO class used to create objects for each feed item while parsing the json. The feed item object contains information like profile pic, name, timestamp, status message, url and feed image.

package info.androidhive.listviewfeed.data;

public class FeedItem {
	private int id;
	private String name, status, image, profilePic, timeStamp, url;

	public FeedItem() {
	}

	public FeedItem(int id, String name, String image, String status,
			String profilePic, String timeStamp, String url) {
		super();
		this.id = id;
		this.name = name;
		this.image = image;
		this.status = status;
		this.profilePic = profilePic;
		this.timeStamp = timeStamp;
		this.url = url;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getImge() {
		return image;
	}

	public void setImge(String image) {
		this.image = image;
	}

	public String getStatus() {
		return status;
	}

	public void setStatus(String status) {
		this.status = status;
	}

	public String getProfilePic() {
		return profilePic;
	}

	public void setProfilePic(String profilePic) {
		this.profilePic = profilePic;
	}

	public String getTimeStamp() {
		return timeStamp;
	}

	public void setTimeStamp(String timeStamp) {
		this.timeStamp = timeStamp;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}
}

14. Now under adapter package create a class named FeedListAdapter.java. This is a custom adapter class for feed list view. This adapter class takes care following things.

> Displaying feed data like name, timestamp, profile pic, status message and feed image.
> Converts timestamp into x minutes/hours/days ago format
> Makes URL clickable by using url.setMovementMethod(LinkMovementMethod.getInstance())

package info.androidhive.listviewfeed.adapter;

import info.androidhive.listviewfeed.FeedImageView;
import info.androidhive.listviewfeed.R;
import info.androidhive.listviewfeed.app.AppController;
import info.androidhive.listviewfeed.data.FeedItem;

import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.text.Html;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;

public class FeedListAdapter extends BaseAdapter {	
	private Activity activity;
	private LayoutInflater inflater;
	private List<FeedItem> feedItems;
	ImageLoader imageLoader = AppController.getInstance().getImageLoader();

	public FeedListAdapter(Activity activity, List<FeedItem> feedItems) {
		this.activity = activity;
		this.feedItems = feedItems;
	}

	@Override
	public int getCount() {
		return feedItems.size();
	}

	@Override
	public Object getItem(int location) {
		return feedItems.get(location);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		if (inflater == null)
			inflater = (LayoutInflater) activity
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		if (convertView == null)
			convertView = inflater.inflate(R.layout.feed_item, null);

		if (imageLoader == null)
			imageLoader = AppController.getInstance().getImageLoader();

		TextView name = (TextView) convertView.findViewById(R.id.name);
		TextView timestamp = (TextView) convertView
				.findViewById(R.id.timestamp);
		TextView statusMsg = (TextView) convertView
				.findViewById(R.id.txtStatusMsg);
		TextView url = (TextView) convertView.findViewById(R.id.txtUrl);
		NetworkImageView profilePic = (NetworkImageView) convertView
				.findViewById(R.id.profilePic);
		FeedImageView feedImageView = (FeedImageView) convertView
				.findViewById(R.id.feedImage1);

		FeedItem item = feedItems.get(position);

		name.setText(item.getName());

		// Converting timestamp into x ago format
		CharSequence timeAgo = DateUtils.getRelativeTimeSpanString(
				Long.parseLong(item.getTimeStamp()),
				System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS);
		timestamp.setText(timeAgo);

		// Chcek for empty status message
		if (!TextUtils.isEmpty(item.getStatus())) {
			statusMsg.setText(item.getStatus());
			statusMsg.setVisibility(View.VISIBLE);
		} else {
			// status is empty, remove from view
			statusMsg.setVisibility(View.GONE);
		}

		// Checking for null feed url
		if (item.getUrl() != null) {
			url.setText(Html.fromHtml("<a href=\"" + item.getUrl() + "\">"
					+ item.getUrl() + "</a> "));

			// Making url clickable
			url.setMovementMethod(LinkMovementMethod.getInstance());
			url.setVisibility(View.VISIBLE);
		} else {
			// url is null, remove from the view
			url.setVisibility(View.GONE);
		}

		// user profile pic
		profilePic.setImageUrl(item.getProfilePic(), imageLoader);

		// Feed image
		if (item.getImge() != null) {
			feedImageView.setImageUrl(item.getImge(), imageLoader);
			feedImageView.setVisibility(View.VISIBLE);
			feedImageView
					.setResponseObserver(new FeedImageView.ResponseObserver() {
						@Override
						public void onError() {
						}

						@Override
						public void onSuccess() {
						}
					});
		} else {
			feedImageView.setVisibility(View.GONE);
		}

		return convertView;
	}

}

15. Now we have all the required classes in place. Open your main activity class MainActivity.java and add the following code. Here using volley JsonObjectRequest I fetched the json, parsed it(created array of feed item objects) and passed the data to list view adapter.

package info.androidhive.listviewfeed;

import info.androidhive.listviewfeed.adapter.FeedListAdapter;
import info.androidhive.listviewfeed.app.AppController;
import info.androidhive.listviewfeed.data.FeedItem;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.Menu;
import android.widget.ListView;

import com.android.volley.Cache;
import com.android.volley.Cache.Entry;
import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonObjectRequest;

public class MainActivity extends Activity {
	private static final String TAG = MainActivity.class.getSimpleName();
	private ListView listView;
	private FeedListAdapter listAdapter;
	private List<FeedItem> feedItems;
	private String URL_FEED = "http://api.androidhive.info/feed/feed.json";

	@SuppressLint("NewApi")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		listView = (ListView) findViewById(R.id.list);

		feedItems = new ArrayList<FeedItem>();

		listAdapter = new FeedListAdapter(this, feedItems);
		listView.setAdapter(listAdapter);
		
		// These two lines not needed,
		// just to get the look of facebook (changing background color & hiding the icon)
		getActionBar().setBackgroundDrawable(new ColorDrawable(Color.parseColor("#3b5998")));
		getActionBar().setIcon(
				   new ColorDrawable(getResources().getColor(android.R.color.transparent)));

		// We first check for cached request
		Cache cache = AppController.getInstance().getRequestQueue().getCache();
		Entry entry = cache.get(URL_FEED);
		if (entry != null) {
			// fetch the data from cache
			try {
				String data = new String(entry.data, "UTF-8");
				try {
					parseJsonFeed(new JSONObject(data));
				} catch (JSONException e) {
					e.printStackTrace();
				}
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}

		} else {
			// making fresh volley request and getting json
			JsonObjectRequest jsonReq = new JsonObjectRequest(Method.GET,
					URL_FEED, null, new Response.Listener<JSONObject>() {

						@Override
						public void onResponse(JSONObject response) {
							VolleyLog.d(TAG, "Response: " + response.toString());
							if (response != null) {
								parseJsonFeed(response);
							}
						}
					}, new Response.ErrorListener() {

						@Override
						public void onErrorResponse(VolleyError error) {
							VolleyLog.d(TAG, "Error: " + error.getMessage());
						}
					});

			// Adding request to volley request queue
			AppController.getInstance().addToRequestQueue(jsonReq);
		}

	}

	/**
	 * Parsing json reponse and passing the data to feed view list adapter
	 * */
	private void parseJsonFeed(JSONObject response) {
		try {
			JSONArray feedArray = response.getJSONArray("feed");

			for (int i = 0; i < feedArray.length(); i++) {
				JSONObject feedObj = (JSONObject) feedArray.get(i);

				FeedItem item = new FeedItem();
				item.setId(feedObj.getInt("id"));
				item.setName(feedObj.getString("name"));

				// Image might be null sometimes
				String image = feedObj.isNull("image") ? null : feedObj
						.getString("image");
				item.setImge(image);
				item.setStatus(feedObj.getString("status"));
				item.setProfilePic(feedObj.getString("profilePic"));
				item.setTimeStamp(feedObj.getString("timeStamp"));

				// url might be null sometimes
				String feedUrl = feedObj.isNull("url") ? null : feedObj
						.getString("url");
				item.setUrl(feedUrl);

				feedItems.add(item);
			}

			// notify data changes to list adapater
			listAdapter.notifyDataSetChanged();
		} catch (JSONException e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

}

Now run the project and test it once. Make sure that your emulator/device is having internet connect before you test.

Android Custom ListView with Image and Text using Volley

$
0
0

My previous article Customized ListView with Image and Text gives you a good overview of customizing a list view which contains a thumbnail image and few text fields. All the list data will be downloaded by making a network calls. The main challenge in that tutorial is downloading the images asynchronously and caching them. Because of lack of good tools I used a third party library (it is a good library though) to download the listview data and caching the images.

Today I am going to explain the same, but this time with a different approach by using an another library called volley. By using volley you can see decent improvement in listview performance without much effort.

Android Custom ListView with Image and Text using Volley


Final Output

The list view we are going to build contains list of movies information where each list row defines single movie. The row layout is customized that contains movie poster as thumbnail image on the left and the movie title, rating, genre and release date on the right. Below is the final output.

android custom listview with image and text using volley

1. Example JSON

I am going to use this example json to load the list data. This json contains array of objects where the each object contains information like movie name, rating, genre and release date.

JSON Url: http://api.androidhive.info/json/movies.json

[
    {
        "title": "Dawn of the Planet of the Apes",
        "image": "http://api.androidhive.info/json/movies/1.jpg",
        "rating": 8.3,
        "releaseYear": 2014,
        "genre": ["Action", "Drama", "Sci-Fi"]
    },
    ....
    ....
]


2. Downloading Volley Library (volley.jar)

If you are coming across volley for the first time, I would suggest you go through my previous article Android working with Volley Library which gives you enough knowledge about volley and other things like building the volley library to generate volley.jar. It is up to you that you want to generate volley.jar on your own or just download the the volley.jar

3. Planning The Layout Design

To achieve this customized layout I choose the RelativeLayout as parent element. All the child elements are aligned using relative properties like align below, align right, align parent bottom etc.,

android custom list view design



Now let’s start by creating a new project.

4. Creating New Project

1. In Eclipse create a new project by navigating to File ⇒ New ⇒ Android Application Project and fill required details. I gave my project package name as
info.androidhive.customlistviewvolley

2. Once the project is created paste the volley.jar in libs folder.

3. I am creating required packages first just to keep the project organised. This step is optional but it is recommended. Create four packages named adapter, app, model and util. So after creating packages my project contains following.
info.androidhive.customlistviewvolley.adater
info.androidhive.customlistviewvolley.app
info.androidhive.customlistviewvolley.model
info.androidhive.customlistviewvolley.util

4. Open colors.xml under res ⇒ values and add below colors. If you don’t see colors.xml file, create a new file and add these color values.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <color name="genre">#666666</color>
    <color name="year">#888888</color>
    <color name="list_divider">#d9d9d9</color>
    <color name="list_row_start_color">#ffffff</color>
    <color name="list_row_end_color">#ffffff</color>
    <color name="list_row_hover_start_color">#ebeef0</color>
    <color name="list_row_hover_end_color">#ebeef0</color>

</resources>



5. Also add below dimensions in dimens.xml file located under res ⇒ values.

<resources>
        
    <dimen name="title">17dp</dimen>
    <dimen name="rating">15dip</dimen>
    <dimen name="genre">13dip</dimen>
    <dimen name="year">12dip</dimen>

</resources>



6. Before start writing java code, I would like to complete the UI part first. Create list_row_bg.xml, list_row_bg_hover.xml and list_row_selector.xml with below respective codes under res ⇒ drawable folder. If you don’t see drawable folder under res, create a new folder and name it as drawable.

list_row_bg.xml – Default style for list view row.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
  <gradient
      android:startColor="@color/list_row_start_color"
      android:endColor="@color/list_row_end_color"
      android:angle="270" />
</shape>

list_row_bg_hover.xml – Style for list view row when row is selected

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <gradient
        android:angle="270"
        android:endColor="@color/list_row_hover_end_color"
        android:startColor="@color/list_row_hover_start_color" />

</shape>

list_row_selector.xml – Selector file to toggle the row states.

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/list_row_bg" android:state_pressed="false" android:state_selected="false"/>
    <item android:drawable="@drawable/list_row_bg_hover" android:state_pressed="true"/>
    <item android:drawable="@drawable/list_row_bg_hover" android:state_pressed="false" android:state_selected="true"/>

</selector>



7. Now open your layout file of main activity(in my case activity_main.xml) and add a ListView element.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:divider="@color/list_divider"
        android:dividerHeight="1dp"
        android:listSelector="@drawable/list_row_selector" />

</RelativeLayout>



8. We need to create another layout file for list view row. This is the main design component in this project as we define actual custom list design here. I am naming this file as list_row.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/list_row_selector"
    android:padding="8dp" >

    <!-- Thumbnail Image -->
    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/thumbnail"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_alignParentLeft="true"
        android:layout_marginRight="8dp" />

    <!-- Movie Title -->
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/thumbnail"
        android:layout_toRightOf="@+id/thumbnail"
        android:textSize="@dimen/title"
        android:textStyle="bold" />

    <!-- Rating -->
    <TextView
        android:id="@+id/rating"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/title"
        android:layout_marginTop="1dip"
        android:layout_toRightOf="@+id/thumbnail"
        android:textSize="@dimen/rating" />
    
    <!-- Genre -->
    <TextView
        android:id="@+id/genre"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/rating"
        android:layout_marginTop="5dp"
        android:layout_toRightOf="@+id/thumbnail"
        android:textColor="@color/genre"
        android:textSize="@dimen/genre" />

    <!-- Release Year -->
    <TextView
        android:id="@+id/releaseYear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:textColor="@color/year"
        android:textSize="@dimen/year" />

</RelativeLayout>



Here we completed the design part. Let’s move to java part.

9. Create LruBitmapCache.java under util package. This class takes care of caching images on disk.

package info.androidhive.customlistviewvolley.util;

import com.android.volley.toolbox.ImageLoader.ImageCache;

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class LruBitmapCache extends LruCache<String, Bitmap> implements
		ImageCache {
	public static int getDefaultLruCacheSize() {
		final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
		final int cacheSize = maxMemory / 8;

		return cacheSize;
	}

	public LruBitmapCache() {
		this(getDefaultLruCacheSize());
	}

	public LruBitmapCache(int sizeInKiloBytes) {
		super(sizeInKiloBytes);
	}

	@Override
	protected int sizeOf(String key, Bitmap value) {
		return value.getRowBytes() * value.getHeight() / 1024;
	}

	@Override
	public Bitmap getBitmap(String url) {
		return get(url);
	}

	@Override
	public void putBitmap(String url, Bitmap bitmap) {
		put(url, bitmap);
	}
}



10. Create AppController.java under app package. This class is a singleton class which initializes core objects of volley library.

package info.androidhive.customlistviewvolley.app;

import info.androidhive.customlistviewvolley.util.LruBitmapCache;
import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

public class AppController extends Application {

	public static final String TAG = AppController.class.getSimpleName();

	private RequestQueue mRequestQueue;
	private ImageLoader mImageLoader;

	private static AppController mInstance;

	@Override
	public void onCreate() {
		super.onCreate();
		mInstance = this;
	}

	public static synchronized AppController getInstance() {
		return mInstance;
	}

	public RequestQueue getRequestQueue() {
		if (mRequestQueue == null) {
			mRequestQueue = Volley.newRequestQueue(getApplicationContext());
		}

		return mRequestQueue;
	}

	public ImageLoader getImageLoader() {
		getRequestQueue();
		if (mImageLoader == null) {
			mImageLoader = new ImageLoader(this.mRequestQueue,
					new LruBitmapCache());
		}
		return this.mImageLoader;
	}

	public <T> void addToRequestQueue(Request<T> req, String tag) {
		// set the default tag if tag is empty
		req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
		getRequestQueue().add(req);
	}

	public <T> void addToRequestQueue(Request<T> req) {
		req.setTag(TAG);
		getRequestQueue().add(req);
	}

	public void cancelPendingRequests(Object tag) {
		if (mRequestQueue != null) {
			mRequestQueue.cancelAll(tag);
		}
	}
}



11. Now add the AppController.java class in AndroidManifest.xml to your <application> tag using name property to execute this class on application start. Also add INTERNET permission as we are going to make network calls.

<application
        android:name="info.androidhive.customlistviewvolley.app.AppController" ../>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.customlistviewvolley"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="18" />

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name="info.androidhive.customlistviewvolley.app.AppController"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="info.androidhive.customlistviewvolley.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>



12. Now create Movie.java under model package. This model class will be used to provide movie objects data to list view after parsing the json.

package info.androidhive.customlistviewvolley.model;

import java.util.ArrayList;

public class Movie {
	private String title, thumbnailUrl;
	private int year;
	private double rating;
	private ArrayList<String> genre;

	public Movie() {
	}

	public Movie(String name, String thumbnailUrl, int year, double rating,
			ArrayList<String> genre) {
		this.title = name;
		this.thumbnailUrl = thumbnailUrl;
		this.year = year;
		this.rating = rating;
		this.genre = genre;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String name) {
		this.title = name;
	}

	public String getThumbnailUrl() {
		return thumbnailUrl;
	}

	public void setThumbnailUrl(String thumbnailUrl) {
		this.thumbnailUrl = thumbnailUrl;
	}

	public int getYear() {
		return year;
	}

	public void setYear(int year) {
		this.year = year;
	}

	public double getRating() {
		return rating;
	}

	public void setRating(double rating) {
		this.rating = rating;
	}

	public ArrayList<String> getGenre() {
		return genre;
	}

	public void setGenre(ArrayList<String> genre) {
		this.genre = genre;
	}

}



13. Create a class named CustomListAdapter.java with below code. This is a custom list adapter class which provides data to list view. In other words it renders the layout_row.xml in list by pre-filling appropriate information.

package info.androidhive.customlistviewvolley.adater;

import info.androidhive.customlistviewvolley.R;
import info.androidhive.customlistviewvolley.app.AppController;
import info.androidhive.customlistviewvolley.model.Movie;

import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;

public class CustomListAdapter extends BaseAdapter {
	private Activity activity;
	private LayoutInflater inflater;
	private List<Movie> movieItems;
	ImageLoader imageLoader = AppController.getInstance().getImageLoader();

	public CustomListAdapter(Activity activity, List<Movie> movieItems) {
		this.activity = activity;
		this.movieItems = movieItems;
	}

	@Override
	public int getCount() {
		return movieItems.size();
	}

	@Override
	public Object getItem(int location) {
		return movieItems.get(location);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		if (inflater == null)
			inflater = (LayoutInflater) activity
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		if (convertView == null)
			convertView = inflater.inflate(R.layout.list_row, null);

		if (imageLoader == null)
			imageLoader = AppController.getInstance().getImageLoader();
		NetworkImageView thumbNail = (NetworkImageView) convertView
				.findViewById(R.id.thumbnail);
		TextView title = (TextView) convertView.findViewById(R.id.title);
		TextView rating = (TextView) convertView.findViewById(R.id.rating);
		TextView genre = (TextView) convertView.findViewById(R.id.genre);
		TextView year = (TextView) convertView.findViewById(R.id.releaseYear);

		// getting movie data for the row
		Movie m = movieItems.get(position);

		// thumbnail image
		thumbNail.setImageUrl(m.getThumbnailUrl(), imageLoader);
		
		// title
		title.setText(m.getTitle());
		
		// rating
		rating.setText("Rating: " + String.valueOf(m.getRating()));
		
		// genre
		String genreStr = "";
		for (String str : m.getGenre()) {
			genreStr += str + ", ";
		}
		genreStr = genreStr.length() > 0 ? genreStr.substring(0,
				genreStr.length() - 2) : genreStr;
		genre.setText(genreStr);
		
		// release year
		year.setText(String.valueOf(m.getYear()));

		return convertView;
	}

}



14. Now open your main activity class (MainActivity.java) and do the below changes. Here I created volley’s JsonArrayRequest to get the json from url. Upon parsing the json, I stored all the json data into an ArrayList as Movie objects. Finally I called notifyDataSetChanged() on CustomListAdapter instance to render the list view with updated information.

package info.androidhive.customlistviewvolley;

import info.androidhive.customlistviewvolley.adater.CustomListAdapter;
import info.androidhive.customlistviewvolley.app.AppController;
import info.androidhive.customlistviewvolley.model.Movie;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.widget.ListView;

import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonArrayRequest;

public class MainActivity extends Activity {
	// Log tag
	private static final String TAG = MainActivity.class.getSimpleName();

	// Movies json url
	private static final String url = "http://api.androidhive.info/json/movies.json";
	private ProgressDialog pDialog;
	private List<Movie> movieList = new ArrayList<Movie>();
	private ListView listView;
	private CustomListAdapter adapter;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		listView = (ListView) findViewById(R.id.list);
		adapter = new CustomListAdapter(this, movieList);
		listView.setAdapter(adapter);

		pDialog = new ProgressDialog(this);
		// Showing progress dialog before making http request
		pDialog.setMessage("Loading...");
		pDialog.show();

		// changing action bar color
		getActionBar().setBackgroundDrawable(
				new ColorDrawable(Color.parseColor("#1b1b1b")));

		// Creating volley request obj
		JsonArrayRequest movieReq = new JsonArrayRequest(url,
				new Response.Listener<JSONArray>() {
					@Override
					public void onResponse(JSONArray response) {
						Log.d(TAG, response.toString());
						hidePDialog();

						// Parsing json
						for (int i = 0; i < response.length(); i++) {
							try {

								JSONObject obj = response.getJSONObject(i);
								Movie movie = new Movie();
								movie.setTitle(obj.getString("title"));
								movie.setThumbnailUrl(obj.getString("image"));
								movie.setRating(((Number) obj.get("rating"))
										.doubleValue());
								movie.setYear(obj.getInt("releaseYear"));

								// Genre is json array
								JSONArray genreArry = obj.getJSONArray("genre");
								ArrayList<String> genre = new ArrayList<String>();
								for (int j = 0; j < genreArry.length(); j++) {
									genre.add((String) genreArry.get(j));
								}
								movie.setGenre(genre);

								// adding movie to movies array
								movieList.add(movie);

							} catch (JSONException e) {
								e.printStackTrace();
							}

						}

						// notifying list adapter about data changes
						// so that it renders the list view with updated data
						adapter.notifyDataSetChanged();
					}
				}, new Response.ErrorListener() {
					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						hidePDialog();

					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(movieReq);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		hidePDialog();
	}

	private void hidePDialog() {
		if (pDialog != null) {
			pDialog.dismiss();
			pDialog = null;
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

}

Now run the project and test it once. Please do comment below if you ran into any issue.

Android JSON parsing using Volley

$
0
0

My previous article Android JSON Parsing Tutorial explains parsing json in a simple manner which got very good feedback and good ranking in search engines. In this tutorial I want to explain the same but in a easy & robust way using volley library.

If you want to know more about Volley and it’s benefits, go through Android working with Volley Library.

android json parsing using volley



So let’s start this with a simple project.

Creating New Project

1. Create a new project in Eclipse from File ⇒ New ⇒ Android Application Project and fill all the required details.

I gave my project name as VolleyJson and package name as info.androidhive.volleyjson

2. Now create a new package under src folder by Right clicking on src ⇒ New ⇒ Package and give the package name as app. So my new package name will be info.androidhive.volleyjson.app

3. Download volley.jar and paste it in project’s libs folder.

4. Create a new class named AppController.java under app package. This is going to be a singleton class where we initialize all the volley core objects.

package info.androidhive.volleyjson.app;

import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
 
public class AppController extends Application {
 
    public static final String TAG = AppController.class.getSimpleName();
 
    private RequestQueue mRequestQueue;
 
    private static AppController mInstance;
 
    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }
 
    public static synchronized AppController getInstance() {
        return mInstance;
    }
 
    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }
 
        return mRequestQueue;
    }
 
    public <T> void addToRequestQueue(Request<T> req, String tag) {
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }
 
    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        getRequestQueue().add(req);
    }
 
    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}


5. AppController.java has to be executed when the app is launched. So add this class in your AndroidManifest.xml using name attribute for <application> tag.

Also add INTERNET permission as we need to make internet calls from the app.

<application
        android:name="info.androidhive.volleyjson.app.AppController">
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.volleyjson"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />
    
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" 
        android:name="info.androidhive.volleyjson.app.AppController">
        <activity
            android:name="info.androidhive.volleyjson.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>



6. Open the layout file of main activity and add below code. (In my case my main activity layout file is activity_main.xml).

In this layout we are adding two Buttons and a TextView. In two Button, one is to invoke json object request and other is to invoke json array request. The TextView is used to display the parsed json response.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >

    <Button
        android:id="@+id/btnObjRequest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:text="Make JSON Object Request" />

    <Button
        android:id="@+id/btnArrayRequest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:text="Make JSON Array Request"
        android:layout_below="@id/btnObjRequest" />
    
    <TextView
        android:id="@+id/txtResponse"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/btnArrayRequest"
        android:layout_marginTop="40px"
        android:padding="20dp" />

</RelativeLayout>


7. Now open main activity class MainActivity.java and add basic code like importing UI elements, adding button click events and initializing other objects.

Below you can notice two methods makeJsonObjectRequest() and makeJsonArrayRequest(). But these methods left empty for now, we’ll add code for these methods in next steps.

package info.androidhive.volleyjson;

import info.androidhive.volleyjson.R;
import info.androidhive.volleyjson.app.AppController;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.JsonObjectRequest;

public class MainActivity extends Activity {

	// json object response url
	private String urlJsonObj = "http://api.androidhive.info/volley/person_object.json";
	
	// json array response url
	private String urlJsonArry = "http://api.androidhive.info/volley/person_array.json";

	private static String TAG = MainActivity.class.getSimpleName();
	private Button btnMakeObjectRequest, btnMakeArrayRequest;

	// Progress dialog
	private ProgressDialog pDialog;

	private TextView txtResponse;

	// temporary string to show the parsed response
	private String jsonResponse;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		btnMakeObjectRequest = (Button) findViewById(R.id.btnObjRequest);
		btnMakeArrayRequest = (Button) findViewById(R.id.btnArrayRequest);
		txtResponse = (TextView) findViewById(R.id.txtResponse);

		pDialog = new ProgressDialog(this);
		pDialog.setMessage("Please wait...");
		pDialog.setCancelable(false);

		btnMakeObjectRequest.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// making json object request
				makeJsonObjectRequest();
			}
		});

		btnMakeArrayRequest.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// making json array request
				makeJsonArrayRequest();
			}
		});

	}

	/**
	 * Method to make json object request where json response starts wtih {
	 * */
	private void makeJsonObjectRequest() {
	}

	/**
	 * Method to make json array request where response starts with [
	 * */
	private void makeJsonArrayRequest() {
	}

	private void showpDialog() {
		if (!pDialog.isShowing())
			pDialog.show();
	}

	private void hidepDialog() {
		if (pDialog.isShowing())
			pDialog.dismiss();
	}
}



Normally json response will be of two types. It can be either json object or json array. If the json starts with {, it is considered to be JSON Object. As well if the json starts with [, then it is JSON Array.

Now we'll see how to make these requests individually.

Making JSON Object Request

8. Volley provides JsonObjectRequest class to make json object request. Add the below code in makeJsonObjectRequest() method. Here we are fetching the json by making a call to below url and parsing it. Finally the parsed response is appended to a string and displayed on the screen.

Sample JSON Object Response

URL: http://api.androidhive.info/volley/person_object.json

{
	"name" : "Ravi Tamada", 
	"email" : "ravi8x@gmail.com",
	"phone" : {
		"home" : "08947 000000",
		"mobile" : "9999999999"
	}
	
}
	/**
	 * Method to make json object request where json response starts wtih {
	 * */
	private void makeJsonObjectRequest() {

		showpDialog();

		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.GET,
				urlJsonObj, null, new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG, response.toString());

						try {
							// Parsing json object response
							// response will be a json object
							String name = response.getString("name");
							String email = response.getString("email");
							JSONObject phone = response.getJSONObject("phone");
							String home = phone.getString("home");
							String mobile = phone.getString("mobile");

							jsonResponse = "";
							jsonResponse += "Name: " + name + "\n\n";
							jsonResponse += "Email: " + email + "\n\n";
							jsonResponse += "Home: " + home + "\n\n";
							jsonResponse += "Mobile: " + mobile + "\n\n";

							txtResponse.setText(jsonResponse);

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									"Error: " + e.getMessage(),
									Toast.LENGTH_LONG).show();
						}
						hidepDialog();
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						// hide the progress dialog
						hidepDialog();
					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(jsonObjReq);
	}


Making JSON Array Request

9. Volley provides JsonArrayRequest class to make json array request. Add the below code in makeJsonArrayRequest() method.

Sample JSON Array Response

URL: http://api.androidhive.info/volley/person_array.json

[
	{
	"name" : "Ravi Tamada", 
	"email" : "ravi8x@gmail.com",
	"phone" : {
		"home" : "08947 000000",
		"mobile" : "9999999999"
	}
	},
	{
	"name" : "Tommy", 
	"email" : "tommy@gmail.com",
	"phone" : {
		"home" : "08946 000000",
		"mobile" : "0000000000"
	}
	}
]
	/**
	 * Method to make json array request where response starts with [
	 * */
	private void makeJsonArrayRequest() {

		showpDialog();

		JsonArrayRequest req = new JsonArrayRequest(urlJsonArry,
				new Response.Listener<JSONArray>() {
					@Override
					public void onResponse(JSONArray response) {
						Log.d(TAG, response.toString());

						try {
							// Parsing json array response
							// loop through each json object
							jsonResponse = "";
							for (int i = 0; i < response.length(); i++) {

								JSONObject person = (JSONObject) response
										.get(i);

								String name = person.getString("name");
								String email = person.getString("email");
								JSONObject phone = person
										.getJSONObject("phone");
								String home = phone.getString("home");
								String mobile = phone.getString("mobile");

								jsonResponse += "Name: " + name + "\n\n";
								jsonResponse += "Email: " + email + "\n\n";
								jsonResponse += "Home: " + home + "\n\n";
								jsonResponse += "Mobile: " + mobile + "\n\n\n";

							}

							txtResponse.setText(jsonResponse);

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									"Error: " + e.getMessage(),
									Toast.LENGTH_LONG).show();
						}

						hidepDialog();
					}
				}, new Response.ErrorListener() {
					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						hidepDialog();
					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(req);
	}

Now run the project and test it once. You should able to see the parsed json displayed on the screen upon tapping the json request buttons.

android json parsing using volley


Complete Code

Here is the complete code of MainActivity.java

package info.androidhive.volleyjson;

import info.androidhive.volleyjson.R;
import info.androidhive.volleyjson.app.AppController;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.JsonObjectRequest;

public class MainActivity extends Activity {

	// json object response url
	private String urlJsonObj = "http://api.androidhive.info/volley/person_object.json";
	
	// json array response url
	private String urlJsonArry = "http://api.androidhive.info/volley/person_array.json";

	private static String TAG = MainActivity.class.getSimpleName();
	private Button btnMakeObjectRequest, btnMakeArrayRequest;

	// Progress dialog
	private ProgressDialog pDialog;

	private TextView txtResponse;

	// temporary string to show the parsed response
	private String jsonResponse;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		btnMakeObjectRequest = (Button) findViewById(R.id.btnObjRequest);
		btnMakeArrayRequest = (Button) findViewById(R.id.btnArrayRequest);
		txtResponse = (TextView) findViewById(R.id.txtResponse);

		pDialog = new ProgressDialog(this);
		pDialog.setMessage("Please wait...");
		pDialog.setCancelable(false);

		btnMakeObjectRequest.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// making json object request
				makeJsonObjectRequest();
			}
		});

		btnMakeArrayRequest.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// making json array request
				makeJsonArrayRequest();
			}
		});

	}

	/**
	 * Method to make json object request where json response starts wtih {
	 * */
	private void makeJsonObjectRequest() {

		showpDialog();

		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.GET,
				urlJsonObj, null, new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG, response.toString());

						try {
							// Parsing json object response
							// response will be a json object
							String name = response.getString("name");
							String email = response.getString("email");
							JSONObject phone = response.getJSONObject("phone");
							String home = phone.getString("home");
							String mobile = phone.getString("mobile");

							jsonResponse = "";
							jsonResponse += "Name: " + name + "\n\n";
							jsonResponse += "Email: " + email + "\n\n";
							jsonResponse += "Home: " + home + "\n\n";
							jsonResponse += "Mobile: " + mobile + "\n\n";

							txtResponse.setText(jsonResponse);

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									"Error: " + e.getMessage(),
									Toast.LENGTH_LONG).show();
						}
						hidepDialog();
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						// hide the progress dialog
						hidepDialog();
					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(jsonObjReq);
	}

	/**
	 * Method to make json array request where response starts with [
	 * */
	private void makeJsonArrayRequest() {

		showpDialog();

		JsonArrayRequest req = new JsonArrayRequest(urlJsonArry,
				new Response.Listener<JSONArray>() {
					@Override
					public void onResponse(JSONArray response) {
						Log.d(TAG, response.toString());

						try {
							// Parsing json array response
							// loop through each json object
							jsonResponse = "";
							for (int i = 0; i < response.length(); i++) {

								JSONObject person = (JSONObject) response
										.get(i);

								String name = person.getString("name");
								String email = person.getString("email");
								JSONObject phone = person
										.getJSONObject("phone");
								String home = phone.getString("home");
								String mobile = phone.getString("mobile");

								jsonResponse += "Name: " + name + "\n\n";
								jsonResponse += "Email: " + email + "\n\n";
								jsonResponse += "Home: " + home + "\n\n";
								jsonResponse += "Mobile: " + mobile + "\n\n\n";

							}

							txtResponse.setText(jsonResponse);

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									"Error: " + e.getMessage(),
									Toast.LENGTH_LONG).show();
						}

						hidepDialog();
					}
				}, new Response.ErrorListener() {
					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						hidepDialog();
					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(req);
	}

	private void showpDialog() {
		if (!pDialog.isShowing())
			pDialog.show();
	}

	private void hidepDialog() {
		if (pDialog.isShowing())
			pDialog.dismiss();
	}
}

Android Building Group Chat App using Sockets – Part 1

$
0
0

We have seen a large number of applications come up in the recent past, to help us connect with each other across different mediums, like Hike, Whatsapp, Viber etc. You would be surprised to learn that its rather quite easy to develop one yourself. I thought providing an insight into developing such an application would be helpful for you guys.

Today we are going to learn how to build a simple group chat app using sockets. I won’t say this is the only way to build a chat app, but it is the quick & easiest way to build one. The best and efficient way would be using the push notifications instead of sockets.

android building chat app using java sockets



Overall we are going to build three components in this article. The first and important component is the socket server. The socket server plays a major role like handling the socket client connections, passing the messages between clients. Second component is the web app where you can join the chat conversation from a browser. Finally the android app. The main advantage of this app is, you can chat between web – web, web – android or android – android.

As this article seems pretty much lengthy, I am dividing the tutorial into two parts. In this first part, all the basic setup and building the web app is covered. In the 2nd Part, building the actual android app is covered.

Below are the final outcomes from this tutorial.

android building chat app like whatsapp


How the App Works Over Sockets?

If you are coming across the ‘sockets’ for the first time, the wikipedia page give you basic knowledge about socket communication. Below you can find a brief info about how our app works.

1. First we’ll have a socket server running. When the android app or web app connects to socket server, the server opens a TCP connection between server and client. The server is capable of opening concurrent connections when there are multiple clients.

2. When a socket connection is established few callback methods like onOpen, onMessage, onExit will be triggered on the both the ends (on server side and client side). There will be another method available to send message from client to server, or vice versa.

3. JSON strings will be exchanged between server and client as a communication medium. Each JSON contains a node called flag to identify the purpose of JSON message. Below is example of JSON when a client joined the conversation that contains the client name, session id and number of people online.

{
    "message": " joined conversation!",
    "flag": "new",
    "sessionId": "4",
    "name": "Ravi Tamada",
    "onlineCount": 6
}

4. Whenever a JSON message received on client side, the JSON will be parsed and appropriate action will be taken.

I hope the above information gave enough knowledge over the app. Now we can move forward and start building one by one component.


1. Eclipse adding J2EE & Tomcat 7 Support

The eclipse IDE that comes along with android SDK, doesn’t have J2EE and Tomcat server support. So we have to add J2EE and tomcat extensions. Another option would be downloading another eclipse that supports J2EE, but I would like use the eclipse that supports both android and j2ee instead of two different IDEs.

1. Download apache tomcat 7 from tomcat website. (You can download it from this direct link). Once downloaded, extract it in some location.

2. In Eclipse go to Help ⇒ Install New Software. Click on Work with drop down and select Juno – http://download.eclipse.org/releases/juno. (You might need to select the appropriate eclipse release depending upon your eclipse flavour)

3. Expand Web, XML, Java EE and OSGi Enterprise Development and select below extensions and proceed with installation.
   > Eclipse Java EE Developer Tools
   > JST Server Adapters
   > JST Server Adapters Extensions

4. Once the extensions are installed, Eclipse will prompt you to restart. When the eclipse re-opened, we need to create a server first. Goto Windows ⇒ Show View ⇒ Server ⇒ Servers.

5. In servers tab, click on new server wizard and select Apache ⇒ Tomcat v7.0 Server. Give server name, browse and select the tomcat home directory which we downloaded previously.

Check out the below video if you not very clear with the above steps.




2. Finding Your PC IP Address

As we need to test this app on multiple devices (it can be a mobile, PC or a laptop) in a wifi network, we need to know the IP address of the PC where the socket server project running. So instead of using localhost, we need to use the ip address in the url. In order to get the ip address of your machine, execute below commands in terminal.

On Windows

ipconfig
windows-os-getting-ip-address

On Mac

ifconfig
mac os getting system ip address

Note: The ip address of your machine might changes whenever you disconnected from wifi or a new device added to wifi network. So make sure that you are using the correct ip address every time you test the app.

Once the Eclipse Tomcat setup is ready and you know the IP address, you are good to go with socket server development. Building the socket server is very easy. The socket server we are going to build won’t take more than two class files.


3. Building the Socket Server

1. In Eclipse create a new Dynamic Web Project by navigating to File ⇒ New ⇒ Other ⇒ Web ⇒ Dynamic Web Project. Give the project name and select the Target runtime as Tomcat 7. I gave my project name as WebMobileGroupChatServer.

Once the project is created, it contains below directory structure.

j2ee web dynamic project directory structure

2. Right click on src ⇒ New ⇒ Package and give the package name. I gave my package name as info.androidhive.webmobilegroupchat.

3. Now download google-collections-0.8.jar, javaee-api-7.0.jar, json-org.jar files and paste them in project’s WebContent ⇒ WEB-INF ⇒ lib folder.

4. Create a new class named JSONUtils.java under project’s src package folder. This class contains methods to generate JSON strings those are required to have the communication b/w socket server and clients.

In the below code, if you observer each json contains flag node which tell the clients the purpose of JSON message. On the client side we have to take appropriate action considering the flag value.

Basically the flag contains four values.

self = This JSON contains the session information of that particular client. This will be the first json a client receives when it opens a sockets connection.

new = This JSON broadcasted to every client informing about the new client that is connected to socket server.

message = This contains the message sent by a client to server. Hence it will broadcasted to every client.

exit = The JSON informs every client about the client that is disconnected from the socket server.

package info.androidhive.webmobilegroupchat;

import org.json.JSONException;
import org.json.JSONObject;

public class JSONUtils {

	// flags to identify the kind of json response on client side
	private static final String FLAG_SELF = "self", FLAG_NEW = "new",
			FLAG_MESSAGE = "message", FLAG_EXIT = "exit";

	public JSONUtils() {
	}

	/**
	 * Json when client needs it's own session details
	 * */
	public String getClientDetailsJson(String sessionId, String message) {
		String json = null;

		try {
			JSONObject jObj = new JSONObject();
			jObj.put("flag", FLAG_SELF);
			jObj.put("sessionId", sessionId);
			jObj.put("message", message);

			json = jObj.toString();
		} catch (JSONException e) {
			e.printStackTrace();
		}

		return json;
	}

	/**
	 * Json to notify all the clients about new person joined
	 * */
	public String getNewClientJson(String sessionId, String name,
			String message, int onlineCount) {
		String json = null;

		try {
			JSONObject jObj = new JSONObject();
			jObj.put("flag", FLAG_NEW);
			jObj.put("name", name);
			jObj.put("sessionId", sessionId);
			jObj.put("message", message);
			jObj.put("onlineCount", onlineCount);

			json = jObj.toString();
		} catch (JSONException e) {
			e.printStackTrace();
		}

		return json;
	}

	/**
	 * Json when the client exits the socket connection
	 * */
	public String getClientExitJson(String sessionId, String name,
			String message, int onlineCount) {
		String json = null;

		try {
			JSONObject jObj = new JSONObject();
			jObj.put("flag", FLAG_EXIT);
			jObj.put("name", name);
			jObj.put("sessionId", sessionId);
			jObj.put("message", message);
			jObj.put("onlineCount", onlineCount);

			json = jObj.toString();
		} catch (JSONException e) {
			e.printStackTrace();
		}

		return json;
	}

	/**
	 * JSON when message needs to be sent to all the clients
	 * */
	public String getSendAllMessageJson(String sessionId, String fromName,
			String message) {
		String json = null;

		try {
			JSONObject jObj = new JSONObject();
			jObj.put("flag", FLAG_MESSAGE);
			jObj.put("sessionId", sessionId);
			jObj.put("name", fromName);
			jObj.put("message", message);

			json = jObj.toString();

		} catch (JSONException e) {
			e.printStackTrace();
		}

		return json;
	}
}



5. Create another class named SocketServer.java and add the below code. This is the where we implement actual socket server.

This class mainly contains four callback methods.

onOpen() – This method is called when a new socket client connects.
onMessage() – This method is called when a new message received from the client.
onClose() – This method is called when a socket client disconnected from the server.
sendMessageToAll() – This method is used to broadcast a message to all socket clients.

package info.androidhive.webmobilegroupchat;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.json.JSONException;
import org.json.JSONObject;

import com.google.common.collect.Maps;

@ServerEndpoint("/chat")
public class SocketServer {

	// set to store all the live sessions
	private static final Set<Session> sessions = Collections
			.synchronizedSet(new HashSet<Session>());

	// Mapping between session and person name
	private static final HashMap<String, String> nameSessionPair = new HashMap<String, String>();

	private JSONUtils jsonUtils = new JSONUtils();

	// Getting query params
	public static Map<String, String> getQueryMap(String query) {
		Map<String, String> map = Maps.newHashMap();
		if (query != null) {
			String[] params = query.split("&");
			for (String param : params) {
				String[] nameval = param.split("=");
				map.put(nameval[0], nameval[1]);
			}
		}
		return map;
	}

	/**
	 * Called when a socket connection opened
	 * */
	@OnOpen
	public void onOpen(Session session) {

		System.out.println(session.getId() + " has opened a connection");

		Map<String, String> queryParams = getQueryMap(session.getQueryString());

		String name = "";

		if (queryParams.containsKey("name")) {

			// Getting client name via query param
			name = queryParams.get("name");
			try {
				name = URLDecoder.decode(name, "UTF-8");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}

			// Mapping client name and session id
			nameSessionPair.put(session.getId(), name);
		}

		// Adding session to session list
		sessions.add(session);

		try {
			// Sending session id to the client that just connected
			session.getBasicRemote().sendText(
					jsonUtils.getClientDetailsJson(session.getId(),
							"Your session details"));
		} catch (IOException e) {
			e.printStackTrace();
		}

		// Notifying all the clients about new person joined
		sendMessageToAll(session.getId(), name, " joined conversation!", true,
				false);

	}

	/**
	 * method called when new message received from any client
	 * 
	 * @param message
	 *            JSON message from client
	 * */
	@OnMessage
	public void onMessage(String message, Session session) {

		System.out.println("Message from " + session.getId() + ": " + message);

		String msg = null;

		// Parsing the json and getting message
		try {
			JSONObject jObj = new JSONObject(message);
			msg = jObj.getString("message");
		} catch (JSONException e) {
			e.printStackTrace();
		}

		// Sending the message to all clients
		sendMessageToAll(session.getId(), nameSessionPair.get(session.getId()),
				msg, false, false);
	}

	/**
	 * Method called when a connection is closed
	 * */
	@OnClose
	public void onClose(Session session) {

		System.out.println("Session " + session.getId() + " has ended");

		// Getting the client name that exited
		String name = nameSessionPair.get(session.getId());

		// removing the session from sessions list
		sessions.remove(session);

		// Notifying all the clients about person exit
		sendMessageToAll(session.getId(), name, " left conversation!", false,
				true);

	}

	/**
	 * Method to send message to all clients
	 * 
	 * @param sessionId
	 * @param message
	 *            message to be sent to clients
	 * @param isNewClient
	 *            flag to identify that message is about new person joined
	 * @param isExit
	 *            flag to identify that a person left the conversation
	 * */
	private void sendMessageToAll(String sessionId, String name,
			String message, boolean isNewClient, boolean isExit) {

		// Looping through all the sessions and sending the message individually
		for (Session s : sessions) {
			String json = null;

			// Checking if the message is about new client joined
			if (isNewClient) {
				json = jsonUtils.getNewClientJson(sessionId, name, message,
						sessions.size());

			} else if (isExit) {
				// Checking if the person left the conversation
				json = jsonUtils.getClientExitJson(sessionId, name, message,
						sessions.size());
			} else {
				// Normal chat conversation message
				json = jsonUtils
						.getSendAllMessageJson(sessionId, name, message);
			}

			try {
				System.out.println("Sending Message To: " + sessionId + ", "
						+ json);

				s.getBasicRemote().sendText(json);
			} catch (IOException e) {
				System.out.println("error in sending. " + s.getId() + ", "
						+ e.getMessage());
				e.printStackTrace();
			}
		}
	}
}



With this we have completed the socket server part. Now quickly we can build a web app to the test the socket server. Again building the web app is very simple. The complete web app can be built using basic web technologies like HTML, CSS & jQuery.


4. Building The Web App (HTML, CSS & jQuery)

To create the web app, we don’t have to create another project. This is the part of same socket server project, so follow the below steps in the same project.

1. Create a file named style.css under WebContent ⇒ WEB-INF folder. This contains the css styles for the web UI.

body {
	padding: 0;
	margin: 0;
}

.body_container {
	width: 1000px;
	margin: 0 auto;
	padding: 0;
}

.clear {
	clear: both;
}

.green {
	color: #8aaf0d;
}

#header {
	margin: 0 auto;
	padding: 50px 0;
	text-align: center;
}

#header h1,#header p.online_count {
	font-family: 'Open Sans', sans-serif;
	font-weight: 300;
}

#header p.online_count {
	font-size: 18px;
	display: none;
}

.box_shadow {
	background: #f3f4f6;
	border: 1px solid #e4e4e4;
	-moz-box-shadow: 0px 0px 2px 1px #e5e5e5;
	-webkit-box-shadow: 0px 0px 2px 1px #e5e5e5;
	box-shadow: 0px 0px 2px 1px #e5e5e5;
}

#prompt_name_container {
	margin: 0 auto;
	width: 350px;
	text-align: center;
}

#prompt_name_container p {
	font-family: 'Open Sans', sans-serif;
	font-weight: 300;
	font-size: 24px;
	color: #5e5e5e;
}

#prompt_name_container #input_name {
	border: 1px solid #dddddd;
	padding: 10px;
	width: 250px;
	display: block;
	margin: 0 auto;
	outline: none;
	font-family: 'Open Sans', sans-serif;
}

#prompt_name_container #btn_join {
	border: none;
	width: 100px;
	display: block;
	outline: none;
	font-family: 'Open Sans', sans-serif;
	color: #fff;
	background: #96be0e;
	font-size: 18px;
	padding: 5px 20px;
	margin: 15px auto;
	cursor: pointer;
}

#message_container {
	display: none;
	width: 500px;
	margin: 0 auto;
	background: #fff;
	padding: 15px 0 0 0;
}

#messages {
	margin: 0;
	padding: 0;
	height: 300px;
	overflow: scroll;
	overflow-x: hidden;
}

#messages li {
	list-style: none;
	font-family: 'Open Sans', sans-serif;
	font-size: 16px;
	padding: 10px 20px;
}

#messages li.new,#messages li.exit {
	font-style: italic;
	color: #bbbbbb;
}

#messages li span.name {
	color: #8aaf0d;
}

#messages li span.red {
	color: #e94e59;
}

#input_message_container {
	margin: 40px 20px 0 20px;
}

#input_message {
	background: #f0f0f0;
	border: none;
	font-size: 20px;
	font-family: 'Open Sans', sans-serif;
	outline: none;
	padding: 10px;
	float: left;
	margin: 0;
	width: 348px;
}

#btn_send {
	float: left;
	margin: 0;
	border: none;
	color: #fff;
	font-family: 'Open Sans', sans-serif;
	background: #96be0e;
	outline: none;
	padding: 10px 20px;
	font-size: 20px;
	cursor: pointer;
}

#btn_close {
	margin: 0;
	border: none;
	color: #fff;
	font-family: 'Open Sans', sans-serif;
	background: #e94e59;
	outline: none;
	padding: 10px 20px;
	font-size: 20px;
	cursor: pointer;
	width: 100%;
	margin: 30px 0 0 0;
}



2. Create another file named main.js and add below javascript. This file contains all the methods required to handle communication between socket server and client. The other things like parsing JSON, appending messages to chat list also taken care in the same file.

// to keep the session id
var sessionId = '';

// name of the client
var name = '';

// socket connection url and port
var socket_url = '192.168.0.102';
var port = '8080';

$(document).ready(function() {

	$("#form_submit, #form_send_message").submit(function(e) {
		e.preventDefault();
		join();
	});
});

var webSocket;

/**
 * Connecting to socket
 */
function join() {
	// Checking person name
	if ($('#input_name').val().trim().length <= 0) {
		alert('Enter your name');
	} else {
		name = $('#input_name').val().trim();

		$('#prompt_name_container').fadeOut(1000, function() {
			// opening socket connection
			openSocket();
		});
	}

	return false;
}

/**
 * Will open the socket connection
 */
function openSocket() {
	// Ensures only one connection is open at a time
	if (webSocket !== undefined && webSocket.readyState !== WebSocket.CLOSED) {
		return;
	}

	// Create a new instance of the websocket
	webSocket = new WebSocket("ws://" + socket_url + ":" + port
			+ "/WebMobileGroupChatServer/chat?name=" + name);

	/**
	 * Binds functions to the listeners for the websocket.
	 */
	webSocket.onopen = function(event) {
		$('#message_container').fadeIn();

		if (event.data === undefined)
			return;

	};

	webSocket.onmessage = function(event) {

		// parsing the json data
		parseMessage(event.data);
	};

	webSocket.onclose = function(event) {
		alert('Error! Connection is closed. Try connecting again.');
	};
}

/**
 * Sending the chat message to server
 */
function send() {
	var message = $('#input_message').val();

	if (message.trim().length > 0) {
		sendMessageToServer('message', message);
	} else {
		alert('Please enter message to send!');
	}

}

/**
 * Closing the socket connection
 */
function closeSocket() {
	webSocket.close();

	$('#message_container').fadeOut(600, function() {
		$('#prompt_name_container').fadeIn();
		// clearing the name and session id
		sessionId = '';
		name = '';

		// clear the ul li messages
		$('#messages').html('');
		$('p.online_count').hide();
	});
}

/**
 * Parsing the json message. The type of message is identified by 'flag' node
 * value flag can be self, new, message, exit
 */
function parseMessage(message) {
	var jObj = $.parseJSON(message);

	// if the flag is 'self' message contains the session id
	if (jObj.flag == 'self') {

		sessionId = jObj.sessionId;

	} else if (jObj.flag == 'new') {
		// if the flag is 'new', a client joined the chat room
		var new_name = 'You';

		// number of people online
		var online_count = jObj.onlineCount;

		$('p.online_count').html(
				'Hello, <span class="green">' + name + '</span>. <b>'
						+ online_count + '</b> people online right now')
				.fadeIn();

		if (jObj.sessionId != sessionId) {
			new_name = jObj.name;
		}

		var li = '<li class="new"><span class="name">' + new_name + '</span> '
				+ jObj.message + '</li>';
		$('#messages').append(li);

		$('#input_message').val('');

	} else if (jObj.flag == 'message') {
		// if the json flag is 'message', it means somebody sent the chat
		// message

		var from_name = 'You';

		if (jObj.sessionId != sessionId) {
			from_name = jObj.name;
		}

		var li = '<li><span class="name">' + from_name + '</span> '
				+ jObj.message + '</li>';

		// appending the chat message to list
		appendChatMessage(li);

		$('#input_message').val('');

	} else if (jObj.flag == 'exit') {
		// if the json flag is 'exit', it means somebody left the chat room
		var li = '<li class="exit"><span class="name red">' + jObj.name
				+ '</span> ' + jObj.message + '</li>';

		var online_count = jObj.onlineCount;

		$('p.online_count').html(
				'Hello, <span class="green">' + name + '</span>. <b>'
						+ online_count + '</b> people online right now');

		appendChatMessage(li);
	}
}

/**
 * Appending the chat message to list
 */
function appendChatMessage(li) {
	$('#messages').append(li);

	// scrolling the list to bottom so that new message will be visible
	$('#messages').scrollTop($('#messages').height());
}

/**
 * Sending message to socket server message will be in json format
 */
function sendMessageToServer(flag, message) {
	var json = '{""}';

	// preparing json object
	var myObject = new Object();
	myObject.sessionId = sessionId;
	myObject.message = message;
	myObject.flag = flag;

	// converting json object to json string
	json = JSON.stringify(myObject);

	// sending message to server
	webSocket.send(json);
}

3. Now download jquery-1.11.1.min and the paste the file in WebContent ⇒ WEB-INF.

4. Finally create another file named index.html and add below code.

<!DOCTYPE html>

<html>
<head>
<title>Android, WebSockets Chat App | AndroidHive
	(www.androidhive.info)</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<script type="text/javascript" src="jquery-1.11.1.min.js"></script>
<link
	href='http://fonts.googleapis.com/css?family=Open+Sans:400,600,300,700'
	rel='stylesheet' type='text/css'>
<link href="style.css" type="text/css" rel='stylesheet' />
<script type="text/javascript" src="main.js"></script>
</head>
<body>
	<div class="body_container">

		<div id="header">
			<h1>Android WebSockets Chat Application</h1>
			<p class='online_count'>
				<b>23</b> people online right now
			</p>
		</div>

		<div id="prompt_name_container" class="box_shadow">
			<p>Enter your name</p>
			<form id="form_submit" method="post">
				<input type="text" id="input_name" /> <input type="submit"
					value="JOIN" id="btn_join">
			</form>
		</div>

		<div id="message_container" class="box_shadow">

			<ul id="messages">
			</ul>


			<div id="input_message_container">
				<form id="form_send_message" method="post" action="#">
					<input type="text" id="input_message"
						placeholder="Type your message here..." /> <input type="submit"
						id="btn_send" onclick="send();" value="Send" />
					<div class="clear"></div>
				</form>
			</div>
			<div>

				<input type="button" onclick="closeSocket();"
					value="Leave Chat Room" id="btn_close" />
			</div>

		</div>

	</div>

</body>
</html>



5. Now run the project by Right Click on project ⇒ Run As ⇒ Run on Server. You can see the project running on http://localhost:8080/WebMobileGroupChatServer/


5. Testing The Socket Server (using the web app)

In order to test the socket server using the web app, follow below steps. You can use multiple devices (like desktop PC, Laptop) or just one machine is enough.

1. Make sure that all the machines connected to same wifi router if you are testing the app on multiple machines. If you are testing the app using a single computer, you don’t have to connect to a wifi network.

2. Find the IP address of the machine on which socket server project is running. (Follow the steps mentioned in 2nd point to get the ip address)

3. Replace the ip address in main.js with your machine’s ip address.

var socket_url = '_YOUR_IP_ADDRESS_';

4. Now access your project url in browser. Replace localhost with your machine ip address in the url. My project url is http://192.168.0.104:8080/WebMobileGroupChatServer/. Access same url in another browser software or another machine’s browser to chat with different machines.


Once you are able to chat between multiple clients, we can go forward and build the android app in Android Building Group Chat App using Sockets – Part 2

Android Building Group Chat App using Sockets – Part 2

$
0
0

In the 1st part, we have learned how to build the socket server and the web chat app. We also did tested the socket server using the web app.

In this part we are going to build the next important component, i.e. android chat app. The app we are about to create will have two screens. The first screen will prompt the user to enter his/her name. This name is to identify the sender whenever a message is received. The second screen is to list the chat messages and to compose a new message.

So let’s start the app by creating a new android project in Eclipse IDE.

android building chat app using java sockets


6. Building The Android Chat App

1. In Eclipse create new android project by navigating to File ⇒ New ⇒ Android Application Project and fill out all the required details.

I gave my project name as WebMobileGroupChat and package name as
info.androidhive.webgroupchat.

2. Add the below color values in res ⇒ values ⇒ colors.xml file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="actionbar">#3cb879</color>
    <color name="body_background">#e8e8e8</color>
    <color name="body_background_green">#82e783</color>
    <color name="server_status_bar">#2b2b2b</color>
    <color name="title_gray">#434343</color>
    <color name="white">#ffffff</color>
    <color name="bg_msg_you">#5eb964</color>
    <color name="bg_msg_from">#e5e7eb</color>
    <color name="msg_border_color">#a1a1a1</color>
    <color name="bg_btn_join">#1e6258</color>
    <color name="bg_msg_input">#e8e8e8</color>
    <color name="text_msg_input">#626262</color>
    <color name="lblFromName">#777777</color>
</resources>



3. Also add the below string values in res ⇒ values ⇒ strings.xml file.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">WebMobileGroupChat</string>
    <string name="title">(Android WebSockets Chat App)</string>
    <string name="author_name">By Ravi Tamada</string>
    <string name="author_url">www.androidhive.info</string>
    <string name="enter_name">Enter your name</string>
    <string name="btn_join">JOIN</string>
    <string name="btn_send">Send</string>

</resources>



4. Edit styles.xml located under res ⇒ values ⇒ styles.xml and add below styles. Here we are adding the styles for the action bar.

<resources>
 
    <style name="ChatAppTheme" parent="@android:style/Theme.Holo.Light">
        <item name="android:actionBarStyle">@style/MyActionBarTheme</item>
    </style>
 
    <style name="MyActionBarTheme" parent="@android:style/Widget.Holo.Light.ActionBar">
        <item name="android:background">@color/actionbar</item>
        <item name="android:titleTextStyle">@style/TitleTextStyle</item>
    </style>
    
     <style name="TitleTextStyle" parent="android:TextAppearance.Holo.Widget.ActionBar.Title">
        <item name="android:textColor">@color/white</item>
    </style>
 
</resources>



5. Now we need an activity to take the username that is required when connecting to socket server. So under res ⇒ layout folder create an xml file named activity_name.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/actionbar"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/imgLogo"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="60dp"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/imgLogo"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="15dp"
        android:text="@string/title"
        android:textColor="@color/white"
        android:textSize="13dp" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:padding="20dp" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginTop="15dp"
            android:text="@string/enter_name"
            android:textColor="@color/white"
            android:textSize="18dp" />

        <EditText
            android:id="@+id/name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginTop="10dp"
            android:background="@color/white"
            android:inputType="textCapWords"
            android:padding="10dp" />

        <Button
            android:id="@+id/btnJoin"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dp"
            android:background="@color/bg_btn_join"
            android:paddingLeft="25dp"
            android:paddingRight="25dp"
            android:text="@string/btn_join"
            android:textColor="@color/white" />
    </LinearLayout>

    <!-- author info -->

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="20dp"
        android:gravity="center_horizontal"
        android:orientation="vertical" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/author_name"
            android:textColor="@color/white"
            android:textSize="12dp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/author_url"
            android:textColor="@color/white"
            android:textSize="12dp" />
    </LinearLayout>

</RelativeLayout>



6. Create a new activity named NameActivity.java under project’s main package. In this activity we don’t handle anything complex. We just take the user input from EditText and send it to other activity.

package info.androidhive.webgroupchat;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class NameActivity extends Activity {

	private Button btnJoin;
	private EditText txtName;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_name);

		btnJoin = (Button) findViewById(R.id.btnJoin);
		txtName = (EditText) findViewById(R.id.name);

		// Hiding the action bar
		getActionBar().hide();

		btnJoin.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				if (txtName.getText().toString().trim().length() > 0) {

					String name = txtName.getText().toString().trim();

					Intent intent = new Intent(NameActivity.this,
							MainActivity.class);
					intent.putExtra("name", name);

					startActivity(intent);

				} else {
					Toast.makeText(getApplicationContext(),
							"Please enter your name", Toast.LENGTH_LONG).show();
				}
			}
		});
	}
}



7. Finally make NameActivity.java as launcher activity in AndroidManifest.xml. Also add INTERNET permission as we need to make internet calls. This is how your manifest file should look like.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.webgroupchat"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="13"
        android:targetSdkVersion="21" />

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/ChatAppTheme" >
        <activity
            android:name=".NameActivity"
            android:label="@string/app_name"
            android:windowSoftInputMode="adjustPan" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait" >
        </activity>
    </application>

</manifest>



After doing the above changes, if you run the app, you should see the name activity launched as first activity. Below is the output of name activity where user can enter their name and move to next activity.

android building chat app using java sockets

Before going to implement sockets, I would like to create few resource files first which required to create messages interface.

8. Download this background image and paste it in project’s res ⇒ drawable folder. (If you don’t see drawable folder, create a new one and name it as drawable). This image will be used as background repeat image for the chat conversation.

9. Create 3 new xml files under drawable folder named tile_bg.xml, bg_msg_from.xml and bg_msg_you.xml and add below codes. These drawable xml files are used as background for chat messages.

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
  android:src="@drawable/bg_messages" 
  android:tileMode="repeat" />
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <!-- view background color -->
    <solid android:color="@color/bg_msg_from" >
    </solid>

    <corners android:radius="5dp" >
    </corners>

</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <!-- view background color -->
    <solid android:color="@color/bg_msg_you" >
    </solid>

    <corners android:radius="5dp" >
    </corners>

</shape>



10. Now under res ⇒ layout folder create two more xml files named list_item_message_left.xml and list_item_message_right.xml. These two layout files are used to align chat messages on left and right in the list view.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="5dp"
    android:paddingTop="5dp" 
    android:paddingLeft="10dp">

    <TextView
        android:id="@+id/lblMsgFrom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12dp"
        android:textColor="@color/lblFromName"
        android:textStyle="italic" 
        android:padding="5dp"/>

    <TextView
        android:id="@+id/txtMsg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:layout_marginRight="80dp"
        android:textColor="@color/title_gray"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:background="@drawable/bg_msg_from"/>

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="right"
    android:orientation="vertical"
    android:paddingBottom="5dp"
    android:paddingRight="10dp"
    android:paddingTop="5dp" >

    <TextView
        android:id="@+id/lblMsgFrom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="5dp"
        android:textColor="@color/lblFromName"
        android:textSize="12dp"
        android:textStyle="italic" />

    <TextView
        android:id="@+id/txtMsg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="80dp"
        android:background="@drawable/bg_msg_you"
        android:paddingBottom="5dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="5dp"
        android:textColor="@color/white"
        android:textSize="16dp" />

</LinearLayout>



11. Now we need to create another layout to list all the chat messages and an option to compose a new message. Create another layout activity_main.xml and add below code.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/tile_bg"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/list_view_messages"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="@null"
        android:divider="@null"
        android:transcriptMode="alwaysScroll" 
        android:stackFromBottom="true">
    </ListView>

    <LinearLayout
        android:id="@+id/llMsgCompose"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:orientation="horizontal"
        android:weightSum="3" >

        <EditText
            android:id="@+id/inputMsg"
            android:layout_width="0dp"
            android:layout_height="fill_parent"
            android:layout_weight="2"
            android:background="@color/bg_msg_input"
            android:textColor="@color/text_msg_input"
            android:paddingLeft="6dp"
            android:paddingRight="6dp"/>

        <Button
            android:id="@+id/btnSend"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@color/bg_btn_join"
            android:textColor="@color/white" 
            android:text="@string/btn_send" />
    </LinearLayout>

</LinearLayout>



12. With the above step, the creation of layout resources is done. Now we’ll quickly create few helper classes. In your project create a new package and name it as other.

After creating the new package my package name will be info.androidhive.webgroupchat.other.

13. In other package, create a class named Utils.java and add below code. This class contains methods to save the user’s session id in shared preferences.

package info.androidhive.webgroupchat.other;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

public class Utils {

	private Context context;
	private SharedPreferences sharedPref;

	private static final String KEY_SHARED_PREF = "ANDROID_WEB_CHAT";
	private static final int KEY_MODE_PRIVATE = 0;
	private static final String KEY_SESSION_ID = "sessionId",
			FLAG_MESSAGE = "message";

	public Utils(Context context) {
		this.context = context;
		sharedPref = this.context.getSharedPreferences(KEY_SHARED_PREF,
				KEY_MODE_PRIVATE);
	}

	public void storeSessionId(String sessionId) {
		Editor editor = sharedPref.edit();
		editor.putString(KEY_SESSION_ID, sessionId);
		editor.commit();
	}

	public String getSessionId() {
		return sharedPref.getString(KEY_SESSION_ID, null);
	}

	public String getSendMessageJSON(String message) {
		String json = null;

		try {
			JSONObject jObj = new JSONObject();
			jObj.put("flag", FLAG_MESSAGE);
			jObj.put("sessionId", getSessionId());
			jObj.put("message", message);

			json = jObj.toString();
		} catch (JSONException e) {
			e.printStackTrace();
		}

		return json;
	}

}



14. Create another class named Message.java. This model class defines each chat message where it contains message id, text and a boolean flag (isSelf) to define message owner. Using this boolean flag we’ll align message left or right in the list view.

package info.androidhive.webgroupchat.other;

public class Message {
	private String fromName, message;
	private boolean isSelf;

	public Message() {
	}

	public Message(String fromName, String message, boolean isSelf) {
		this.fromName = fromName;
		this.message = message;
		this.isSelf = isSelf;
	}

	public String getFromName() {
		return fromName;
	}

	public void setFromName(String fromName) {
		this.fromName = fromName;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public boolean isSelf() {
		return isSelf;
	}

	public void setSelf(boolean isSelf) {
		this.isSelf = isSelf;
	}

}



15. Create a class named WsConfig.java. This is where we define socket configuration i.e the socket url, port number and end point.

package info.androidhive.webgroupchat.other;

public class WsConfig {
	public static final String URL_WEBSOCKET = "ws://192.168.0.102:8080/WebMobileGroupChatServer/chat?name=";
}



16. Now under your main package create a class named MessagesListAdapter.java to implement the custom list view adapter class. This class plays a major role in rendering the list by aligning the chat messages left or right.

package info.androidhive.webgroupchat;

import info.androidhive.webgroupchat.other.Message;

import java.util.List;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class MessagesListAdapter extends BaseAdapter {

	private Context context;
	private List<Message> messagesItems;

	public MessagesListAdapter(Context context, List<Message> navDrawerItems) {
		this.context = context;
		this.messagesItems = navDrawerItems;
	}

	@Override
	public int getCount() {
		return messagesItems.size();
	}

	@Override
	public Object getItem(int position) {
		return messagesItems.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@SuppressLint("InflateParams")
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		/**
		 * The following list not implemented reusable list items as list items
		 * are showing incorrect data Add the solution if you have one
		 * */

		Message m = messagesItems.get(position);

		LayoutInflater mInflater = (LayoutInflater) context
				.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);

		// Identifying the message owner
		if (messagesItems.get(position).isSelf()) {
			// message belongs to you, so load the right aligned layout
			convertView = mInflater.inflate(R.layout.list_item_message_right,
					null);
		} else {
			// message belongs to other person, load the left aligned layout
			convertView = mInflater.inflate(R.layout.list_item_message_left,
					null);
		}

		TextView lblFrom = (TextView) convertView.findViewById(R.id.lblMsgFrom);
		TextView txtMsg = (TextView) convertView.findViewById(R.id.txtMsg);

		txtMsg.setText(m.getMessage());
		lblFrom.setText(m.getFromName());

		return convertView;
	}
}



17. Download the android websockets library and extract somewhere. Thanks to Koush for writing such a useful library.

18. Import the downloaded android websockets library into Eclipse workspace. Goto File ⇒ Import ⇒ Android ⇒ Existing Android Code Into Workspace and select the downloaded library project home directory.

19. Now add this project as a Library to our project. Right Click on project ⇒ Properties ⇒ Android (on left) ⇒ Add (on right, under Library section) and select the imported project.

android-adding-library-project

20. Finally open the main activity class (MainActivity.java) do the below changes. The below code very simple and everything is self explanatory.

> A web socket is created using WebSocketClient class and it has all the callback methods like onConnect, onMessage and onDisconnect.

> In onMessage method parseMessage() is called to parse the JSON received from the socket server.

> In parseMessage() method, the purpose of JSON is identified by reading the flag value.

> When a new message is received, the message is added to list view data source and adapter.notifyDataSetChanged() is called to update the chat list.

> sendMessageToServer() method is used to send the message from android device to socket server.

> playBeep() method is called to play device’s default notification sound whenever a new message is received.

package info.androidhive.webgroupchat;

import info.androidhive.webgroupchat.other.Message;
import info.androidhive.webgroupchat.other.Utils;
import info.androidhive.webgroupchat.other.WsConfig;

import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Intent;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

import com.codebutler.android_websockets.WebSocketClient;

public class MainActivity extends Activity {

	// LogCat tag
	private static final String TAG = MainActivity.class.getSimpleName();

	private Button btnSend;
	private EditText inputMsg;

	private WebSocketClient client;

	// Chat messages list adapter
	private MessagesListAdapter adapter;
	private List<Message> listMessages;
	private ListView listViewMessages;

	private Utils utils;

	// Client name
	private String name = null;

	// JSON flags to identify the kind of JSON response
	private static final String TAG_SELF = "self", TAG_NEW = "new",
			TAG_MESSAGE = "message", TAG_EXIT = "exit";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		btnSend = (Button) findViewById(R.id.btnSend);
		inputMsg = (EditText) findViewById(R.id.inputMsg);
		listViewMessages = (ListView) findViewById(R.id.list_view_messages);

		utils = new Utils(getApplicationContext());

		// Getting the person name from previous screen
		Intent i = getIntent();
		name = i.getStringExtra("name");

		btnSend.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// Sending message to web socket server
				sendMessageToServer(utils.getSendMessageJSON(inputMsg.getText()
						.toString()));

				// Clearing the input filed once message was sent
				inputMsg.setText("");
			}
		});

		listMessages = new ArrayList<Message>();

		adapter = new MessagesListAdapter(this, listMessages);
		listViewMessages.setAdapter(adapter);

		/**
		 * Creating web socket client. This will have callback methods
		 * */
		client = new WebSocketClient(URI.create(WsConfig.URL_WEBSOCKET
				+ URLEncoder.encode(name)), new WebSocketClient.Listener() {
			@Override
			public void onConnect() {

			}

			/**
			 * On receiving the message from web socket server
			 * */
			@Override
			public void onMessage(String message) {
				Log.d(TAG, String.format("Got string message! %s", message));

				parseMessage(message);

			}

			@Override
			public void onMessage(byte[] data) {
				Log.d(TAG, String.format("Got binary message! %s",
						bytesToHex(data)));

				// Message will be in JSON format
				parseMessage(bytesToHex(data));
			}

			/**
			 * Called when the connection is terminated
			 * */
			@Override
			public void onDisconnect(int code, String reason) {

				String message = String.format(Locale.US,
						"Disconnected! Code: %d Reason: %s", code, reason);

				showToast(message);

				// clear the session id from shared preferences
				utils.storeSessionId(null);
			}

			@Override
			public void onError(Exception error) {
				Log.e(TAG, "Error! : " + error);

				showToast("Error! : " + error);
			}

		}, null);

		client.connect();
	}

	/**
	 * Method to send message to web socket server
	 * */
	private void sendMessageToServer(String message) {
		if (client != null && client.isConnected()) {
			client.send(message);
		}
	}

	/**
	 * Parsing the JSON message received from server The intent of message will
	 * be identified by JSON node 'flag'. flag = self, message belongs to the
	 * person. flag = new, a new person joined the conversation. flag = message,
	 * a new message received from server. flag = exit, somebody left the
	 * conversation.
	 * */
	private void parseMessage(final String msg) {

		try {
			JSONObject jObj = new JSONObject(msg);

			// JSON node 'flag'
			String flag = jObj.getString("flag");

			// if flag is 'self', this JSON contains session id
			if (flag.equalsIgnoreCase(TAG_SELF)) {

				String sessionId = jObj.getString("sessionId");

				// Save the session id in shared preferences
				utils.storeSessionId(sessionId);

				Log.e(TAG, "Your session id: " + utils.getSessionId());

			} else if (flag.equalsIgnoreCase(TAG_NEW)) {
				// If the flag is 'new', new person joined the room
				String name = jObj.getString("name");
				String message = jObj.getString("message");

				// number of people online
				String onlineCount = jObj.getString("onlineCount");

				showToast(name + message + ". Currently " + onlineCount
						+ " people online!");

			} else if (flag.equalsIgnoreCase(TAG_MESSAGE)) {
				// if the flag is 'message', new message received
				String fromName = name;
				String message = jObj.getString("message");
				String sessionId = jObj.getString("sessionId");
				boolean isSelf = true;

				// Checking if the message was sent by you
				if (!sessionId.equals(utils.getSessionId())) {
					fromName = jObj.getString("name");
					isSelf = false;
				}

				Message m = new Message(fromName, message, isSelf);

				// Appending the message to chat list
				appendMessage(m);

			} else if (flag.equalsIgnoreCase(TAG_EXIT)) {
				// If the flag is 'exit', somebody left the conversation
				String name = jObj.getString("name");
				String message = jObj.getString("message");

				showToast(name + message);
			}

		} catch (JSONException e) {
			e.printStackTrace();
		}

	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		
		if(client != null & client.isConnected()){
			client.disconnect();
		}
	}

	/**
	 * Appending message to list view
	 * */
	private void appendMessage(final Message m) {
		runOnUiThread(new Runnable() {

			@Override
			public void run() {
				listMessages.add(m);

				adapter.notifyDataSetChanged();

				// Playing device's notification
				playBeep();
			}
		});
	}

	private void showToast(final String message) {

		runOnUiThread(new Runnable() {

			@Override
			public void run() {
				Toast.makeText(getApplicationContext(), message,
						Toast.LENGTH_LONG).show();
			}
		});

	}

	/**
	 * Plays device's default notification sound
	 * */
	public void playBeep() {

		try {
			Uri notification = RingtoneManager
					.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
			Ringtone r = RingtoneManager.getRingtone(getApplicationContext(),
					notification);
			r.play();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();

	public static String bytesToHex(byte[] bytes) {
		char[] hexChars = new char[bytes.length * 2];
		for (int j = 0; j < bytes.length; j++) {
			int v = bytes[j] & 0xFF;
			hexChars[j * 2] = hexArray[v >>> 4];
			hexChars[j * 2 + 1] = hexArray[v & 0x0F];
		}
		return new String(hexChars);
	}

}



Now if you run the app, you can see the below screen as main activity output.

android building chat app like whatsapp

With this we have completed the android app part too.

7. Testing the Web and Android App

To test the android app you need two android mobiles or you can just use one android mobile and a web app. Follow below steps to test the android app.

1. Make sure that all your devices are connected to same wifi network. If you are using two android mobiles, connect them to same wifi network.

2. Get the ip address of the machine on which socket server is running. Follow 2nd step in Part1 tutorial to get the ip address of your machine.

3. Replace the ip address in WsConfig.java and main.js with your machine IP address.

4. Deploy the app on to android devices. If you are testing the app using web and android apps, open both the apps and test.

android-building-the-chat-app

The same conversation can be seen on web app too.

androd-chat-app-dad-mom-son

I hope everyone could able to build the app without any hurdles. If you have any queries or suggestions, please do let me know in the comment section below.


Android Uploading Camera Image, Video to Server with Progress Bar

$
0
0

My previous tutorial explains how to download a file by showing a progress bar. In this article I am going to explain how to upload a file to server by showing the progress bar. Using this tutorial you can build an app like Instagram where you can capture image or record a video using camera and then upload to a server. On the server side, I used PHP language to read the file and moved it to a particular location.

The best thing about this article is, it works well with larger file uploads too without any out of memory errors. I have tested the app by uploading 50MB file flawlessly.

android file upload with progress bar


Prerequisite

As this article uploads the image/video taken from camera, you need to have knowledge over android camera module. So I recommend you go through my previous tutorial Android Working with Camera which gives you an overview of integrating camera in your android apps.


1. Creating Android Project

1. In Eclipse create a new android project by navigating to File ⇒ New ⇒ Android Application Project and fill out all the required details.

2. Open strings.xml located under res ⇒ values and add below string values.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Camera File Upload</string>
    <string name="btnTakePicture">Capture Image</string>
    <string name="btnRecordVideo">Record Video</string>
    <string name="or">(or)</string>
    <string name="btnUploadToServer">Upload to Server</string>

</resources>



3. Add below color values in colors.xml located under res ⇒ values folder.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <color name="view_background">#e8ecfa</color>
    <color name="btn_bg">#277bec</color>
    <color name="white">#ffffff</color>
    <color name="txt_font">#4e5572</color>
    <color name="action_bar">#1f2649</color>

</resources>



4. Now under src folder create a new class named Config.java. This class file contains file upload URL and image directory name to save the image/video on mobile memory. You will have to replace the file upload url with yours while testing.

package info.androidhive.camerafileupload;

public class Config {
	// File upload url (replace the ip with your server address)
	public static final String FILE_UPLOAD_URL = "http://192.168.0.104/AndroidFileUpload/fileUpload.php";
	
	// Directory name to store captured images and videos
    public static final String IMAGE_DIRECTORY_NAME = "Android File Upload";
}



5. Create a class named AndroidMultiPartEntity.java and paste below code. This class is a custom MultipartEntity class which provides very important functionality required for this project such as progress bar incrementation.

package info.androidhive.camerafileupload;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;

import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;

@SuppressWarnings("deprecation")
public class AndroidMultiPartEntity extends MultipartEntity

{

	private final ProgressListener listener;

	public AndroidMultiPartEntity(final ProgressListener listener) {
		super();
		this.listener = listener;
	}

	public AndroidMultiPartEntity(final HttpMultipartMode mode,
			final ProgressListener listener) {
		super(mode);
		this.listener = listener;
	}

	public AndroidMultiPartEntity(HttpMultipartMode mode, final String boundary,
			final Charset charset, final ProgressListener listener) {
		super(mode, boundary, charset);
		this.listener = listener;
	}

	@Override
	public void writeTo(final OutputStream outstream) throws IOException {
		super.writeTo(new CountingOutputStream(outstream, this.listener));
	}

	public static interface ProgressListener {
		void transferred(long num);
	}

	public static class CountingOutputStream extends FilterOutputStream {

		private final ProgressListener listener;
		private long transferred;

		public CountingOutputStream(final OutputStream out,
				final ProgressListener listener) {
			super(out);
			this.listener = listener;
			this.transferred = 0;
		}

		public void write(byte[] b, int off, int len) throws IOException {
			out.write(b, off, len);
			this.transferred += len;
			this.listener.transferred(this.transferred);
		}

		public void write(int b) throws IOException {
			out.write(b);
			this.transferred++;
			this.listener.transferred(this.transferred);
		}
	}
}



Now we’ll add camera support in our app by creating a simple screen with two buttons to invoke camera app to capture image or record video.

6. Open your AndroidManifest.xml file and add required permissions. You can notice that UploadActivity also added in below manifest file. We’ll create it in few minutes.

INTERNET – Required to make network calls
WRITE_EXTERNAL_STORAGE – Required to store image/video on to storage
RECORD_AUDIO – Required to record audio along with video

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.camerafileupload"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="21" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="info.androidhive.camerafileupload.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="info.androidhive.camerafileupload.UploadActivity"
            android:screenOrientation="portrait" >
        </activity>
    </application>

</manifest>



7. Open the layout file of your main activity (activity_main.xml) and add below code. This creates a layout with two buttons.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/view_background"
    android:baselineAligned="false"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:orientation="vertical" >

        <!-- Capture picture button -->

        <Button
            android:id="@+id/btnCapturePicture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp"
            android:background="@color/btn_bg"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:text="@string/btnTakePicture"
            android:textColor="@color/white" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp"
            android:gravity="center_horizontal"
            android:text="@string/or"
            android:textColor="@color/txt_font" />

        <!-- Record video button -->

        <Button
            android:id="@+id/btnRecordVideo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/btn_bg"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:text="@string/btnRecordVideo"
            android:textColor="@color/white" />
    </LinearLayout>

</RelativeLayout>



8. Add below camera related code in your MainActivity.java class. This code is directly taken from this tutorial.

In brief what this activity will do is,

> Camera app will be launched on tapping take picture or record video button.
> Once the image / video is captured, it will be stored on to mobile SDCard.
> Finally UploadActivity will be launched by passing the SDCard path of the media that is captured. The process of uploading will be done in UploadActivity.

package info.androidhive.camerafileupload;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {
	
	// LogCat tag
	private static final String TAG = MainActivity.class.getSimpleName();
	
 
    // Camera activity request codes
    private static final int CAMERA_CAPTURE_IMAGE_REQUEST_CODE = 100;
    private static final int CAMERA_CAPTURE_VIDEO_REQUEST_CODE = 200;
    
    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;
 
    private Uri fileUri; // file url to store image/video
    
    private Button btnCapturePicture, btnRecordVideo;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // Changing action bar background color
        // These two lines are not needed
        getActionBar().setBackgroundDrawable(new ColorDrawable(Color.parseColor(getResources().getString(R.color.action_bar))));
 
        btnCapturePicture = (Button) findViewById(R.id.btnCapturePicture);
        btnRecordVideo = (Button) findViewById(R.id.btnRecordVideo);
 
        /**
         * Capture image button click event
         */
        btnCapturePicture.setOnClickListener(new View.OnClickListener() {
 
            @Override
            public void onClick(View v) {
                // capture picture
                captureImage();
            }
        });
 
        /**
         * Record video button click event
         */
        btnRecordVideo.setOnClickListener(new View.OnClickListener() {
 
            @Override
            public void onClick(View v) {
                // record video
                recordVideo();
            }
        });
 
        // Checking camera availability
        if (!isDeviceSupportCamera()) {
            Toast.makeText(getApplicationContext(),
                    "Sorry! Your device doesn't support camera",
                    Toast.LENGTH_LONG).show();
            // will close the app if the device does't have camera
            finish();
        }
    }
 
    /**
     * Checking device has camera hardware or not
     * */
    private boolean isDeviceSupportCamera() {
        if (getApplicationContext().getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_CAMERA)) {
            // this device has a camera
            return true;
        } else {
            // no camera on this device
            return false;
        }
    }
 
    /**
     * Launching camera app to capture image
     */
    private void captureImage() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 
        fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);
 
        intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
 
        // start the image capture Intent
        startActivityForResult(intent, CAMERA_CAPTURE_IMAGE_REQUEST_CODE);
    }
    
    /**
     * Launching camera app to record video
     */
    private void recordVideo() {
        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
 
        fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);
 
        // set video quality
        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
 
        intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file
                                                            // name
 
        // start the video capture Intent
        startActivityForResult(intent, CAMERA_CAPTURE_VIDEO_REQUEST_CODE);
    }
 
    /**
     * Here we store the file url as it will be null after returning from camera
     * app
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
 
        // save file url in bundle as it will be null on screen orientation
        // changes
        outState.putParcelable("file_uri", fileUri);
    }
 
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
 
        // get the file url
        fileUri = savedInstanceState.getParcelable("file_uri");
    }
 
    
 
    /**
     * Receiving activity result method will be called after closing the camera
     * */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // if the result is capturing Image
        if (requestCode == CAMERA_CAPTURE_IMAGE_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                
            	// successfully captured the image
                // launching upload activity
            	launchUploadActivity(true);
            	
            	
            } else if (resultCode == RESULT_CANCELED) {
                
            	// user cancelled Image capture
                Toast.makeText(getApplicationContext(),
                        "User cancelled image capture", Toast.LENGTH_SHORT)
                        .show();
            
            } else {
                // failed to capture image
                Toast.makeText(getApplicationContext(),
                        "Sorry! Failed to capture image", Toast.LENGTH_SHORT)
                        .show();
            }
        
        } else if (requestCode == CAMERA_CAPTURE_VIDEO_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                
            	// video successfully recorded
                // launching upload activity
            	launchUploadActivity(false);
            
            } else if (resultCode == RESULT_CANCELED) {
                
            	// user cancelled recording
                Toast.makeText(getApplicationContext(),
                        "User cancelled video recording", Toast.LENGTH_SHORT)
                        .show();
            
            } else {
                // failed to record video
                Toast.makeText(getApplicationContext(),
                        "Sorry! Failed to record video", Toast.LENGTH_SHORT)
                        .show();
            }
        }
    }
    
    private void launchUploadActivity(boolean isImage){
    	Intent i = new Intent(MainActivity.this, UploadActivity.class);
        i.putExtra("filePath", fileUri.getPath());
        i.putExtra("isImage", isImage);
        startActivity(i);
    }
     
    /**
     * ------------ Helper Methods ---------------------- 
     * */
 
    /**
     * Creating file uri to store image/video
     */
    public Uri getOutputMediaFileUri(int type) {
        return Uri.fromFile(getOutputMediaFile(type));
    }
 
    /**
     * returning image / video
     */
    private static File getOutputMediaFile(int type) {
 
        // External sdcard location
        File mediaStorageDir = new File(
                Environment
                        .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                Config.IMAGE_DIRECTORY_NAME);
 
        // Create the storage directory if it does not exist
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.d(TAG, "Oops! Failed create "
                        + Config.IMAGE_DIRECTORY_NAME + " directory");
                return null;
            }
        }
 
        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
                Locale.getDefault()).format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator
                    + "IMG_" + timeStamp + ".jpg");
        } else if (type == MEDIA_TYPE_VIDEO) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator
                    + "VID_" + timeStamp + ".mp4");
        } else {
            return null;
        }
 
        return mediaFile;
    }
}



Now if you run the app, you should see following output.

android-file-upload-camera-screen
android-file-upload-camera-taking-camera-picture



Once you are able to launch camera and capture images, we can move forward and start creating the upload activity.

9. Create an xml file under res ⇒ layout folder named activity_upload.xml. This layout contains ImageView, VideoView to preview the captured media and a ProgressBar to show uploading progress.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/view_background"
    android:orientation="vertical"
    android:padding="10dp" >

    

    <!-- To display picture taken -->

    <ImageView
        android:id="@+id/imgPreview"
        android:layout_width="fill_parent"
        android:layout_height="200dp"
        android:visibility="gone" 
        android:layout_marginTop="15dp"/>

    <!-- Videoview to preview recorded video -->

    <VideoView
        android:id="@+id/videoPreview"
        android:layout_width="fill_parent"
        android:layout_height="400dp"
        android:visibility="gone" 
        android:layout_marginTop="15dp"/>

    <TextView
        android:id="@+id/txtPercentage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="15dp"
        android:layout_marginTop="15dp"
        android:textColor="@color/txt_font"
        android:textSize="30dp" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="20dp"
        android:layout_marginBottom="35dp" 
        android:visibility="gone"/>

    <Button
        android:id="@+id/btnUpload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:background="@color/btn_bg"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:text="@string/btnUploadToServer"
        android:textColor="@color/white" 
        android:layout_marginBottom="20dp"/>

</LinearLayout>



10. Create a class named UploadActivity.java and paste below code. In this activity

> The path of captured camera image/video is received from MainActivity and image/video is displayed on the screen for preview purpose.
> UploadFileToServer async method takes care of uploading file to server and updating the Progress Bar.

package info.androidhive.camerafileupload;

import info.androidhive.camerafileupload.AndroidMultiPartEntity.ProgressListener;

import java.io.File;
import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;

public class UploadActivity extends Activity {
	// LogCat tag
	private static final String TAG = MainActivity.class.getSimpleName();

	private ProgressBar progressBar;
	private String filePath = null;
	private TextView txtPercentage;
	private ImageView imgPreview;
	private VideoView vidPreview;
	private Button btnUpload;
	long totalSize = 0;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_upload);
		txtPercentage = (TextView) findViewById(R.id.txtPercentage);
		btnUpload = (Button) findViewById(R.id.btnUpload);
		progressBar = (ProgressBar) findViewById(R.id.progressBar);
		imgPreview = (ImageView) findViewById(R.id.imgPreview);
		vidPreview = (VideoView) findViewById(R.id.videoPreview);

		// Changing action bar background color
		getActionBar().setBackgroundDrawable(
				new ColorDrawable(Color.parseColor(getResources().getString(
						R.color.action_bar))));

		// Receiving the data from previous activity
		Intent i = getIntent();

		// image or video path that is captured in previous activity
		filePath = i.getStringExtra("filePath");

		// boolean flag to identify the media type, image or video
		boolean isImage = i.getBooleanExtra("isImage", true);

		if (filePath != null) {
			// Displaying the image or video on the screen
			previewMedia(isImage);
		} else {
			Toast.makeText(getApplicationContext(),
					"Sorry, file path is missing!", Toast.LENGTH_LONG).show();
		}

		btnUpload.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// uploading the file to server
				new UploadFileToServer().execute();
			}
		});

	}

	/**
	 * Displaying captured image/video on the screen
	 * */
	private void previewMedia(boolean isImage) {
		// Checking whether captured media is image or video
		if (isImage) {
			imgPreview.setVisibility(View.VISIBLE);
			vidPreview.setVisibility(View.GONE);
			// bimatp factory
			BitmapFactory.Options options = new BitmapFactory.Options();

			// down sizing image as it throws OutOfMemory Exception for larger
			// images
			options.inSampleSize = 8;

			final Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

			imgPreview.setImageBitmap(bitmap);
		} else {
			imgPreview.setVisibility(View.GONE);
			vidPreview.setVisibility(View.VISIBLE);
			vidPreview.setVideoPath(filePath);
			// start playing
			vidPreview.start();
		}
	}

	/**
	 * Uploading the file to server
	 * */
	private class UploadFileToServer extends AsyncTask<Void, Integer, String> {
		@Override
		protected void onPreExecute() {
			// setting progress bar to zero
			progressBar.setProgress(0);
			super.onPreExecute();
		}

		@Override
		protected void onProgressUpdate(Integer... progress) {
			// Making progress bar visible
			progressBar.setVisibility(View.VISIBLE);

			// updating progress bar value
			progressBar.setProgress(progress[0]);

			// updating percentage value
			txtPercentage.setText(String.valueOf(progress[0]) + "%");
		}

		@Override
		protected String doInBackground(Void... params) {
			return uploadFile();
		}

		@SuppressWarnings("deprecation")
		private String uploadFile() {
			String responseString = null;

			HttpClient httpclient = new DefaultHttpClient();
			HttpPost httppost = new HttpPost(Config.FILE_UPLOAD_URL);

			try {
				AndroidMultiPartEntity entity = new AndroidMultiPartEntity(
						new ProgressListener() {

							@Override
							public void transferred(long num) {
								publishProgress((int) ((num / (float) totalSize) * 100));
							}
						});

				File sourceFile = new File(filePath);

				// Adding file data to http body
				entity.addPart("image", new FileBody(sourceFile));

				// Extra parameters if you want to pass to server
				entity.addPart("website",
						new StringBody("www.androidhive.info"));
				entity.addPart("email", new StringBody("abc@gmail.com"));

				totalSize = entity.getContentLength();
				httppost.setEntity(entity);

				// Making server call
				HttpResponse response = httpclient.execute(httppost);
				HttpEntity r_entity = response.getEntity();

				int statusCode = response.getStatusLine().getStatusCode();
				if (statusCode == 200) {
					// Server response
					responseString = EntityUtils.toString(r_entity);
				} else {
					responseString = "Error occurred! Http Status Code: "
							+ statusCode;
				}

			} catch (ClientProtocolException e) {
				responseString = e.toString();
			} catch (IOException e) {
				responseString = e.toString();
			}

			return responseString;

		}

		@Override
		protected void onPostExecute(String result) {
			Log.e(TAG, "Response from server: " + result);

			// showing the server response in an alert dialog
			showAlert(result);

			super.onPostExecute(result);
		}

	}

	/**
	 * Method to show alert dialog
	 * */
	private void showAlert(String message) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setMessage(message).setTitle("Response from Servers")
				.setCancelable(false)
				.setPositiveButton("OK", new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
						// do nothing
					}
				});
		AlertDialog alert = builder.create();
		alert.show();
	}

}



Until now we are done with android project. Now let’s quickly create the PHP project to receive the file that is being sent from android app. But before that, we need to do small configuration changes to WAMP server.

2. Installing & Configuring WAMP Server

1. Download and install WAMP software. On windows machine, WAMP will be installed at C:\wamp location.

2. Open php.ini and modify below values. By default wamp server allows maximum of 2MB file only to upload. After changing the below values, you can upload the files upto 50MB size.

wamp-server-editing-php.ini-file
upload_max_filesize = 50M
post_max_size = 50M
max_input_time = 300
max_execution_time = 300

3. Now restart the WAMP server.


3. Creating PHP Project

1. Go inside C:\wamp\www and create a folder named AndroidFileUpload. This will be the root directory of our project.

2. Now go into AndroidFileUpload folder and create a folder named uploads to keep all the uploaded files.

3. Create a file named fileUpload.php and paste below content. Below php code takes care of receiving the files from android app and store them in uploads folder. Upon the processing the file, server responds with a JSON message.

<?php

// Path to move uploaded files
$target_path = "uploads/";

// array for final json respone
$response = array();

// getting server ip address
$server_ip = gethostbyname(gethostname());

// final file url that is being uploaded
$file_upload_url = 'http://' . $server_ip . '/' . 'AndroidFileUpload' . '/' . $target_path;


if (isset($_FILES['image']['name'])) {
    $target_path = $target_path . basename($_FILES['image']['name']);

    // reading other post parameters
    $email = isset($_POST['email']) ? $_POST['email'] : '';
    $website = isset($_POST['website']) ? $_POST['website'] : '';

    $response['file_name'] = basename($_FILES['image']['name']);
    $response['email'] = $email;
    $response['website'] = $website;

    try {
        // Throws exception incase file is not being moved
        if (!move_uploaded_file($_FILES['image']['tmp_name'], $target_path)) {
            // make error flag true
            $response['error'] = true;
            $response['message'] = 'Could not move the file!';
        }

        // File successfully uploaded
        $response['message'] = 'File uploaded successfully!';
        $response['error'] = false;
        $response['file_path'] = $file_upload_url . basename($_FILES['image']['name']);
    } catch (Exception $e) {
        // Exception occurred. Make error flag true
        $response['error'] = true;
        $response['message'] = $e->getMessage();
    }
} else {
    // File parameter is missing
    $response['error'] = true;
    $response['message'] = 'Not received any file!F';
}

// Echo final json response to client
echo json_encode($response);
?>



Below is the sample JSON response if the file is uploaded successfully. You can use error value to verify the upload on android side.

{
    "file_name": "DSC_0021.JPG",
    "email": "admin@androidhive.info",
    "website": "www.androidhive.info",
    "message": "File uploaded successfully!",
    "error": false,
    "file_path": "http://192.168.0.104/AndroidFileUpload/uploads/DSC_0021.JPG"
}


4. Testing the File Upload (localhost)

The following steps shows you how to test the both apps together locally.

1. Connect the both the devices (machine running the wamp server & android mobile) to same wifi network.

2. Start the WAMP server.

3. Get the ip address of the machine that is running the PHP project. You can get the ip address by typing ipconfig in command prompt. (On mac os, use ifconfig to get the ip address)

4. Replace the ip address in Config.java (check 4th step in android project) with your ip address.

5. Deploy & run the android app on the mobile.

android-uploading-camera-picture-to-server
android-uploading-camera-picture-to-server1
android-uploading-camera-picture-to-server2



References
1. Stackoverflow Question about file upload with progress bar.

2. Icon that I used as app icon.

Android Integrating PayPal using PHP, MySQL – Part 1

$
0
0

When you are building an eCommerce app, integrating the payment gateway is one of the most important building blocks of an app. Today we are going to learn how to integrate most popular payment gateway PayPal in our android app.

For an eCommerce app, building android app is not enough. We need to have a server side component too to maintain the inventory, customers, transactions and other important things. So this tutorial covers both the topics by diving the article into two parts. In this part, we are going to learn how to build the PHP, MySQL server and integrating the PayPal REST API. In the next part Android Integrating PayPal using PHP, MySQL – Part 2 we’ll learn how to build the android app and integrate the PayPal gateway.

android paypal integration php mysql



1. Overview
2. Creating PayPal App (Client ID & Secret)
3. PayPal Sandbox Test Account
4. Downloading PayPal PHP Rest API SDK
5. Downloading Slim Framework
6. Downloading & Installing WAMP
7. Creating MySQL Database
8. Creating PHP Project
9. Downloading PayPal Android SDK (Part 2)
10. Creating the Android Project (Part 2)
11. Testing the App (Part 2)
12. Final Thoughts (Part 2)


Below are the screenshots of the final app.

android paypal listing the shopping cart
android paypal payment gateway>


1. Overview

PayPal provides multiple payment options such as Single Payment (Receives the payment immediately), Future Payments (Can be used for recurring payments) and Profile Sharing (Obtains information about customer). For our use case Single payment is best suited options as we need to get the payment immediately once user purchases some products.

Below diagram explains the complete app flow from listing the products to completing the checkout.

android paypal php mysql integration overview

1. First the android app connects with server and fetches the products json. Products json will be parsed and all the products will be displayed on the android app.

2. User selects the products and make the payment using PayPal payment option.

3. After successful payment, PayPal issues the json that contains the payment id.

4. Android app sends the payment id to our server for verification.

5. Server make a REST API call to PayPal along with payment id to verify the payment.

6. PayPal responds a json in which we have to check for “state”: “approved” (and few other flags) for a successful payment.>


2. Creating PayPal App (Client ID & Secret)

In order to make the calls to PayPal API we need to create an app in developer.paypal.com and obtain Client ID & Secret.

1. Log into PayPal’s Developer account. If you are visiting for the first time, register and create a new account.

2. Once logged in, you will be redirected to my apps page where you can create a new app.

3. Give your app name, select developer account and click on create app. Once the app is created, you can notice the Client id & Secret. We’re gonna need these keys in our both server and client apps.

pay pal creating new app
pay pal client id and secret


3. PayPal Sandbox Test Account

Paypal provides a test environment called sandbox to test the app before going live. These test accounts comes with paypal balance credited into it using which you can make test purchases. To get your sandbox account credentials, follow below steps.

1. Go to PayPal’s Developer account and click on Accounts under Sandbox on the left panel.

2. On the right you can see the sandbox test accounts. Select the buyer email and click on Profile.

3. In the popup window, click on Change Password to change the password in case if you are not sure about the password.

4. In the popup window, goto Funding tab to see the test balance.

You have to use these credentials to test the app in sandbox environment.

paypal sanbox developer account
paypal sandbox account change password
paypal sandbox developer account money


4. Downloading PayPal PHP Rest API SDK

It’s always a good practise to use the SDKs provided by the vendors rather than building ourselves. Paypal provides REST API SDK for multiple platforms. As we choose to write the server side code in PHP, download the latest release of PayPal-PHP-SDK.

Here is the direct link for PayPal-PHP-SDK-1.2.0.zip


5. Downloading Slim Framework

PHP Slim library allows you to write simple and efficient REST APIs. This framework we use here to generate json responses. To know more about Slim, read my previous article Building REST API for Android app using PHP, Slim and MySQL about building a perfect REST API when your app needs to talk to a php, mysql server.

Download the latest version of Slim Framework.


6. Downloading & Installing WAMP

Download and Install WAMP from http://www.wampserver.com/en/ and start the program from Start => All Programs. Once started, you should be able to access via http://localhost/ in the browser.

Watch the below video to know how to download and install WAMP.


7. Creating MySQL Database

Overall we are going to create four tables. users (to store the customer information), products (to store the product information like name, price, description), payments (to store the paypal transactions) and sales (to keep the product sales done by customers). This is very simple database design. In real world scenario, the database will be more complex than this.

android paypal shopping cart database design

Open phpmyadmin by going to http://localhost/phpmyadmin and execute below SQL query to create required database and tables. Also I am inserting a user and few sample products for testing.


CREATE DATABASE IF NOT EXISTS `paypal` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci;
USE `paypal`;


CREATE TABLE IF NOT EXISTS `payments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) NOT NULL,
  `paypalPaymentId` text NOT NULL,
  `create_time` text NOT NULL,
  `update_time` text NOT NULL,
  `state` varchar(15) NOT NULL,
  `amount` decimal(6,2) NOT NULL,
  `currency` varchar(3) NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `userId` (`userId`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;


CREATE TABLE IF NOT EXISTS `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` text NOT NULL,
  `price` decimal(6,2) NOT NULL,
  `description` text NOT NULL,
  `image` text NOT NULL,
  `sku` text NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;


INSERT INTO `products` (`id`, `name`, `price`, `description`, `image`, `sku`, `created_at`) VALUES
(1, 'Google Nexus 6', '690.50', 'Midnight Blue, with 32 GB', 'http://api.androidhive.info/images/nexus5.jpeg', 'sku-2123wers100', '2015-02-04 23:19:42'),
(2, 'Sandisk Cruzer Blade 16 GB Flash Pendrive', '4.50', 'USB 2.0, 16 GB, Black & Red, Read 17.62 MB/sec, Write 4.42 MB/sec', 'http://api.androidhive.info/images/sandisk.jpeg', 'sku-78955545w', '2015-02-10 22:54:28'),
(3, 'Kanvas Katha Backpack', '11.25', '1 Zippered Pocket Outside at Front, Loop Handle, Dual Padded Straps at the Back, 1 Compartment', 'http://api.androidhive.info/images/backpack.jpeg', 'sku-8493948kk4', '2015-02-10 22:55:34'),
(4, 'Prestige Pressure Cooker', '30.00', 'Prestige Induction Starter Pack Deluxe Plus Pressure Cooker 5 L', 'http://api.androidhive.info/images/prestige.jpeg', 'sku-90903034ll', '2015-02-10 22:59:25');


CREATE TABLE IF NOT EXISTS `sales` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `paymentId` int(11) NOT NULL,
  `productId` int(11) NOT NULL,
  `state` varchar(15) NOT NULL,
  `salePrice` decimal(6,2) NOT NULL,
  `quantity` int(4) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `paymentId` (`paymentId`),
  KEY `productId` (`productId`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=17 ;


CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `email` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `id` (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;


INSERT INTO `users` (`id`, `name`, `email`) VALUES
(1, 'Android Hive', 'androidhive@gmail.com');

ALTER TABLE `payments`
  ADD CONSTRAINT `payments_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;

ALTER TABLE `sales`
  ADD CONSTRAINT `sales_ibfk_2` FOREIGN KEY (`productId`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  ADD CONSTRAINT `sales_ibfk_1` FOREIGN KEY (`paymentId`) REFERENCES `payments` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;


8. Creating PHP Project

Once you are done creating the database, let’s create the PHP project. Below is the project structure that we are going to create now. Carefully place all the folders/files in appropriate location as shown in the diagram. I am using the Netbeans IDE to develop my PHP project.

paypal server php project structure

In the project, the purpose of each file/folder is

include – All the config & helper classes goes into this directory
libs – All the third party libraries (Slim & PayPal) will be placed here
v1 – It is the version directory of our API

index.php – All the API calls will be handled in this file
.htaccess – Contains Apache web server configuration



1. Goto the directory where wamp is installed (generally wamp will be installed at c:\wamp) and open www directory. This is where all the php projects will be placed.

2. Inside www folder, create a folder named PayPalServer. This is the root directory for our project.

3. Now inside PayPalServer, create three folders named include, libs and v1.

4. Paste the Slim Framework and PayPal SDK in libs folder.

5. Create a file named Config.php in the include directory and add the below code. This file contains configuration values like database credentials, paypal client id & secret and default currency. Change the username and password with your mysql credentials.

<?php

/**
 * Database configuration
 */
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_HOST', 'localhost');
define('DB_NAME', 'paypal');

define('DEFAULT_CURRENCY', 'USD');
define('PAYPAL_CLIENT_ID', 'AdOTNRDUqb6jBLfB1IaVrNHFqLKhWROWCNZiuGrPQBqI0h_Hbf6teycjptu0'); // Paypal client id
define('PAYPAL_SECRET', 'EP5sARCiqusS6XGQG3Y-DpZ5KRL9lagYy8Wg0cvMrnznTUGsen3HMzHqdkXZ'); // Paypal secret

?>



6. Create another file named DBConnect.php in include directory and paste below code. This class takes care of opening database connection.

<?php

/**
 * Handling database connection
 *
 * @author Ravi Tamada
 */
class DbConnect {

    private $conn;

    function __construct() {
        
    }

    /**
     * Establishing database connection
     * @return database connection handler
     */
    function connect() {
        include_once dirname(__FILE__) . '/Config.php';

        // Connecting to mysql database
        $this->conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);

        // Check for database connection error
        if (mysqli_connect_errno()) {
            echo "Failed to connect to MySQL: " . mysqli_connect_error();
        }

        // returing connection resource
        return $this->conn;
    }

}

?>



7. Create another class named DBHandler.php in include directory. This class contains methods to performs CRUD operations on the database.

getAllProducts() – Retrieves all the products from products table
getProductBySku() – Fetches a product by it’s sku code
storePayment() – Stores paypal payment transaction.
storeSale() – Stores sale of a particular product.

<?php

/**
 * Class to handle all db operations
 * This class will have CRUD methods for database tables
 *
 * @author Ravi Tamada
 */
class DbHandler {

    private $conn;

    function __construct() {

        require_once dirname(__FILE__) . '/DbConnect.php';
        // opening db connection
        $db = new DbConnect();
        $this->conn = $db->connect();
    }

    /**
     * Listing products
     * 
     * */
    public function getAllProducts() {
        $stmt = $this->conn->prepare("SELECT * FROM products");
        $stmt->execute();
        $products = $stmt->get_result();
        $stmt->close();
        return $products;
    }

    /*
     * Fetches a product by its sku
     */
    public function getProductBySku($sku) {
        $stmt = $this->conn->prepare("SELECT * FROM products where sku = ?");
        $stmt->bind_param("s", $sku);

        if ($stmt->execute()) {
            $product = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $product;
        } else {
            $stmt->close();
            return NULL;
        }
    }

    /**
     * Stores paypal payment in payments table
     */
    public function storePayment($paypalPaymentId, $userId, $create_time, $update_time, $state, $amount, $currency) {
        $stmt = $this->conn->prepare("INSERT INTO payments(paypalPaymentId, userId, create_time, update_time, state, amount, currency) VALUES(?,?,?,?,?,?,?)") or die(mysql_error());
        $stmt->bind_param("sisssds", $paypalPaymentId, $userId, $create_time, $update_time, $state, $amount, $currency);
        $result = $stmt->execute();
        $stmt->close();

        if ($result) {
            // task row created
            // now assign the task to user
            $payment_id = $this->conn->insert_id;
            return $payment_id;
        } else {
            // task failed to create
            return NULL;
        }
    }

    /**
     * Stores item sale in sales table
     */
    public function storeSale($payment_id, $product_id, $state, $salePrice, $quantity) {
        $stmt = $this->conn->prepare("INSERT INTO sales(paymentId, productId, state, salePrice, quantity) VALUES(?,?,?,?,?)");
        $stmt->bind_param("iisdi", $payment_id, $product_id, $state, $salePrice, $quantity);
        $result = $stmt->execute();
        $stmt->close();

        if ($result) {
            $sale_id = $this->conn->insert_id;
            return $sale_id;
        } else {
            // task failed to create
            return NULL;
        }
    }

}

?>



8. Now create a file named .htaccess inside v1 folder and add below rules.

RewriteEngine On 
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteRule ^(.*)$ %{ENV:BASE}index.php [QSA,L]



9. Finally create index.php inside v1 and paste below code. This is most important file where we handle all the REST requests using Slim Framework.

In the below code

echoResponse() – Prints final json response when API call is made

$app->get(‘/products’.. – Fetches all the products from products table and prints in json format

$app->post(‘/verifyPayment’.. – Verifies the paypal payment on the server side that was done on mobile app. This server side verification is very important for every paypal transaction to avoid fraudulent payments.



<?php

ini_set('display_errors', 1);
require_once '../include/DBHandler.php';
require '../libs/Slim/Slim.php';

require __DIR__ . '/../libs/PayPal/autoload.php';

use PayPal\Api\Payment;

\Slim\Slim::registerAutoloader();

$app = new \Slim\Slim();

$userId = 1;

/**
 * Echoing json response to client
 * @param String $status_code Http response code
 * @param Int $response Json response
 */
function echoResponse($status_code, $response) {
    $app = \Slim\Slim::getInstance();
    // Http response code
    $app->status($status_code);

    // setting response content type to json
    $app->contentType('application/json');

    echo json_encode($response);
}

function authenticate(\Slim\Route $route) {
    // Implement your user authentication here
    // Check http://www.androidhive.info/2014/01/how-to-create-rest-api-for-android-app-using-php-slim-and-mysql-day-23/
}

/**
 * Lists all products
 * method - GET
 */
$app->get('/products', 'authenticate', function() {
            $response = array();
            $db = new DbHandler();

            // fetching all products
            $result = $db->getAllProducts();

            $response["error"] = false;
            $response["products"] = array();

            // looping through result and preparing products array
            while ($task = $result->fetch_assoc()) {
                $tmp = array();
                $tmp["id"] = $task["id"];
                $tmp["name"] = $task["name"];
                $tmp["price"] = $task["price"];
                $tmp["description"] = $task["description"];
                $tmp["image"] = $task["image"];
                $tmp["sku"] = $task["sku"];
                $tmp["created_at"] = $task["created_at"];
                array_push($response["products"], $tmp);
            }

            echoResponse(200, $response);
        });

/**
 * verifying the mobile payment on the server side
 * method - POST
 * @param paymentId paypal payment id
 * @param paymentClientJson paypal json after the payment
 */
$app->post('/verifyPayment', 'authenticate', function() use ($app) {

            $response["error"] = false;
            $response["message"] = "Payment verified successfully";
            global $userId;


            require_once '../include/Config.php';

            try {
                $paymentId = $app->request()->post('paymentId');
                $payment_client = json_decode($app->request()->post('paymentClientJson'), true);

                $apiContext = new \PayPal\Rest\ApiContext(
                        new \PayPal\Auth\OAuthTokenCredential(
                        PAYPAL_CLIENT_ID, // ClientID
                        PAYPAL_SECRET      // ClientSecret
                        )
                );

                // Gettin payment details by making call to paypal rest api
                $payment = Payment::get($paymentId, $apiContext);

                // Verifying the state approved
                if ($payment->getState() != 'approved') {
                    $response["error"] = true;
                    $response["message"] = "Payment has not been verified. Status is " . $payment->getState();
                    echoResponse(200, $response);
                    return;
                }

                // Amount on client side
                $amount_client = $payment_client["amount"];

                // Currency on client side
                $currency_client = $payment_client["currency_code"];

                // Paypal transactions
                $transaction = $payment->getTransactions()[0];
                // Amount on server side
                $amount_server = $transaction->getAmount()->getTotal();
                // Currency on server side
                $currency_server = $transaction->getAmount()->getCurrency();
                $sale_state = $transaction->getRelatedResources()[0]->getSale()->getState();

                // Storing the payment in payments table
                $db = new DbHandler();
                $payment_id_in_db = $db->storePayment($payment->getId(), $userId, $payment->getCreateTime(), $payment->getUpdateTime(), $payment->getState(), $amount_server, $amount_server);

                // Verifying the amount
                if ($amount_server != $amount_client) {
                    $response["error"] = true;
                    $response["message"] = "Payment amount doesn't matched.";
                    echoResponse(200, $response);
                    return;
                }

                // Verifying the currency
                if ($currency_server != $currency_client) {
                    $response["error"] = true;
                    $response["message"] = "Payment currency doesn't matched.";
                    echoResponse(200, $response);
                    return;
                }

                // Verifying the sale state
                if ($sale_state != 'completed') {
                    $response["error"] = true;
                    $response["message"] = "Sale not completed";
                    echoResponse(200, $response);
                    return;
                }

                // storing the saled items
                insertItemSales($payment_id_in_db, $transaction, $sale_state);

                echoResponse(200, $response);
            } catch (\PayPal\Exception\PayPalConnectionException $exc) {
                if ($exc->getCode() == 404) {
                    $response["error"] = true;
                    $response["message"] = "Payment not found!";
                    echoResponse(404, $response);
                } else {
                    $response["error"] = true;
                    $response["message"] = "Unknown error occurred!" . $exc->getMessage();
                    echoResponse(500, $response);
                }
            } catch (Exception $exc) {
                $response["error"] = true;
                $response["message"] = "Unknown error occurred!" . $exc->getMessage();
                echoResponse(500, $response);
            }
        });

/**
 * method to store the saled items in sales table
 */
function insertItemSales($paymentId, $transaction, $state) {

    $item_list = $transaction->getItemList();

    $db = new DbHandler();

    foreach ($item_list->items as $item) {
        $sku = $item->sku;
        $price = $item->price;
        $quanity = $item->quantity;

        $product = $db->getProductBySku($sku);

        // inserting row into sales table
        $db->storeSale($paymentId, $product["id"], $state, $price, $quanity);
    }
}

$app->run();
?>



Now we have completed the server side part. Below are the final endpoints to which our android app should make requests.

URL endpoints

URLMethodParametersDescription
http://localhost/PayPalServer/v1/productsGETFetches all the products
http://localhost/PayPalServer/v1/verifyPaymentPOSTpaymentId, paymentClientJsonVerifies paypal payment

In the next article Android Integrating PayPal using PHP, MySQL – Part 2 we’re going to build the android app and integrate the PayPal gateway. If you have any queries in this part, please do comment in the comment section below.

Android Integrating PayPal using PHP, MySQL – Part 2

$
0
0

In the previous part Android Integrating PayPal using PHP, MySQL – Part 1, we have covered building the PayPal server side part i.e creating mysql database and writing the PHP services those interacts with android app and PayPal REST API.

In this part we are going to cover the remaining things like building the android app and integrating the PayPal payment gateway. Finally we conclude this by doing few tests in local environment.

android paypal integration php mysql


9. Downloading PayPal Android SDK

Just like PayPal PHP REST API SDK, PayPal provides SDK for mobile platforms too. Download the PayPal Android SDK and extract it. It comes with a Sample App, docs and libs. We would be interested in using the libs folder. If you want to try Future Payments and Profile Sharing, you can find example code in the sample app.


10. Creating the Android Project

Now let’s start building the android app.

1. Create a new project in Eclipse by going to File ⇒ New ⇒ Android Application Project and fill the required information.

I gave my project name as PayPalClient and package name as info.androidhive.paypalclient

2. Now quickly create two packages named app and helper.

3. Paste all the contents of PayPal Android SDK’s libs folder into our project’s libs folder.

Below is the final project structure that we’re going to create. You should have all the PayPal lib files and the packages placed as shown below.

android paypal project structure

4. Open colors.xml under res ⇒ value and add below color resources.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="btn_bg_checkout">#428bca</color>
    <color name="list_divider">#dedede</color>
    <color name="white">#ffffff</color>
    <color name="lbl_product_name">#333333</color>
    <color name="lbl_product_description">#444444</color>
    <color name="bg_msg_you">#5eb964</color>
    <color name="bg_msg_from">#e5e7eb</color>
    <color name="msg_border_color">#a1a1a1</color>
    <color name="bg_btn_join">#1e6258</color>
    <color name="bg_msg_input">#e8e8e8</color>
    <color name="text_msg_input">#626262</color>
    <color name="lblFromName">#777777</color>
</resources>



5. Open strings.xml under res ⇒ values and add below string values.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">PalPal Client</string>
    <string name="checkout">Checkout</string>
    <string name="add_to_cart">Add to Cart</string>

</resources>



6. I am using volley to make network calls. Download the volley.jar and paste it in libs folder. (If you are new to volley, this tutorial will give you a good overview about volley library).

7. Create class named LruBitmapCache.java under helper package. The purpose of this class to cache the downloaded images.

package info.androidhive.palpalclient.helper;

import com.android.volley.toolbox.ImageLoader.ImageCache;

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class LruBitmapCache extends LruCache<String, Bitmap> implements
		ImageCache {
	public static int getDefaultLruCacheSize() {
		final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
		final int cacheSize = maxMemory / 8;

		return cacheSize;
	}

	public LruBitmapCache() {
		this(getDefaultLruCacheSize());
	}

	public LruBitmapCache(int sizeInKiloBytes) {
		super(sizeInKiloBytes);
	}

	@Override
	protected int sizeOf(String key, Bitmap value) {
		return value.getRowBytes() * value.getHeight() / 1024;
	}

	@Override
	public Bitmap getBitmap(String url) {
		return get(url);
	}

	@Override
	public void putBitmap(String url, Bitmap bitmap) {
		put(url, bitmap);
	}
}



8. Crete a class named AppController.java under app package. This is a singleton class that extends from Application which will be executed on app launch. All the initialization of volley objects will be done here.

package info.androidhive.palpalclient.app;

import info.androidhive.palpalclient.helper.LruBitmapCache;
import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

public class AppController extends Application {

	public static final String TAG = AppController.class.getSimpleName();

	private RequestQueue mRequestQueue;
	private ImageLoader mImageLoader;

	private static AppController mInstance;

	@Override
	public void onCreate() {
		super.onCreate();
		mInstance = this;
	}

	public static synchronized AppController getInstance() {
		return mInstance;
	}

	public RequestQueue getRequestQueue() {
		if (mRequestQueue == null) {
			mRequestQueue = Volley.newRequestQueue(getApplicationContext());
		}

		return mRequestQueue;
	}

	public ImageLoader getImageLoader() {
		getRequestQueue();
		if (mImageLoader == null) {
			mImageLoader = new ImageLoader(this.mRequestQueue,
					new LruBitmapCache());
		}
		return this.mImageLoader;
	}

	public <T> void addToRequestQueue(Request<T> req, String tag) {
		// set the default tag if tag is empty
		req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
		getRequestQueue().add(req);
	}

	public <T> void addToRequestQueue(Request<T> req) {
		req.setTag(TAG);
		getRequestQueue().add(req);
	}

	public void cancelPendingRequests(Object tag) {
		if (mRequestQueue != null) {
			mRequestQueue.cancelAll(tag);
		}
	}
}



9. Now edit the AndroidManifest.xml and do below changes.

> Add the AppController class to <application> tag using android:name property.

> Add CAMERA, VIBRATE, ACCESS_NETWORK_STATE and INTERNET permissions.

> Add the camera feature. This should be added when user wants to pay using card.io feature.

> Add the PayPalService

> Add the PayPal SDK activities (PaymentActivity, LoginActivity, PaymentMethodActivity, PaymentConfirmActivity and CardIOActivity). These are necessary activities to make paypal payment.

Finally the AndroidManifest.xml should look like below.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.palpalclient"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />

    <!-- for card.io card scanning -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.VIBRATE" />

    <uses-feature
        android:name="android.hardware.camera"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.camera.autofocus"
        android:required="false" />

    <!-- for most things, including card.io & paypal -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name="info.androidhive.palpalclient.app.AppController"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name="info.androidhive.palpalclient.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name="com.paypal.android.sdk.payments.PayPalService"
            android:exported="false" /> 

        <activity android:name="com.paypal.android.sdk.payments.PaymentActivity" />
        <activity android:name="com.paypal.android.sdk.payments.LoginActivity" />
        <activity android:name="com.paypal.android.sdk.payments.PaymentMethodActivity" />
        <activity android:name="com.paypal.android.sdk.payments.PaymentConfirmActivity" />
        <activity
            android:name="io.card.payment.CardIOActivity"
            android:configChanges="keyboardHidden|orientation" />
        <activity android:name="io.card.payment.DataEntryActivity" />
    </application>

</manifest>



10. Crete a class named Config.java under app package. This class contains all the configuration variables like PayPal client id & secret, paypal environment and url endpoints to our server which we built in the first part of this series.

package info.androidhive.palpalclient.app;

import com.paypal.android.sdk.payments.PayPalConfiguration;
import com.paypal.android.sdk.payments.PayPalPayment;

public class Config {

	// PayPal app configuration
	public static final String PAYPAL_CLIENT_ID = "AbLgy0hRsq0PmoGK-ws2-jlBIeBVKUUU0xRjbfW1-GAckylz_TDNsh1cMrIiSksc2wpqYC2PisTrKhko";
	public static final String PAYPAL_CLIENT_SECRET = "";

	public static final String PAYPAL_ENVIRONMENT = PayPalConfiguration.ENVIRONMENT_SANDBOX;
	public static final String PAYMENT_INTENT = PayPalPayment.PAYMENT_INTENT_SALE;
	public static final String DEFAULT_CURRENCY = "USD";

	// PayPal server urls
	public static final String URL_PRODUCTS = "http://192.168.0.103/PayPalServer/v1/products";
	public static final String URL_VERIFY_PAYMENT = "http://192.168.0.103/PayPalServer/v1/verifyPayment";

}



11. Create a model class named Product.java under helper package. This class will be used while parsing the products json.

package info.androidhive.palpalclient.helper;

import java.math.BigDecimal;

public class Product {
	private String id, name, description, image, sku;
	private BigDecimal price;

	public Product() {

	}

	public Product(String id, String name, String description, String image,
			BigDecimal price, String sku) {
		this.id = id;
		this.name = name;
		this.description = description;
		this.image = image;
		this.price = price;
		this.sku = sku;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public String getImage() {
		return image;
	}

	public void setImage(String image) {
		this.image = image;
	}

	public BigDecimal getPrice() {
		return price;
	}

	public void setPrice(BigDecimal price) {
		this.price = price;
	}

	public String getSku() {
		return sku;
	}

	public void setSku(String sku) {
		this.sku = sku;
	}

}



12. Under res ⇒ layout, create a file named list_item_product.xml. This layout file renders a single list item in products list view. This layout contains the product image on the left and other details like product name, description, price on the right.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:paddingBottom="10dp" >

    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/productImage"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_margin="8dp"
        android:scaleType="fitCenter" >
    </com.android.volley.toolbox.NetworkImageView>

    <TextView
        android:id="@+id/productName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/productImage"
        android:padding="5dp"
        android:textColor="@color/lbl_product_name"
        android:textSize="16dp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/productDescription"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/productName"
        android:layout_toRightOf="@id/productImage"
        android:padding="5dp"
        android:textColor="@color/lbl_product_description" />

    <TextView
        android:id="@+id/productPrice"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/productDescription"
        android:layout_toRightOf="@id/productImage"
        android:padding="5dp"
        android:textColor="@color/lbl_product_description" />

    <Button
        android:id="@+id/btnAddToCart"
        android:layout_width="wrap_content"
        android:layout_height="30dp"
        android:layout_below="@id/productPrice"
        android:layout_margin="5dp"
        android:layout_toRightOf="@id/productImage"
        android:background="#64d048"
        android:paddingLeft="5dp"
        android:paddingRight="5dp"
        android:text="@string/add_to_cart"
        android:textColor="@color/white" />

</RelativeLayout>



13. As the product list view is customized, we need to write the custom list adapter class. Create a class named ProductListAdapter.java under helper package. This adapter class renders list_item_product.xml by filling appropriate product data in a single list row.

package info.androidhive.palpalclient.helper;

import info.androidhive.palpalclient.R;
import info.androidhive.palpalclient.app.AppController;

import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.TextView;

import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;

public class ProductListAdapter extends BaseAdapter {
	private Activity activity;
	private LayoutInflater inflater;
	private List<Product> products;
	private ProductListAdapterListener listener;
	ImageLoader imageLoader = AppController.getInstance().getImageLoader();

	public ProductListAdapter(Activity activity, List<Product> feedItems,
			ProductListAdapterListener listener) {
		this.activity = activity;
		this.products = feedItems;
		this.listener = listener;
	}

	@Override
	public int getCount() {
		return products.size();
	}

	@Override
	public Object getItem(int location) {
		return products.get(location);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		if (inflater == null)
			inflater = (LayoutInflater) activity
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		if (convertView == null)
			convertView = inflater.inflate(R.layout.list_item_product, null);

		if (imageLoader == null)
			imageLoader = AppController.getInstance().getImageLoader();

		TextView name = (TextView) convertView.findViewById(R.id.productName);
		TextView description = (TextView) convertView
				.findViewById(R.id.productDescription);
		TextView price = (TextView) convertView.findViewById(R.id.productPrice);

		NetworkImageView image = (NetworkImageView) convertView
				.findViewById(R.id.productImage);

		Button btnAddToCart = (Button) convertView
				.findViewById(R.id.btnAddToCart);

		final Product product = products.get(position);

		name.setText(product.getName());

		description.setText(product.getDescription());

		price.setText("Price: $" + product.getPrice());

		// user profile pic
		image.setImageUrl(product.getImage(), imageLoader);

		btnAddToCart.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				listener.onAddToCartPressed(product);
			}
		});

		return convertView;
	}

	public interface ProductListAdapterListener {
		public void onAddToCartPressed(Product product);
	}

}



14. Now we have all the helpers classes ready. Let’s move to main activity and start adding the PayPal code. Create a layout file under res layout named activity_main.xml and add below code. This layout contains a ListView to display the product list and a button to do the ckeckout.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white" >

    <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:paddingBottom="20dp"
        android:divider="@color/list_divider"
        android:dividerHeight="1dp">
    </ListView>

    <Button
        android:id="@+id/btnCheckout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="@string/checkout" 
        android:background="@color/btn_bg_checkout"/>

</RelativeLayout>



15. Open your MainActivity.java and do below changes. Basically to add PayPal support, we need to take below simple steps.

> Create a PayPalConfiguration object by setting necessary configuration like environment and client id.

> Start the PayPalService in onCreate()

> When user presses the checkout button, start the PaymentActivity by passing necessary information like final items, price, description etc.

> Once the payment is done, receive the PaymentConfirmation in onActivityResult(). This is where you will receives paypal response like payment id and other important information.

> Finally send the payment id and response json to our server for server side verification.

In the below code

fetchProducts() – Fetches the products json from our server.

verifyPaymentOnServer() – Verifies the mobile payment on the server.

prepareFinalCart() – Prepare the final cart information like total amount, items that needs to be submitted to paypal payment activity.

launchPayPalPayment() – Launches the PayPal payment activity

onAddToCartPressed() – Adds the item to cart when add to cart button is pressed.

package info.androidhive.palpalclient;

import info.androidhive.palpalclient.app.AppController;
import info.androidhive.palpalclient.app.Config;
import info.androidhive.palpalclient.helper.Product;
import info.androidhive.palpalclient.helper.ProductListAdapter;
import info.androidhive.palpalclient.helper.ProductListAdapter.ProductListAdapterListener;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.RetryPolicy;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.StringRequest;
import com.paypal.android.sdk.payments.PayPalConfiguration;
import com.paypal.android.sdk.payments.PayPalItem;
import com.paypal.android.sdk.payments.PayPalPayment;
import com.paypal.android.sdk.payments.PayPalPaymentDetails;
import com.paypal.android.sdk.payments.PayPalService;
import com.paypal.android.sdk.payments.PaymentActivity;
import com.paypal.android.sdk.payments.PaymentConfirmation;

public class MainActivity extends Activity implements
		ProductListAdapterListener {
	private static final String TAG = MainActivity.class.getSimpleName();

	private ListView listView;
	private Button btnCheckout;

	// To store all the products
	private List<Product> productsList;

	// To store the products those are added to cart
	private List<PayPalItem> productsInCart = new ArrayList<PayPalItem>();

	private ProductListAdapter adapter;

	// Progress dialog
	private ProgressDialog pDialog;

	private static final int REQUEST_CODE_PAYMENT = 1;

	// PayPal configuration
	private static PayPalConfiguration paypalConfig = new PayPalConfiguration()
			.environment(Config.PAYPAL_ENVIRONMENT).clientId(
					Config.PAYPAL_CLIENT_ID);

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		listView = (ListView) findViewById(R.id.list);
		btnCheckout = (Button) findViewById(R.id.btnCheckout);

		productsList = new ArrayList<Product>();
		adapter = new ProductListAdapter(this, productsList, this);

		listView.setAdapter(adapter);

		pDialog = new ProgressDialog(this);
		pDialog.setCancelable(false);

		// Starting PayPal service
		Intent intent = new Intent(this, PayPalService.class);
		intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, paypalConfig);
		startService(intent);

		// Checkout button click listener
		btnCheckout.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {

				// Check for empty cart
				if (productsInCart.size() > 0) {
					launchPayPalPayment();
				} else {
					Toast.makeText(getApplicationContext(), "Cart is empty! Please add few products to cart.",
							Toast.LENGTH_SHORT).show();
				}

			}
		});

		// Fetching products from server
		fetchProducts();
	}

	/**
	 * Fetching the products from our server
	 * */
	private void fetchProducts() {
		// Showing progress dialog before making request

		pDialog.setMessage("Fetching products...");

		showpDialog();

		// Making json object request
		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.GET,
				Config.URL_PRODUCTS, null, new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG, response.toString());

						try {
							JSONArray products = response
									.getJSONArray("products");

							// looping through all product nodes and storing
							// them in array list
							for (int i = 0; i < products.length(); i++) {

								JSONObject product = (JSONObject) products
										.get(i);

								String id = product.getString("id");
								String name = product.getString("name");
								String description = product
										.getString("description");
								String image = product.getString("image");
								BigDecimal price = new BigDecimal(product
										.getString("price"));
								String sku = product.getString("sku");

								Product p = new Product(id, name, description,
										image, price, sku);

								productsList.add(p);
							}

							// notifying adapter about data changes, so that the
							// list renders with new data
							adapter.notifyDataSetChanged();

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									"Error: " + e.getMessage(),
									Toast.LENGTH_LONG).show();
						}

						// hiding the progress dialog
						hidepDialog();
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						// hide the progress dialog
						hidepDialog();
					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(jsonObjReq);
	}

	/**
	 * Verifying the mobile payment on the server to avoid fraudulent payment
	 * */
	private void verifyPaymentOnServer(final String paymentId,
			final String payment_client) {
		// Showing progress dialog before making request
		pDialog.setMessage("Verifying payment...");
		showpDialog();

		StringRequest verifyReq = new StringRequest(Method.POST,
				Config.URL_VERIFY_PAYMENT, new Response.Listener<String>() {

					@Override
					public void onResponse(String response) {
						Log.d(TAG, "verify payment: " + response.toString());

						try {
							JSONObject res = new JSONObject(response);
							boolean error = res.getBoolean("error");
							String message = res.getString("message");

							// user error boolean flag to check for errors

							Toast.makeText(getApplicationContext(), message,
									Toast.LENGTH_SHORT).show();

							if (!error) {
								// empty the cart
								productsInCart.clear();
							}

						} catch (JSONException e) {
							e.printStackTrace();
						}

						// hiding the progress dialog
						hidepDialog();

					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						Log.e(TAG, "Verify Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						// hiding the progress dialog
						hidepDialog();
					}
				}) {

			@Override
			protected Map<String, String> getParams() {

				Map<String, String> params = new HashMap<String, String>();
				params.put("paymentId", paymentId);
				params.put("paymentClientJson", payment_client);

				return params;
			}
		};

		// Setting timeout to volley request as verification request takes sometime
		int socketTimeout = 60000;
		RetryPolicy policy = new DefaultRetryPolicy(socketTimeout,
				DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
				DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
		verifyReq.setRetryPolicy(policy);

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(verifyReq);
	}

	/**
	 * Preparing final cart amount that needs to be sent to PayPal for payment
	 * */
	private PayPalPayment prepareFinalCart() {

		PayPalItem[] items = new PayPalItem[productsInCart.size()];
		items = productsInCart.toArray(items);

		// Total amount
		BigDecimal subtotal = PayPalItem.getItemTotal(items);

		// If you have shipping cost, add it here
		BigDecimal shipping = new BigDecimal("0.0");

		// If you have tax, add it here
		BigDecimal tax = new BigDecimal("0.0");

		PayPalPaymentDetails paymentDetails = new PayPalPaymentDetails(
				shipping, subtotal, tax);

		BigDecimal amount = subtotal.add(shipping).add(tax);

		PayPalPayment payment = new PayPalPayment(
				amount,
				Config.DEFAULT_CURRENCY,
				"Description about transaction. This will be displayed to the user.",
				Config.PAYMENT_INTENT);

		payment.items(items).paymentDetails(paymentDetails);

		// Custom field like invoice_number etc.,
		payment.custom("This is text that will be associated with the payment that the app can use.");

		return payment;
	}

	/**
	 * Launching PalPay payment activity to complete the payment
	 * */
	private void launchPayPalPayment() {

		PayPalPayment thingsToBuy = prepareFinalCart();

		Intent intent = new Intent(MainActivity.this, PaymentActivity.class);

		intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, paypalConfig);

		intent.putExtra(PaymentActivity.EXTRA_PAYMENT, thingsToBuy);

		startActivityForResult(intent, REQUEST_CODE_PAYMENT);
	}

	/**
	 * Receiving the PalPay payment response
	 * */
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (requestCode == REQUEST_CODE_PAYMENT) {
			if (resultCode == Activity.RESULT_OK) {
				PaymentConfirmation confirm = data
						.getParcelableExtra(PaymentActivity.EXTRA_RESULT_CONFIRMATION);
				if (confirm != null) {
					try {
						Log.e(TAG, confirm.toJSONObject().toString(4));
						Log.e(TAG, confirm.getPayment().toJSONObject()
								.toString(4));

						String paymentId = confirm.toJSONObject()
								.getJSONObject("response").getString("id");

						String payment_client = confirm.getPayment()
								.toJSONObject().toString();

						Log.e(TAG, "paymentId: " + paymentId
								+ ", payment_json: " + payment_client);

						// Now verify the payment on the server side
						verifyPaymentOnServer(paymentId, payment_client);

					} catch (JSONException e) {
						Log.e(TAG, "an extremely unlikely failure occurred: ",
								e);
					}
				}
			} else if (resultCode == Activity.RESULT_CANCELED) {
				Log.e(TAG, "The user canceled.");
			} else if (resultCode == PaymentActivity.RESULT_EXTRAS_INVALID) {
				Log.e(TAG,
						"An invalid Payment or PayPalConfiguration was submitted.");
			}
		}
	}

	private void showpDialog() {
		if (!pDialog.isShowing())
			pDialog.show();
	}

	private void hidepDialog() {
		if (pDialog.isShowing())
			pDialog.dismiss();
	}

	@Override
	public void onAddToCartPressed(Product product) {

		PayPalItem item = new PayPalItem(product.getName(), 1,
				product.getPrice(), Config.DEFAULT_CURRENCY, product.getSku());

		productsInCart.add(item);

		Toast.makeText(getApplicationContext(),
				item.getName() + " added to cart!", Toast.LENGTH_SHORT).show();

	}

}


Here we completed android part too. In order to test both the server and mobile apps in the local environment, follow below steps.

11. Testing the App

> Make sure that both devices (the device running the php project and the android device) are connected to same wifi network.

> Replace the PAYPAL_CLIENT_ID & PAYPAL_SECRET in Config.php with your paypal keys in your php project.

> Replace the PAYPAL_CLIENT_ID in Config.java with your paypal client id

> Run the WAMP server and get the ip address of the machine by executing ipconfig in cmd. (On mac, use ifconfig to get the ip address). We need to use this ip address in the url instead of localhost.

> Replace the ip address of URL_PRODUCTS and URL_VERIFY_PAYMENT with your ip address in Config.java.

> Deploy and run the android app on device. If you get the products list displayed, your app successfully connecting with the php server.

> Try selecting the products you want to buy and proceed with the checkout. You should able to see PayPal payment screens and do the payment. When it ask for paypal credentials, use the sandbox credentials.

android paypal listing the shopping cart
android paypal payment gateway>
android paypal integrating verifying mobile payment


12. Final Thoughts

⇒ Making Your App Live
When you are making your app live, read the guidelines ‘Going Live with Your Application‘ provided by PayPal to move your app to production environment.

In our app, change the environment from PayPalConfiguration.ENVIRONMENT_SANDBOX to PayPalConfiguration.ENVIRONMENT_PRODUCTION in Config.java


⇒ Supported Currencies
PayPal is not supporting all the currencies as of now. Here is the list of currencies PayPal is supporting. Unfortunately, for indian users INR is not in the list. But you can use google currency calculator to get realtime INR to USD currency convert rate.


References:
> Goto through PayPal REST API doc for detailed explanation of each REST API call.

> PayPal Android Mobile SDK

> PayPal PHP REST API SDK

> Server image used in the illustration.

Android Swipe Down to Refresh ListView Tutorial

$
0
0

You might have noticed that lot of android apps like Twitter, Google+ provides an option to swipe / pull down to refresh it’s content. Whenever user swipes down from top, a loader will be shown and will disappear once the new content is loaded. In this tutorial we are going to learn how to provide the same option to your apps too.

Previously we used to implement a custom swipe view to detect the swipe down. But android made our day easier by introducing SwipeRefreshLayout in android.support.v4 to detect the vertical swipe on any view.

android-swipe-down-to-refresh-list-view-tutorial


1. Android SwipeRefreshLayout

Implementing SwipeRefreshLayout is very easy. Whenever you want to detect the swipe down on any view, just the wrap the view around SwipeRefreshLayout element. In our case, we are going to use it with ListView. And implement your activity class from SwipeRefreshLayout.OnRefreshListener. When user swipes down the view, onRefresh() method will be triggered. In you need to take appropriate action in that function like making an http call and fetch the latest data.

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipe_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <!-- place your view here -->

</android.support.v4.widget.SwipeRefreshLayout>


2. Example JSON

To demonstrate this tutorial, I am showing IMDB top 250 movies in a List View. For this I have created a json service which gives 20 movies in each request. You need to pass offset param to get the next set of results. Initially offset value should be 0, whenever the list is swiped down, we make an http call to get the next 20 movies and will update the ListView.

URL: http://api.androidhive.info/json/imdb_top_250.php?offset=0


3. Creating Android Project

1. In Android Studio, create a new project by navigating to File ⇒ New Project and fill all the required details. When it prompts to select a default activity, select Blank Activity and proceed.

2. Open build.gradle located under app folder and add volley library dependency. We are going to use volley to make HTTP calls to fetch the json.

com.mcxiaoke.volley:library-aar:1.0.0

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.0'
    compile 'com.mcxiaoke.volley:library-aar:1.0.0'
}



3. Open colors.xml under res ⇒ values and add below color resources. If you don’t find colors.xml, create a new file with the name. The color resources added below are used to set background color for movies rank in list view.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="movie_serial_bg">
        <item>#24c6d5</item>
        <item>#57dd86</item>
        <item>#ad7dcf</item>
        <item>#ff484d</item>
        <item>#fcba59</item>
        <item>#24c6d5</item>
    </string-array>
</resources>



3. Now under your project’s package, create three packages named app, activity and helper.

4. Under app package, create a class named MyApplication.java and add below code. This is a singleton Application class which initiates volley core objects on app launch.

package info.androidhive.swiperefresh.app;

import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;

/**
 * Created by Ravi on 13/05/15.
 */

public class MyApplication extends Application {

    public static final String TAG = MyApplication.class
            .getSimpleName();

    private RequestQueue mRequestQueue;

    private static MyApplication mInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }

    public static synchronized MyApplication getInstance() {
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }

        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req, String tag) {
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }

    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        getRequestQueue().add(req);
    }

    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}



5. Open AndroidManifest.xml and add MyApplication.java class to <application> tag. Also you need to add INTERNET permission as we need to make http calls.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.swiperefresh">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:name=".app.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity
            android:name=".activity.MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>



6. Now let’s create a custom adapter class for our list view. Under res ⇒ layout folder, create an xml layout named list_row.xml. This xml renders single list row in the ListView.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/serial"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="25dp"
        android:layout_margin="5dp"
        android:layout_alignParentLeft="true"
        android:textSize="20dp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/title"
        android:layout_toRightOf="@id/serial"
        android:layout_centerVertical="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:paddingLeft="20dp"
        android:textSize="18dp" />

</RelativeLayout>



7. Under helper package, create a java class named Movie.java and add below code. This is a model class required to create movie objects to provide data to the List View

package info.androidhive.swiperefresh.helper;

/**
 * Created by Ravi on 13/05/15.
 */
public class Movie {
    public int id;
    public String title;

    public Movie() {
    }

    public Movie(int id, String title) {
        this.title = title;
        this.id = id;
    }
}



8. Under helper package, create another class named SwipeListAdapter.java. This class is a custom adapter class which inflates the list_row.xml by applying proper data.

package info.androidhive.swiperefresh.helper;

import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.List;

import info.androidhive.swiperefresh.R;

/**
 * Created by Ravi on 13/05/15.
 */
public class SwipeListAdapter extends BaseAdapter {
    private Activity activity;
    private LayoutInflater inflater;
    private List<Movie> movieList;
    private String[] bgColors;

    public SwipeListAdapter(Activity activity, List<Movie> movieList) {
        this.activity = activity;
        this.movieList = movieList;
        bgColors = activity.getApplicationContext().getResources().getStringArray(R.array.movie_serial_bg);
    }

    @Override
    public int getCount() {
        return movieList.size();
    }

    @Override
    public Object getItem(int location) {
        return movieList.get(location);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (inflater == null)
            inflater = (LayoutInflater) activity
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (convertView == null)
            convertView = inflater.inflate(R.layout.list_row, null);

        TextView serial = (TextView) convertView.findViewById(R.id.serial);
        TextView title = (TextView) convertView.findViewById(R.id.title);

        serial.setText(String.valueOf(movieList.get(position).id));
        title.setText(movieList.get(position).title);

        String color = bgColors[position % bgColors.length];
        serial.setBackgroundColor(Color.parseColor(color));

        return convertView;
    }

}



10. Now we have all the required files in place, let’s start implementing the actual swipe refresh view. Open the layout file of your main activity (activity_main.xml) and modify the layout as shown below. I have added a ListView to show list of movies and wrapped it around SwipeRefreshLayout to get the swipe to refresh.

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipe_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ListView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/listView">

    </ListView>

</android.support.v4.widget.SwipeRefreshLayout>



11. Finally open MainActivity.java and do the below changes to achieve the swipe refresh list view.

> Implement the activity from SwipeRefreshLayout.OnRefreshListener and override the onRefresh() method.

> Call fetchMovies() which is a volley’s json array call to fetch the json and update the list view.

> onRefresh() is triggered whenever user swipes down the view. So call fetchMovies() inside this method to get the next set of movies response.

package info.androidhive.swiperefresh.activity;

import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.widget.ListView;
import android.widget.Toast;

import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

import info.androidhive.swiperefresh.R;
import info.androidhive.swiperefresh.app.MyApplication;
import info.androidhive.swiperefresh.helper.Movie;
import info.androidhive.swiperefresh.helper.SwipeListAdapter;


public class MainActivity extends ActionBarActivity implements SwipeRefreshLayout.OnRefreshListener {

    private String TAG = MainActivity.class.getSimpleName();

    private String URL_TOP_250 = "http://api.androidhive.info/json/imdb_top_250.php?offset=";

    private SwipeRefreshLayout swipeRefreshLayout;
    private ListView listView;
    private SwipeListAdapter adapter;
    private List<Movie> movieList;

    // initially offset will be 0, later will be updated while parsing the json
    private int offSet = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = (ListView) findViewById(R.id.listView);
        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);

        movieList = new ArrayList<>();
        adapter = new SwipeListAdapter(this, movieList);
        listView.setAdapter(adapter);

        swipeRefreshLayout.setOnRefreshListener(this);

        /**
         * Showing Swipe Refresh animation on activity create
         * As animation won't start on onCreate, post runnable is used
         */
        swipeRefreshLayout.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        swipeRefreshLayout.setRefreshing(true);

                                        fetchMovies();
                                    }
                                }
        );

    }

    /**
     * This method is called when swipe refresh is pulled down
     */
    @Override
    public void onRefresh() {
        fetchMovies();
    }

    /**
     * Fetching movies json by making http call
     */
    private void fetchMovies() {

        // showing refresh animation before making http call
        swipeRefreshLayout.setRefreshing(true);

        // appending offset to url
        String url = URL_TOP_250 + offSet;

        // Volley's json array request object
        JsonArrayRequest req = new JsonArrayRequest(url,
                new Response.Listener<JSONArray>() {
                    @Override
                    public void onResponse(JSONArray response) {
                        Log.d(TAG, response.toString());

                        if (response.length() > 0) {

                            // looping through json and adding to movies list
                            for (int i = 0; i < response.length(); i++) {
                                try {
                                    JSONObject movieObj = response.getJSONObject(i);

                                    int rank = movieObj.getInt("rank");
                                    String title = movieObj.getString("title");

                                    Movie m = new Movie(rank, title);

                                    movieList.add(0, m);

                                    // updating offset value to highest value
                                    if (rank >= offSet)
                                        offSet = rank;

                                } catch (JSONException e) {
                                    Log.e(TAG, "JSON Parsing error: " + e.getMessage());
                                }
                            }

                            adapter.notifyDataSetChanged();
                        }

                        // stopping swipe refresh
                        swipeRefreshLayout.setRefreshing(false);

                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(TAG, "Server Error: " + error.getMessage());

                Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_LONG).show();

                // stopping swipe refresh
                swipeRefreshLayout.setRefreshing(false);
            }
        });

        // Adding request to request queue
        MyApplication.getInstance().addToRequestQueue(req);
    }

}



Run the project and test it. You should able see the swipe refresh animation on app launch and list view updated each time you swipe down it.

android swipe down to refresh listview


4. PHP Class to Generate JSON

As lot of people are requesting me to provide the PHP code to generate the json, I am giving the code to generate the json used in this article. You can run this code using WAMP or XAMP softwares. Checkout Video 1 and Video 2 for installation and running PHP project in WAMP.

<?php

// sleep for 2 sec show that the androd swipe refresh will be visible for sometime
sleep(2);

// all top 250 movies
$movies = array("The Shawshank Redemption", "The Godfather", "The Godfather: Part II", "The Dark Knight", "Pulp Fiction", "Schindler's List", "12 Angry Men", "The Good, the Bad and the Ugly", "The Lord of the Rings: The Return of the King", "Fight Club", "The Lord of the Rings: The Fellowship of the Ring", "Star Wars: Episode V - The Empire Strikes Back", "Forrest Gump", "Inception", "One Flew Over the Cuckoo's Nest", "The Lord of the Rings: The Two Towers", "Goodfellas", "The Matrix", "Star Wars", "Seven Samurai", "City of God", "Se7en", "The Usual Suspects", "The Silence of the Lambs", "It's a Wonderful Life", "Interstellar", "Léon: The Professional", "Life Is Beautiful", "Once Upon a Time in the West", "Casablanca", "American History X", "Saving Private Ryan", "Spirited Away", "Raiders of the Lost Ark", "City Lights", "Psycho", "Rear Window", "The Intouchables", "Whiplash", "Modern Times", "The Green Mile", "Terminator 2: Judgment Day", "Memento", "The Pianist", "The Departed", "Apocalypse Now", "Gladiator", "Sunset Blvd.", "Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb", "Back to the Future", "Alien", "The Prestige", "The Lion King", "The Great Dictator", "The Lives of Others", "Cinema Paradiso", "Django Unchained", "The Shining", "Paths of Glory", "The Dark Knight Rises", "American Beauty", "WALL·E", "North by Northwest", "Aliens", "Citizen Kane", "Grave of the Fireflies", "Vertigo", "M", "Oldboy", "Das Boot", "Amélie", "Princess Mononoke", "Star Wars: Episode VI - Return of the Jedi", "Once Upon a Time in America", "Toy Story 3", "Reservoir Dogs", "A Clockwork Orange", "Braveheart", "Taxi Driver", "Double Indemnity", "Witness for the Prosecution", "Requiem for a Dream", "To Kill a Mockingbird", "Lawrence of Arabia", "Eternal Sunshine of the Spotless Mind", "Full Metal Jacket", "Bicycle Thieves", "The Sting", "Singin' in the Rain", "Amadeus", "Monty Python and the Holy Grail", "Snatch.", "2001: A Space Odyssey", "For a Few Dollars More", "Rashomon", "L.A. Confidential", "The Kid", "All About Eve", "The Apartment", "Inglourious Basterds", "Toy Story", "The Treasure of the Sierra Madre", "A Separation", "Indiana Jones and the Last Crusade", "Yojimbo", "The Third Man", "Some Like It Hot", "Metropolis", "Batman Begins", "Unforgiven", "Scarface", "Like Stars on Earth", "Raging Bull", "Up", "3 Idiots", "Downfall", "Chinatown", "The Great Escape", "Die Hard", "The Hunt", "On the Waterfront", "Heat", "Mr. Smith Goes to Washington", "Pan's Labyrinth", "Good Will Hunting", "The Bridge on the River Kwai", "My Neighbor Totoro", "Ikiru", "The Seventh Seal", "The Gold Rush", "Ran", "Wild Strawberries", "The General", "Blade Runner", "The Elephant Man", "Lock, Stock and Two Smoking Barrels", "The Secret in Their Eyes", "The Wolf of Wall Street", "Casino", "Gran Torino", "Howl's Moving Castle", "Warrior", "The Big Lebowski", "V for Vendetta", "Rebecca", "The Bandit", "Gone Girl", "The Deer Hunter", "Judgment at Nuremberg", "Cool Hand Luke", "How to Train Your Dragon", "It Happened One Night", "Fargo", "A Beautiful Mind", "Gone with the Wind", "Trainspotting", "Into the Wild", "Rush", "Dial M for Murder", "The Maltese Falcon", "The Sixth Sense", "Mary and Max", "Finding Nemo", "The Thing", "The Wages of Fear", "Hotel Rwanda", "No Country for Old Men", "Incendies", "Rang De Basanti", "Kill Bill: Vol. 1", "Platoon", "Life of Brian", "Butch Cassidy and the Sundance Kid", "Network", "A Wednesday", "Munna Bhai M.B.B.S.", "Touch of Evil", "There Will Be Blood", "12 Years a Slave", "Annie Hall", "The 400 Blows", "Stand by Me", "The Princess Bride", "Persona", "The Grand Budapest Hotel", "Amores Perros", "Ben-Hur", "Diabolique", "In the Name of the Father", "The Grapes of Wrath", "Million Dollar Baby", "Sin City", "Hachi: A Dog's Tale", "Nausicaä of the Valley of the Wind", "The Wizard of Oz", "The Best Years of Our Lives", "Gandhi", "The Avengers", "The Bourne Ultimatum", "Donnie Darko", "Shutter Island", "Stalker", "8½", "Guardians of the Galaxy", "Strangers on a Train", "Infernal Affairs", "Twelve Monkeys", "Fanny and Alexander", "Before Sunrise", "Boyhood", "Jaws", "The Imitation Game", "The Battle of Algiers", "The Terminator", "High Noon", "Groundhog Day", "Harry Potter and the Deathly Hallows: Part 2", "Memories of Murder", "The King's Speech", "Ip Man", "Monsters, Inc.", "Notorious", "Rocky", "Dog Day Afternoon", "Barry Lyndon", "La Haine", "The Truman Show", "Who's Afraid of Virginia Woolf?", "A Fistful of Dollars", "Dil Chahta Hai", "The Night of the Hunter", "Pirates of the Caribbean: The Curse of the Black Pearl", "Lagaan: Once Upon a Time in India", "Castle in the Sky", "Jurassic Park", "X-Men: Days of Future Past", "La Strada", "The Help", "Roman Holiday", "Wild Tales", "The Big Sleep", "Spring, Summer, Fall, Winter... and Spring", "Le Samouraï", "Prisoners", "Underground", "The Graduate", "Paris, Texas", "Solaris", "Three Colors: Red", "Papillon");

// reading offset from get parameter
$offset = isset($_GET['offset']) && $_GET['offset'] != '' ? $_GET['offset'] : 0;

// page limit
$limit = 20;


$movies_array = array();

// loop through page movies
for ($j = $offset; $j < $offset + $limit && $j < sizeof($movies); $j++) {
    $tmp = array();
    $tmp['rank'] = $j + 1;
    $tmp['title'] = $movies[$j];

    array_push($movies_array, $tmp);
}

// printing json response
echo json_encode($movies_array);
?>

Android adding SMS Verification Like WhatsApp – Part 1

$
0
0

My previous tutorial explains how to implement the user login / registration by collecting user name, email and password. But what if you want to sign up an user using their mobile number like WhatsApp, Viber by verifying their mobile number in order to get genuine users.

Before going further make sure that you have basic knowledge about android client-sever communication. If you are new to this, don’t worry, these articles (MySQL Spinner & PHP, MySQL – Android) gives you a good start.

As this article seems lengthy, I have divided it into two parts. In this part we’ll learn the server side part i.e building the PHP, MySQL project by integrating the SMS gateway. In the 2nd part we cover the implementation of android app.

android-sms-verification-like-whatsapp


1. How It Works

Below is the simple architecture diagram that explains the process of complete registration which involves the PHP, MySQL server and a SMS gateway.

android-sms-verification-php-mysql

1. First user mobile number will be sent to our server where new user row will be created.

2. Our server requests the SMS gateway for an sms to the mobile number with a verification code.

3. SMS gateway sends an SMS to the user device with the verification code.

4. The verification code will be sent back our server again for verification. Our server verifies it and activates the user.

2. Choosing the SMS Gateway

SMS gateways are helpful in sending an SMS to any mobile number that comes under any mobile network. Basically gateways have a contract with all the mobile networks to send the SMS to a particular mobile number. In INDIA we have lot of SMS providers, but choosing the best SMS service provider is difficult. I found Solutions Infini, SMS Gupshup, Value First and Msg91 are quite popular. But the most important thing keep in mind while choosing the provider is make sure that they do support REST API as our server needs to trigger an SMS whenever a request comes from the android app.

Unfortunately there are no free SMS gateways to test your android app. You have to contact any of the service providers seeking for a demo account or buy the SMS credits.


3. Signing up with MSG91 (API Key)

To demonstrate the tutorial, I have selected Msg91 gateway for their simplicity. Follow the below steps to get register and obtain your REST API Key. Please be aware that this gateway is for INDIAN users only. If you are not from INDIA, choose the gateway which is available in your country.

1. Signup with Msg91.

2. Buy the SMS credits by selecting the appropriate details. In the form select SMS Type as
TRANSACTIONAL ROUTE and give no.of SMS as 2500 which is minimum credits to buy. The cost would be around ₹600.

3. Proceed with the payment. Once the payment was done, you can see the SMS credits credited into your account.

4. To obtain your API key, goto the API doc and click on KEY on the top right corner. You will be shown your API Key in a popup dialog. Copy this API Key, we’re gonna need it in our PHP project.

Check the below video demonstrating buying the SMS credits on Msg91.


4. Building the PHP, MySQL REST API

Now we have the API Key needed to send the SMS. Let’s start building the PHP project needed for the android app. We are going to build a simple REST API to manage the users who signs up using the android app. At the end of this project, we should be able to build two end points needed for the user registration. One is to register user which sends an SMS with a verification code. Second endpoint is to verify the code and completes the user registration.


4.1 Installing WAMP software

Download & Install WAMP server from www.wampserver.com/en/. Once WAMP is installed, launch the program from Start ⇒ All Programs ⇒ WampServer ⇒ StartWampServer. If you are on Mac, MAMP will be useful.

You can test your server by opening the address http://localhost/ in your browser.
Also you can check phpmyadmin by opening http://localhost/phpmyadmin


4.2 Creating MySQL Database

For this project we need two tables. One is users table to store the user information and second one is sms_codes to store the user’s sms verification codes.

Below are the columns needed in each table.

android sms verification mysql database

Open http://localhost/phpmyadmin and execute below queries to create required database and tables.

Create a database named android_sms.

CREATE DATABASE android_sms;

USE android_sms;

Create required tables and relationship by executing below sql queries.

CREATE TABLE `sms_codes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `code` varchar(6) NOT NULL,
  `status` int(1) NOT NULL DEFAULT '0',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;


CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `email` varchar(250) NOT NULL,
  `mobile` varchar(10) NOT NULL,
  `apikey` varchar(32) NOT NULL,
  `status` int(1) NOT NULL DEFAULT '0',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=64 ;


ALTER TABLE `sms_codes`
  ADD CONSTRAINT `sms_codes_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;


4.3 Creating PHP Project

Once you are done with database setup, let’s move to php project development. You can use any IDE like Netbeans to develop this project or just the notepad++ is enough.

1. Goto the WAMP project directory i.e C:/wamp/www and create a folder named android_sms. This will be the root directory of our PHP project.

2. Inside android_sms, create a folder named include. In this folder we keep all the configuration like database credentials, Msg91 API Key etc.,

3. Create a file under include folder named Config.php and add below code. Replace the MySQL credentials and Msg91 API Key in the below code with yours.

<?php
/**
 * Database configuration
 */
define('DB_USERNAME', 'root');
define('DB_PASSWORD', 'root');
define('DB_HOST', 'localhost');
define('DB_NAME', 'android_sms');


/**
 * MSG91 configuration
 */
define('MSG91_AUTH_KEY', "88276AGwzewOEdFs559d2888");
// sender id should 6 character long
define('MSG91_SENDER_ID', 'ANHIVE');

define('USER_CREATED_SUCCESSFULLY', 0);
define('USER_CREATE_FAILED', 1);
define('USER_ALREADY_EXISTED', 2);
?>



4. Under include folder, create another php file named DbConnect.php This file handles the database connection.

<?php

/**
 * Handling database connection
 *
 * @author Ravi Tamada
 * @link URL Tutorial link
 */
class DbConnect {

    private $conn;

    function __construct() {        
    }

    /**
     * Establishing database connection
     * @return database connection handler
     */
    function connect() {
        include_once dirname(__FILE__) . '/Config.php';

        // Connecting to mysql database
        $this->conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);

        // Check for database connection error
        if (mysqli_connect_errno()) {
            echo "Failed to connect to MySQL: " . mysqli_connect_error();
            exit;
        }

        // returing connection resource
        return $this->conn;
    }

}

?>



5. Create another php file named DbHandler.php under include folder. This file contains the major functions to perform the read, write operations onto database.

<?php

/**
 * Class to handle all db operations
 * This class will have CRUD methods for database tables
 *
 * @author Ravi Tamada
 * @link URL Tutorial link
 */
class DbHandler {

    private $conn;

    function __construct() {
        require_once dirname(__FILE__) . '/DbConnect.php';
        // opening db connection
        $db = new DbConnect();
        $this->conn = $db->connect();
    }

    /* ------------- `users` table method ------------------ */

    /**
     * Creating new user
     * @param String $name User full name
     * @param String $email User login email id
     * @param String $mobile User mobile number
     * @param String $otp user verificaiton code
     */
    public function createUser($name, $email, $mobile, $otp) {
        $response = array();

        // First check if user already existed in db
        if (!$this->isUserExists($mobile)) {

            // Generating API key
            $api_key = $this->generateApiKey();

            // insert query
            $stmt = $this->conn->prepare("INSERT INTO users(name, email, mobile, apikey, status) values(?, ?, ?, ?, 0)");
            $stmt->bind_param("ssss", $name, $email, $mobile, $api_key);

            $result = $stmt->execute();

            $new_user_id = $stmt->insert_id;

            $stmt->close();

            // Check for successful insertion
            if ($result) {

                $otp_result = $this->createOtp($new_user_id, $otp);

                // User successfully inserted
                return USER_CREATED_SUCCESSFULLY;
            } else {
                // Failed to create user
                return USER_CREATE_FAILED;
            }
        } else {
            // User with same email already existed in the db
            return USER_ALREADY_EXISTED;
        }

        return $response;
    }

    public function createOtp($user_id, $otp) {

        // delete the old otp if exists
        $stmt = $this->conn->prepare("DELETE FROM sms_codes where user_id = ?");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();


        $stmt = $this->conn->prepare("INSERT INTO sms_codes(user_id, code, status) values(?, ?, 0)");
        $stmt->bind_param("is", $user_id, $otp);

        $result = $stmt->execute();

        $stmt->close();

        return $result;
    }
    
    /**
     * Checking for duplicate user by mobile number
     * @param String $email email to check in db
     * @return boolean
     */
    private function isUserExists($mobile) {
        $stmt = $this->conn->prepare("SELECT id from users WHERE mobile = ? and status = 1");
        $stmt->bind_param("s", $mobile);
        $stmt->execute();
        $stmt->store_result();
        $num_rows = $stmt->num_rows;
        $stmt->close();
        return $num_rows > 0;
    }

    public function activateUser($otp) {
        $stmt = $this->conn->prepare("SELECT u.id, u.name, u.email, u.mobile, u.apikey, u.status, u.created_at FROM users u, sms_codes WHERE sms_codes.code = ? AND sms_codes.user_id = u.id");
        $stmt->bind_param("s", $otp);

        if ($stmt->execute()) {
            // $user = $stmt->get_result()->fetch_assoc();
            $stmt->bind_result($id, $name, $email, $mobile, $apikey, $status, $created_at);
            
            $stmt->store_result();

            if ($stmt->num_rows > 0) {
                
                $stmt->fetch();
                
                // activate the user
                $this->activateUserStatus($id);
                
                $user = array();
                $user["name"] = $name;
                $user["email"] = $email;
                $user["mobile"] = $mobile;
                $user["apikey"] = $apikey;
                $user["status"] = $status;
                $user["created_at"] = $created_at;
                
                $stmt->close();
                
                return $user;
            } else {
                return NULL;
            }
        } else {
            return NULL;
        }

        return $result;
    }
    
    public function activateUserStatus($user_id){
        $stmt = $this->conn->prepare("UPDATE users set status = 1 where id = ?");
        $stmt->bind_param("i", $user_id);
        
        $stmt->execute();
        
        $stmt = $this->conn->prepare("UPDATE sms_codes set status = 1 where user_id = ?");
        $stmt->bind_param("i", $user_id);
        
        $stmt->execute();
    }

    /**
     * Generating random Unique MD5 String for user Api key
     */
    private function generateApiKey() {
        return md5(uniqid(rand(), true));
    }
}
?>



6. Now we have all the core logic ready. Let’s create an endpoint to register the user. In your project’s root directory, create a file named request_sms.php.

In below code

> First we receive the name, email and mobile number those were sent from the android device as a POST parameters.

> We create an entry in users table by calling createUser() function. Initially the user status will be 0 which indicate the user is inactive. This status will be made to 1 when the user verifies the OTP.

> Once the user row is created, we request for an SMS to the mobile number by calling sendSms() method.

> sendSms() method will make a call to Msg91 REST API to send SMS with a 6 digits OTP to the users mobile number.

Below is the sample SMS message the user will receive to their mobile. The OTP should be prefixed by : and space in the message.

Hello! Welcome to AndroidHive. Your OPT is : 228767
<?php

include './include/DbHandler.php';
$db = new DbHandler();


$response = array();

if (isset($_POST['mobile']) && $_POST['mobile'] != '') {

    $name = $_POST['name'];
    $email = $_POST['email'];
    $mobile = $_POST['mobile'];

    $otp = rand(100000, 999999);

    $res = $db->createUser($name, $email, $mobile, $otp);

    if ($res == USER_CREATED_SUCCESSFULLY) {
        
        // send sms
        sendSms($mobile, $otp);
        
        $response["error"] = false;
        $response["message"] = "SMS request is initiated! You will be receiving it shortly.";
    } else if ($res == USER_CREATE_FAILED) {
        $response["error"] = true;
        $response["message"] = "Sorry! Error occurred in registration.";
    } else if ($res == USER_ALREADY_EXISTED) {
        $response["error"] = true;
        $response["message"] = "Mobile number already existed!";
    }
} else {
    $response["error"] = true;
    $response["message"] = "Sorry! mobile number is not valid or missing.";
}

echo json_encode($response);

function sendSms($mobile, $otp) {
    
    $otp_prefix = ':';

    //Your message to send, Add URL encoding here.
    $message = urlencode("Hello! Welcome to AndroidHive. Your OPT is '$otp_prefix $otp'");

    $response_type = 'json';

    //Define route 
    $route = "4";
    
    //Prepare you post parameters
    $postData = array(
        'authkey' => MSG91_AUTH_KEY,
        'mobiles' => $mobile,
        'message' => $message,
        'sender' => MSG91_SENDER_ID,
        'route' => $route,
        'response' => $response_type
    );

//API URL
    $url = "https://control.msg91.com/sendhttp.php";

// init the resource
    $ch = curl_init();
    curl_setopt_array($ch, array(
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $postData
            //,CURLOPT_FOLLOWLOCATION => true
    ));


    //Ignore SSL certificate verification
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);


    //get response
    $output = curl_exec($ch);

    //Print error if any
    if (curl_errno($ch)) {
        echo 'error:' . curl_error($ch);
    }

    curl_close($ch);
}
?>



7. Now we need to create another endpoint to verify the OTP. Create a php file named verify_otp.php with below content.

In the below code

> The OTP will be received from the android device as a POST param.

> The user who matches with OTP will fetched from the users table.

> Then the user status will be set to 1 in both users and sms_codes table which makes the user active.

> The above two steps are implemented in activateUser() function.

<?php

include './include/DbHandler.php';
$db = new DbHandler();


$response = array();
$response["error"] = false;

if (isset($_POST['otp']) && $_POST['otp'] != '') {
    $otp = $_POST['otp'];


    $user = $db->activateUser($otp);

    if ($user != NULL) {

        $response["message"] = "User created successfully!";
        $response["profile"] = $user;
    } else {
        $response["message"] = "Sorry! Failed to create your account.";
    }
    
    
} else {
    $response["message"] = "Sorry! OTP is missing.";
}


echo json_encode($response);
?>


5. Testing the REST API

To test our API, install Postman chrome extension which allows you to test the endpoints by passing the parameters required.

Request SMS

URL Method Parameters Description
http://localhost/android_sms/request_sms.php POST name, email, mobile Request SMS

The below json should be produced when SMS sent successfully.

{
    "error": false,
    "message": "SMS request is initiated! You will be receiving it shortly."
}



Verifying user OTP

URL Method Parameters Description
http://localhost/android_sms/verify_otp.php POST otp verifying user verification code

When the OTP is verified successfully, the complete user profile information should be produced in the json.

{
    "error": false,
    "message": "User created successfully!",
    "profile": {
        "name": "Ravi Tamada",
        "email": "ravi@androidhive.info",
        "mobile": "0000000000",
        "apikey": "088d196bacbe6bf08657720c9d562390",
        "status": 0,
        "created_at": "2015-07-30 22:18:59"
    }
}

Watch the below video know how to use Postman to test the endpoints.



This completes the server side part. In the 2nd part we are going to learn how to create the android app by integrating the API we have just built.

Viewing all 27 articles
Browse latest View live