AndroidHive | Tutorials, Games, Apps, Tips http://www.androidhive.info Mon, 21 Jul 2014 20:53:53 +0000 en-US hourly 1 http://wordpress.org/?v=3.9.1 Android Building Multi-Language Supported Apphttp://www.androidhive.info/2014/07/android-building-multi-language-supported-app/ http://www.androidhive.info/2014/07/android-building-multi-language-supported-app/#comments Mon, 21 Jul 2014 20:53:53 +0000 http://www.androidhive.info/?p=15146 Android is one of the few popular mobile operating systems having millions of users over 190 countries and growing day by day. So when you are aiming your app to be globally successful, it is always a good idea to make the app localized.

While localizing, you should consider using appropriate text, audio, currency, numbers and graphics depending upon the region or country. But this tutorial only covers localizing strings i.e supporting multiple languages. Localizing with Resources explains about other things should be considered when localizing your app.

Android Building Multi-Language Supported

In this article we are going to build a Multi-Language supported app that supports French, Deutsch (German), Hindi and Japanese.



1. How String Localization Works

By default android considers English as primary language and loads the string resources from res ⇒ values ⇒ strings.xml. When you want to add support for another language, you need to create a values folder by appending an Hyphen and the ISO language code. For example if you want to add support for French, you should create a values folder named values-fr and keep a strings.xml file in it with all the strings translated into French language.

In brief the localization works as follows

1. When user changes the device language through Settings ⇒ Language & Input, android OS itself checks for appropriate language resources in the app. (Let’s say user is selecting French)

2. If the app supports selected language, android looks for it’s string resources in values-(ISO language Code) folder in the project. (For french it loads the string values from values-fr/string.xml)

3. If the supported language strings.xml misses any string value, android always loads the missing strings from default strings.xml file i.e values/strings.xml

So it is mandatory that the default stings.xml file should contains all the string values that app uses. Other wise the app will crash with Force Close error.

Do’s:

While you are supporting multiple languages, you should consider below as a best practice while defining the strings. Always declare the string in strings.xml only.

<string name="note_email">Enter your email address</string>

When referring it in xml, use @strings notation.

<TextView ...   android:text="@string/note_email"  />

When defining the string through java code, use R.string

emailNote.setText(R.string.note_email);

Don’ts:

Never hard code the string in xml or in java code which make the translation difficult.

<TextView ...   android:text="Enter your email address"  />
emailNote.setText("Enter your email address");    



So let’s create a new project and try with an example.

2. Creating New Project

1. Create a new project in Eclipse by going to File ⇒ New ⇒ Android Application Project and fill the required information.

2. Add following colors in colors.xml located under values. If you don’t see colors.xml, create a new file and add the below colors.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="white">#ffffff</color>
    <color name="bg_gradient_start">#b21331</color>
    <color name="bg_gradient_end">#820d2a</color>
    <color name="bg_button_login">#380813</color>
</resources>

3. Under drawable folder create three files named bg_button_rounded.xml,bg_form_rounded.xml, bg_gradient.xml with following contents. These files are not related to language support, but just to give nice gradients background and rounded corners to buttons, input boxes.

bg_button_rounded.xml

<?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_button_login" >
    </solid>
 
    <!-- If you want to add some padding -->
    <padding
        android:left="5dp"
        android:top="5dp"
        android:right="5dp"
        android:bottom="5dp"    >
    </padding>
 
    <!-- Here is the corner radius -->
    <corners
        android:radius="6dp"   >
    </corners>
 
</shape>

bg_form_rounded.xml

<?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/white" >
    </solid>
 
    <!-- If you want to add some padding -->
    <padding
        android:left="5dp"
        android:top="5dp"
        android:right="5dp"
        android:bottom="5dp"    >
    </padding>
 
    <!-- Here is the corner radius -->
    <corners
        android:radius="6dp"   >
    </corners>
 
</shape>

bg_gradient.xml

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <gradient
        android:gradientRadius="750"
        android:endColor="@color/bg_gradient_end"
        android:startColor="@color/bg_gradient_start"
        android:type="radial" />
</shape>

4. Open strings.xml located under values folder and add following strings. These are default English language strings.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Multi Language App</string>
    <string name="action_settings">Settings</string>
    
    <string name="welcome">Welcome!</string>
    <string name="email">Email Address</string>
    <string name="password">Password</string>
    <string name="login">Login</string>
    <string name="signup">Don\'t have account? Sign Up</string>

</resources>

5. Now under res folder create three folders named values-de, values-fr, values-hi, values-ja and a strings.xml file in each of the folders.

Your project should look like this once you created the required files/folders.

android mult language support project structure

Now translate the strings into respected languages and place them in appropriate strings.xml files.

Deutsch values-de/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <string name="welcome">Willkommen!</string>
    <string name="email">Email Addresse</string>
    <string name="password">passowrd</string>
    <string name="login">Login</string>
    <string name="signup">müssen nicht angemeldet? Anmeldung</string>

</resources>



French values-fr/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <string name="welcome">accueil</string>
    <string name="email">adresse e-mail</string>
    <string name="password">mot de passe</string>
    <string name="login">connexion</string>
    <string name="signup">Ne pas avoir un compte? signer</string>

</resources>



Hindi values-hi/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <string name="welcome">स्वागतम</string>
    <string name="email">ईमेल पता</string>
    <string name="password">पासवर्ड</string>
    <string name="login">लॉगिन</string>
    <string name="signup">खाता नहीं है? साइन अप करें</string>

</resources>



Japanese values-ja/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
    <string name="welcome">歓迎</string>
    <string name="email">電子メールアドレス</string>
    <string name="password">パスワード</string>
    <string name="login">ログイン</string>
    <string name="signup">アカウントをお持ちでない場合は?サインアップ</string>

</resources>

6. Open your main activity layout file (in my case activity_main.xml) and add the following content to create a simple layout. This layout contains a title and a login form.

<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="@drawable/bg_gradient"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="40dp"
            android:text="@string/welcome"
            android:textColor="@color/white"
            android:textSize="45dp"
            android:textStyle="bold" />

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/bg_form_rounded"
            android:orientation="vertical" >

            <EditText
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="10dp"
                android:background="@null"
                android:hint="@string/email"
                android:padding="5dp" 
                android:singleLine="true"/>

            <EditText
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:background="@null"
                android:hint="@string/password"
                android:inputType="textPassword"           
                android:padding="5dp" />
        </LinearLayout>

        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="25dp"
            android:background="@drawable/bg_button_rounded"
            android:text="@string/login" 
            android:textColor="@color/white"/>
    </LinearLayout>
    
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/signup"
        android:layout_alignParentBottom="true"
        android:gravity="center_horizontal"
        android:layout_marginBottom="25dp"
        android:textColor="@color/white"/>

</RelativeLayout>

7. Open your MainActivity.java and make sure that it has following code. This code will be added automatically when you create new project.

package info.androidhive.multilanguageapp;

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

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		getActionBar().hide();
	}
}

Now if you run the project you should see the app in English (assuming that your device is set to English language)

android-multi-language-app-english


3. Testing The Other Languages

In order to see the app in other languages follow below steps or check the above demo video.

1. On the device go to Settings ⇒ Language & Input
2. Select the Language and choose the language that you supported in the app.

android-multi-language-app-hindi
android-multi-language-app-deutsch
android-multi-language-app-french
android-multi-language-app-japanese


4. Android Localization Language ISO Codes

Below table give you ISO languages codes for all the languages that android supports.

LanguageLocalevalues/strings.xml
Germandevalues-de/strings.xml
Chinesezhvalues-zh/strings.xml
Czechcsvalues-cs/strings.xml
Dutchnlvalues-nl/strings.xml
Frenchfrvalues-fr/strings.xml
Italianitvalues-it/strings.xml
Japanesejavalues-ja/strings.xml
Koreankovalues-ko/strings.xml
Polishplvalues-pl/strings.xml
Russianruvalues-ru/strings.xml
Spanishesvalues-es/strings.xml
Arabicarvalues-ar/strings.xml
Bulgarianbgvalues-bg/strings.xml
Catalancavalues-ca/strings.xml
Croatianhrvalues-hr/strings.xml
Danishdavalues-da/strings.xml
Finnishfivalues-fi/strings.xml
Greekelvalues-el/strings.xml
Hebrewiwvalues-iw/strings.xml
Hindihivalues-hi/strings.xml
Hungarianhuvalues-hu/strings.xml
Indonesianinvalues-in/strings.xml
Latvianlvvalues-lv/strings.xml
Lithuanianltvalues-lt/strings.xml
Norwegiannbvalues-nb/strings.xml
Portugueseptvalues-pt/strings.xml
Romanianrovalues-ro/strings.xml
Serbiansrvalues-sr/strings.xml
Slovakskvalues-sk/strings.xml
Slovenianslvalues-sl/strings.xml
Swedishsvvalues-sv/strings.xml
Tagalogtlvalues-tl/strings.xml
Thaithvalues-th/strings.xml
Turkishtrvalues-tr/strings.xml
Ukrainianukvalues-uk/strings.xml
Vietnamesevivalues-vi/strings.xml

Source: http://bit.ly/1qYfHDL

5. Translation Services

Right now I used Google Translate service to translate the strings into other languages. But if you want more accurate and meaningful translation always go for professional services like Professional translations through Google Play

Finally Localization Checklist gives you list of things to be verified before the app goes live when localization considered.

]]>
http://www.androidhive.info/2014/07/android-building-multi-language-supported-app/feed/ 0
StealthGenie- An Android Monitoring App That Keep Things On Trackhttp://www.androidhive.info/2014/07/stealthgenie-an-android-monitoring-app-that-keep-things-on-track/ http://www.androidhive.info/2014/07/stealthgenie-an-android-monitoring-app-that-keep-things-on-track/#comments Sat, 19 Jul 2014 14:33:51 +0000 http://www.androidhive.info/?p=15446 You might have seen a lot of Android apps in the market that are providing employers instant business solutions and ease of communication. Similarly there are apps that are providing digital parenting solutions. Parents have also given Androids to their kids and believe that the android is helping them out in the educational hurdles. Well you are not wrong! But if you are a business employer who is heading an immense team of employees or a busy parent who is worried about your kids’ usage of mobile technology, the Android Spy App is going to be something imperative in your life.

You can’t think of monitoring your employees by hiring a private investigator or sneaking your kids physically. In this era of digitalization you have to think about spy app. When it comes to the spy app, you will see multiple apps in the market. What is your first priority while choosing any spy app? Obviously, the one that is undetectable. You can think of StealthGenie that is not only undetectable, but also provides instant monitoring solutions to its users.

This is how one parent is using StealthGenie and monitoring his kid who has been studying in a boarding school.

Here is what employers have to say about StealthGenie that is helping them in running their delivery business smoothly.

So, here are the 9 exclusive features that compel one to go for StealthGenie. These features outweigh the use of mobile technology by creating a balance in your life.

1. Check Call Logs

Won’t you get concerned when your kid hesitates while talking in front of you? It becomes necessary for you to check the call details. With this feature you can keep a check on what numbers are calling or what is the frequency of calls by particular number. You can also see the time and date of the phone call.

2. Monitor Chats On Instant Messengers

Do you know sexting has gone mainstream? The sexting scandals are on the rise. According to teenage sexting statistics, 71% of teen girls and 67% of teen boys who have sent or posted sexually suggestive content say they have sent/posted this content to a boyfriend/girlfriend. This is quite alarming. Similarly, you never know if your employee is sending messages that can put your market reputation at risk. You can monitor WhatsApp chats, Viber chats, Skype chats or even Facebook messages with this feature. This can help you out in formulating limitations guidelines accordingly.

3. Record The Calls

Why your kid is looking worried after the phone call? What if your employee is talking to your competitor? If you want to know more, put their calls on the recording with the call recording feature. You can even put specific numbers on call recording and can listen to these recordings later.

4. Put A Digital Fence

Geo tracking is the common feature that it offers and can tell you the exact location of the mobile, but Geo fencing is like your digital guard that will notify you if your kid enters into the particular area. All you need to do is mark the particular areas in red and green zones. You can also create a fence in more than one location simultaneously. You just have to choose the preference like how you want to be notified. You can get the alerts via SMS, via emails or even both. The choice is completely up to you.

5. Read Incoming/Outgoing Emails

You can read all kinds of business communication that your employee is making with clients and with your partner companies. Likewise you will get to know if your kids are receiving spamming emails.

6. Look At Multimedia

You can get access to photos, videos or kind of music your kids have saved on their mobile. Moreover, you can check the employee phone, if it has any confidential information on their mobile in the form of photos or recordings of any business meetings.

7. Monitor Internet Activities

With this feature you can monitor what kind of websites your kids and employees are visiting. You can check that whether the visited websites are age appropriate for your kids or not. You can even look at the bookmarked websites.

8. Get Instant Alerts

Apart from Geo-fencing feature, if you don’t want your kid to use particular words in their emails and messages, you can put them in a list of suspicious words. If your kid will use these words while texting or in emails you will be notified via SMS and email about its use. Moreover, you can also get alerts if a particular number is calling.

9. Remote Control The Phone

This feature works amazingly. If your kid’s phone is lost or your employee phone is lost what you are most concerned about? Obviously it’s the multimedia, the confidential business data and contact list. The phone may also have bank account information. With remote control feature you can send command to the phone so that it can get locked. You can also send remote command in order to wipe out all phone data. You know the best part? The data will remain in the online control panel. Means you don’t need to worry about data backup if you want.

9

Exclusivity+Affordability- What Else Could Be Better?

I am sure you are going to install it now! Let me surprise you again by telling that it is conveniently affordable. Packages start from as low as $8 per month. You can hardly say ‘NO’ to this exclusive affordable package.

Now the Smartphone users can get amazing monitoring experience with StealthGenie Spy App. Remember, it’s never too late to take a good decision for your business and family.

]]>
http://www.androidhive.info/2014/07/stealthgenie-an-android-monitoring-app-that-keep-things-on-track/feed/ 0
Android Speech To Text Tutorialhttp://www.androidhive.info/2014/07/android-speech-to-text-tutorial/ http://www.androidhive.info/2014/07/android-speech-to-text-tutorial/#comments Sun, 13 Jul 2014 11:37:10 +0000 http://www.androidhive.info/?p=8464 Android comes with an inbuilt feature speech to text through which you can provide speech input to your app. With this you can add some of the cool features to your app like adding voice navigation(Helpful when you are targeting disabled people), filling a form with voice input etc.,

In the background how voice input works is, the speech input will be streamed to a server, on the server voice will be converted to text and finally text will be sent back to our app.

If you want to do the other way i.e converting text to speech, follow my previous tutorial Android Text to Speech

speech to text banner

I have created a simple app to demonstrate this tutorial. Below is the screenshot of the app which contains a simple button to invoke speech input and a TextView to display the converted speech text.

android speech to text

So let’s start by creating simple app.

Sample Application

1. Create a new project in Eclipse by going to File ⇒ New ⇒ Android Application Project and give required information.

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">Speech To Text</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
    <string name="speech_prompt">Say something&#8230;</string>
    <string name="speech_not_supported">Sorry! Your device doesn\'t support speech input</string>
    <string name="tap_on_mic">Tap on mic to speak</string>
</resources>

3. Open colors.xml located under res ⇒ values and add below colors. If you don’t see colors.xml, create a new file and add the values.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="white">#ffffff</color>
    <color name="bg_gradient_start">#31244e</color>
    <color name="bg_gradient_end">#6b394c</color>
</resources>

4. Now open the layout file for main activity(activity_main.xml) and add below code to create a simple layout.

<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="@drawable/bg_gradient"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/txtSpeechInput"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:textColor="@color/white"
        android:textSize="26dp"
        android:textStyle="normal" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="60dp"
        android:gravity="center"
        android:orientation="vertical" >

        <ImageButton
            android:id="@+id/btnSpeak"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@null"
            android:src="@drawable/ico_mic" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="@string/tap_on_mic"
            android:textColor="@color/white"
            android:textSize="15dp"
            android:textStyle="normal" />
    </LinearLayout>

</RelativeLayout>

5. Finally open your MainActivity.java and do the following changes. In simple adding speech input will be done in two steps.

Step 1: Starting RecognizerIntent
First we need to create a RecognizerIntent by setting necessary flags such as
ACTION_RECOGNIZE_SPEECH – Simply takes user’s speech input and returns it to same activity
LANGUAGE_MODEL_FREE_FORM – Considers input in free form English
EXTRA_PROMPT – Text prompt to show to the user when asking them to speak

Step 2: Receiving the speech response
Once the speech input is done we have to catch the response in onActivityResult and take appropriate action needed.

package info.androidhive.speechtotext;

import java.util.ArrayList;
import java.util.Locale;

import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.view.Menu;
import android.view.View;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

	private TextView txtSpeechInput;
	private ImageButton btnSpeak;
	private final int REQ_CODE_SPEECH_INPUT = 100;

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

		txtSpeechInput = (TextView) findViewById(R.id.txtSpeechInput);
		btnSpeak = (ImageButton) findViewById(R.id.btnSpeak);

		// hide the action bar
		getActionBar().hide();

		btnSpeak.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				promptSpeechInput();
			}
		});

	}

	/**
	 * Showing google speech input dialog
	 * */
	private void promptSpeechInput() {
		Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
		intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
				RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
		intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
		intent.putExtra(RecognizerIntent.EXTRA_PROMPT,
				getString(R.string.speech_prompt));
		try {
			startActivityForResult(intent, REQ_CODE_SPEECH_INPUT);
		} catch (ActivityNotFoundException a) {
			Toast.makeText(getApplicationContext(),
					getString(R.string.speech_not_supported),
					Toast.LENGTH_SHORT).show();
		}
	}

	/**
	 * Receiving speech input
	 * */
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		switch (requestCode) {
		case REQ_CODE_SPEECH_INPUT: {
			if (resultCode == RESULT_OK && null != data) {

				ArrayList<String> result = data
						.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
				txtSpeechInput.setText(result.get(0));
			}
			break;
		}

		}
	}

	@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;
	}

}

Run the app in a real device. Make sure that the device has good internet connectivity while you are testing.


Enabling Offline Mode

Right now all the devices are not supporting offline speech input. However you can follow this discussion to enable offline speech input for supported devices.

I have downloaded speech input packages on my Nexus 5 and offline speech is working fine.

1. On your device go to Settings -> Language and Input. Click on icon on Google voice input.
2. Under ALL tab select the language you want to download.
3. Once the language package downloaded, you can see it under INSTALLED tab.

android speech to text offline mode
android speech to text offline mode
android speech to text offline mode
]]>
http://www.androidhive.info/2014/07/android-speech-to-text-tutorial/feed/ 0
Android Facebook like Custom ListView Feed using Volleyhttp://www.androidhive.info/2014/06/android-facebook-like-custom-listview-feed-using-volley/ http://www.androidhive.info/2014/06/android-facebook-like-custom-listview-feed-using-volley/#comments Sun, 22 Jun 2014 17:32:09 +0000 http://www.androidhive.info/?p=5297 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.

]]>
http://www.androidhive.info/2014/06/android-facebook-like-custom-listview-feed-using-volley/feed/ 0
DU Speed Booster For Android Smartphones: Reviewhttp://www.androidhive.info/2014/06/du-speed-booster-for-android-smartphones-review/ http://www.androidhive.info/2014/06/du-speed-booster-for-android-smartphones-review/#comments Mon, 09 Jun 2014 14:55:27 +0000 http://www.androidhive.info/?p=5730 There is availability of almost all kinds of Android apps, be it messaging, gaming, editing, etc. These numerous apps running on your Smart phone affect the speed of your device. The slow working of phone is one of the major problems faced by the users. Hence, to combat this drawback of consequence of usage of heavy apps, an app named DU Speed Booster has been developed.
In order to increase the working speed of any phone, it is necessary to clear away the cache and useless files. This action will result into increased free space in the memory and even improve battery life. This is what the free DU Speed Booster app does; it increments the speed up to 50%-60% by carrying out those actions.

DU SPEED BOOSTER FOR ANDROID SMARTPHONES

When it comes to any kind of application, the user first looks at the level of convenience with its interface. Apps with user-friendly interface are the most popular; one among those is the DU Speed Booster. It has got an attractive, eye-catching background with a time saving one-touch interface. It’s too easy and not at all complex to switch through different options given. Available in more than 10 languages, it attracts almost all the Android users around the world. Additionally, it has got an animated floating icon, called as spaceship, which is located on your home page du_speed_icon. It is designed in such a way that users find it pleasing to interact with this application.

As you can see above, the DU Speed Booster app dashboard displays six main labels: Accelerator, Trash Cleaner, App Manager, Game Booster, Security and Battery Saver.


Accelerator:

This feature acts as a speed booster. It does the work of a task manager by washing away all the background tasks and unwanted app processes. Take a glance at the image below, it provides three major advices: Process Manager, Auto-start Manager and Freeze App Manager. These, altogether, carry out the entire job of acceleration. In simple words, the Process Manager is a task killer; the Auto-start Manager is an auto-start app disabler and Freeze App Manager freezes the unwanted app. Hence, the memory is cleaned up giving much more free available space and speed is accelerated.

Trash Cleaner:

This feature boosts your Android device speed by particularly clearing up the remaining files of uninstalled apps and even the unused files left over after the installation of an app. It deletes the cache file from both the phone memory and SD card, thus freeing some storage space and helping in speed boosting!

App Manager:

As the name suggests, it manages the apps on your phone, so that the unwanted app files don’t occupy much space and affect the speed of your device. It is much like an organizer that not only regulates the apps on both phone memory and SD card, but also offers an easy and simple install or uninstall process by taking advantage of its single- touch interface. In order to reduce the load on phone memory, it provides an option to transfer the apps on your phone to the SD card. This will lessen down the burden on phone and make its working fast and efficient.

Download it for free on Google Play

Game Booster:

This can be considered the best feature added by the team of DU app developers, in November 2013, after the launch of the app. Gaming on any Smartphone is like an addiction to users, especially the teenagers, but on the contrary they face a lot of problems in its efficiency and steady go. Thus, the Game Booster smoothens your gaming by supporting all the necessary game operations. Along with manual selection of games, it also has an auto-recognition game system.

Security:

The Security label provides the Antivirus protection to your Android device. It contains three categories: Antivirus, Permission Manager and a Blocker. The Antivirus regulates scanning process of the system and gets the phone rid of the viruses. The Permission Manager describes every app on your phone in detail. Lastly, the Blocker enables blocking mode for particular calls and SMS that you mention to be blocked.

Battery Saver:

Speeding up the phone is not only an issue, but saving and extending the battery life is too. A long battery life will give an efficient and faster move to every app you run. Therefore, this factor has been included in this speed booster app.

The Android users can now gear up their Smartphones with the DU Speed Booster and get an amazing gaming experience with smooth working of each and every application on their phones. Try this soon, its worthy than any other!

Visit DU Speed Booster Official Site

]]>
http://www.androidhive.info/2014/06/du-speed-booster-for-android-smartphones-review/feed/ 0
Android Streaming Live Camera Video to Web Pagehttp://www.androidhive.info/2014/06/android-streaming-live-camera-video-to-web-page/ http://www.androidhive.info/2014/06/android-streaming-live-camera-video-to-web-page/#comments Sun, 01 Jun 2014 16:33:09 +0000 http://www.androidhive.info/?p=4449 Long time back I am looking for a solution to stream android live camera video to a webpage over internet (not wifi). After doing some research I came up with a working solution by combining multiple components together. The tutorial helps you get started with real time video streaming.

If you are beginner, this article will introduce you to lot new things like real time streaming protocols (RTSP, RTMP), Wowza Media Engine, libstreaming, WAMP and jwplayer.

android live video streaming

Below are the two protocols on which real time streaming really works. Go through the wikipedia links to get enough knowledge on underlaying technology behind real time streaming.

RTSP Protocol
Real Time Streaming Protocol is a networking protocol mainly used to stream real time media data like audio or video. It establishes a streaming session between client and server. In this tutorial we use this protocol while sending video stream from android mobile to streaming server.

Lean more about Real Time Streaming Protocol

RTMP Protocol
Real Time Messaging Protocol was developed by Adobe for Flash Player to transmit the realtime media (audio, video) between server and flash player. This protocol we use to receive video stream from server to flash player.

Lean more about Real Time Messaging Protocol

Below diagram is a high level architecture diagram of android video streaming. First android streams camera video to wowza media engine. Second wowza decodes the video and starts a streaming channel. Thirst webpage consumes wowza stream and plays the video on the page.

android-video-streaming-architecture


1. Downloading Libstreaming library

Building a RTSP library involves deep understanding of real time streaming protocols and good command over multiple java media APIs which is not easy for every beginner. Luckly Fyhertz made our lives easier by providing an excellent RTSP library called libstreaming for android. Using this library, streaming video / audio from android mobile can done with very few lines of code.

Download the library and keep it aside.


2. Installing & Configuring Wowza Media Engine

Wowza Media Engine is very popular streaming engine which can stream high quality video and audio. In our project it acts as server side streaming framework which receives video from android device and starts a streaming service which will be again consumed by webpage to display the video.

Wowza also comes with an admin panel called Wowza Media Engine Manager to control streaming channels, publishers and other stuff. Unfortunately wowza is not a free software, you will have to buy a commercial license. But don’t worry, it comes with a trail period of 6 months which is more than enough for testing.

2.1 Registration
In order to use Wowa media engine you need to register and get a license key first. The key will be sent to your email address after the registration. So make sure that you entered a valid email address instead of abc@abc.com :)

Once you complete registration here, you can find the license key in the email.

wowza registration license key android

2.2 Downloading & Installing
1. Download Wowza Media Engine from here
2. Run the installer and enter the license key when it asks for it.

2.3 Creating publisher username and password
Once wowza installed, open Wowza Streaming Engine Manager from Start => All Programs. This opens up an admin panel in the browser. While streaming from android mobile, the streaming video needs to be authenticated with Wowza before start decoding it. So we need to create a publisher username and password first. These credentials we will use in our android app later.

To create a publisher, click on Server ⇒ Publisher ⇒ Add Publisher and enter credentials.

wowza creating publisher android video streaming
wowza creating publisher android video streaming

2.4 Creating streaming channel
You can create your own streaming url in the admin panel. But Wowza already creates a channel(app) for you named live. I am going to use the same app in this tutorial. If you want, you can create your own.

Here we completes wowza setup. Now let’s start the android project.

3. Creating Android project

1. In Eclipse create a new project by navigating to File ⇒ New ⇒ Android Application Project and fill required details.

2. Now import the downloaded libstreaming project into your Eclipse workspace. File ⇒ Import ⇒ Android ⇒ Existing Android Code Into Workspace and browse to libstreaming.

3. Now add the libstreaming project as a library project to our project. Right click on our project ⇒ Properties. It will open up a dialog window. On the left select Android and on the Right under Library add libstreaming project.

android libstreaming library

4. As we are accessing the camera, we need to add the permissions in AndroidManifest.xml first. Add INTERNET, WRITE_EXTERNAL_STORAGE, RECORD_AUDIO, CAMERA permissions in your mainifest file.

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

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

    <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />
    <uses-feature
        android:name="android.hardware.camera.autofocus"
        android:required="false" />

    <supports-screens
        android:largeScreens="true"
        android:normalScreens="true"
        android:smallScreens="true"
        android:xlargeScreens="true" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CAMERA" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="info.androidhive.androidvideostreaming.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="landscape" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>



5. Open the activity layout file main activity and add the following content (My layout file name activity_main.xml). Here we are adding libstreaming surfaceview for camera preview.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/surface_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:background="@android:color/black" >

    <net.majorkernelpanic.streaming.gl.SurfaceView
        android:id="@+id/surface"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

</FrameLayout>



6. Now I am creating a class to keep wowza configuration values like streaming url, publisher username and password. Create a class named AppConfig.java and add the following. Replace the streaming url (keep your PC ip address), username and password with your wowza details.

public class AppConfig {
	public static final String STREAM_URL = "rtsp://192.168.43.233:1935/live/android_test";
	public static final String PUBLISHER_USERNAME = "ravi";
	public static final String PUBLISHER_PASSWORD = "passtemp";
}



7. Open your main activity class and implement the class from RtspClient.Callback, Session.Callback, SurfaceHolder.Callback and initialize required variables and class instances.

public class MainActivity extends Activity implements RtspClient.Callback,
		Session.Callback, SurfaceHolder.Callback {
import net.majorkernelpanic.streaming.Session;
import net.majorkernelpanic.streaming.gl.SurfaceView;
import net.majorkernelpanic.streaming.rtsp.RtspClient;
import android.app.Activity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity implements RtspClient.Callback,
		Session.Callback, SurfaceHolder.Callback {

	// log tag
	public final static String TAG = MainActivity.class.getSimpleName();

	// surfaceview
	private static SurfaceView mSurfaceView;

	// Rtsp session
	private Session mSession;
	private static RtspClient mClient;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		// getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		setContentView(R.layout.activity_main);

		mSurfaceView = (SurfaceView) findViewById(R.id.surface);

		mSurfaceView.getHolder().addCallback(this);

		// Initialize RTSP client
		initRtspClient();
	}

	@Override
	protected void onResume() {
		super.onResume();
		
		toggleStreaming();
	}
	
	@Override
	protected void onPause(){
		super.onPause();
		
		toggleStreaming();
	}

	@Override
	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
	}

	@Override
	public void surfaceCreated(SurfaceHolder arg0) {

	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {

	}

	@Override
	public void onBitrareUpdate(long bitrate) {

	}

	@Override
	public void onSessionError(int reason, int streamType, Exception e) {

	}

	@Override
	public void onPreviewStarted() {

	}

	@Override
	public void onSessionConfigured() {

	}

	@Override
	public void onSessionStarted() {

	}

	@Override
	public void onSessionStopped() {

	}

	@Override
	public void onRtspUpdate(int message, Exception exception) {

	}
}



8. initRtspClient() will initialize Rtsp client which takes care of streaming video to wowza media server. Add the following code in your main activity. toggleStreaming() takes care of toggling video stream on/off. Finally in OnDestroy() method we have to release rtsp client, session and surfaceview.

private void initRtspClient() {
		// Configures the SessionBuilder
		mSession = SessionBuilder.getInstance()
				.setContext(getApplicationContext())
				.setAudioEncoder(SessionBuilder.AUDIO_NONE)
				.setAudioQuality(new AudioQuality(8000, 16000))
				.setVideoEncoder(SessionBuilder.VIDEO_H264)
				.setSurfaceView(mSurfaceView).setPreviewOrientation(0)
				.setCallback(this).build();

		// Configures the RTSP client
		mClient = new RtspClient();
		mClient.setSession(mSession);
		mClient.setCallback(this);
		mSurfaceView.setAspectRatioMode(SurfaceView.ASPECT_RATIO_PREVIEW);
		String ip, port, path;

		// We parse the URI written in the Editext
		Pattern uri = Pattern.compile("rtsp://(.+):(\\d+)/(.+)");
		Matcher m = uri.matcher(AppConfig.STREAM_URL);
		m.find();
		ip = m.group(1);
		port = m.group(2);
		path = m.group(3);

		mClient.setCredentials(AppConfig.PUBLISHER_USERNAME,
				AppConfig.PUBLISHER_PASSWORD);
		mClient.setServerAddress(ip, Integer.parseInt(port));
		mClient.setStreamPath("/" + path);
	}

	private void toggleStreaming() {
		if (!mClient.isStreaming()) {
			// Start camera preview
			mSession.startPreview();

			// Start video stream
			mClient.startStream();
		} else {
			// already streaming, stop streaming
			// stop camera preview
			mSession.stopPreview();

			// stop streaming
			mClient.stopStream();
		}
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		mClient.release();
		mSession.release();
		mSurfaceView.getHolder().removeCallback(this);
	}



Complete Code
Following is the full code of my main activity.

package info.androidhive.androidvideostreaming;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.majorkernelpanic.streaming.Session;
import net.majorkernelpanic.streaming.SessionBuilder;
import net.majorkernelpanic.streaming.audio.AudioQuality;
import net.majorkernelpanic.streaming.gl.SurfaceView;
import net.majorkernelpanic.streaming.rtsp.RtspClient;
import net.majorkernelpanic.streaming.video.VideoQuality;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity implements RtspClient.Callback,
		Session.Callback, SurfaceHolder.Callback {
	// log tag
	public final static String TAG = MainActivity.class.getSimpleName();

	// surfaceview
	private static SurfaceView mSurfaceView;

	// Rtsp session
	private Session mSession;
	private static RtspClient mClient;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
		// getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		setContentView(R.layout.activity_main);

		mSurfaceView = (SurfaceView) findViewById(R.id.surface);

		mSurfaceView.getHolder().addCallback(this);

		// Initialize RTSP client
		initRtspClient();		

	}

	@Override
	protected void onResume() {
		super.onResume();
		
		toggleStreaming();
	}
	
	@Override
	protected void onPause(){
		super.onPause();
		
		toggleStreaming();
	}

	private void initRtspClient() {
		// Configures the SessionBuilder
		mSession = SessionBuilder.getInstance()
				.setContext(getApplicationContext())
				.setAudioEncoder(SessionBuilder.AUDIO_NONE)
				.setAudioQuality(new AudioQuality(8000, 16000))				
				.setVideoEncoder(SessionBuilder.VIDEO_H264)
				.setSurfaceView(mSurfaceView).setPreviewOrientation(0)
				.setCallback(this).build();

		// Configures the RTSP client
		mClient = new RtspClient();
		mClient.setSession(mSession);
		mClient.setCallback(this);
		mSurfaceView.setAspectRatioMode(SurfaceView.ASPECT_RATIO_PREVIEW);
		String ip, port, path;

		// We parse the URI written in the Editext
		Pattern uri = Pattern.compile("rtsp://(.+):(\\d+)/(.+)");
		Matcher m = uri.matcher(AppConfig.STREAM_URL);
		m.find();
		ip = m.group(1);
		port = m.group(2);
		path = m.group(3);

		mClient.setCredentials(AppConfig.PUBLISHER_USERNAME,
				AppConfig.PUBLISHER_PASSWORD);
		mClient.setServerAddress(ip, Integer.parseInt(port));
		mClient.setStreamPath("/" + path);
	}

	private void toggleStreaming() {
		if (!mClient.isStreaming()) {
			// Start camera preview
			mSession.startPreview();

			// Start video stream
			mClient.startStream();
		} else {
			// already streaming, stop streaming
			// stop camera preview
			mSession.stopPreview();

			// stop streaming
			mClient.stopStream();
		}
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		mClient.release();
		mSession.release();
		mSurfaceView.getHolder().removeCallback(this);
	}

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

	@Override
	public void onSessionError(int reason, int streamType, Exception e) {
		switch (reason) {
		case Session.ERROR_CAMERA_ALREADY_IN_USE:
			break;
		case Session.ERROR_CAMERA_HAS_NO_FLASH:
			break;
		case Session.ERROR_INVALID_SURFACE:
			break;
		case Session.ERROR_STORAGE_NOT_READY:
			break;
		case Session.ERROR_CONFIGURATION_NOT_SUPPORTED:
			break;
		case Session.ERROR_OTHER:
			break;
		}

		if (e != null) {
			alertError(e.getMessage());
			e.printStackTrace();
		}
	}

	private void alertError(final String msg) {
		final String error = (msg == null) ? "Unknown error: " : msg;
		AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
		builder.setMessage(error).setPositiveButton("Ok",
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
					}
				});
		AlertDialog dialog = builder.create();
		dialog.show();
	}

	@Override
	public void onRtspUpdate(int message, Exception exception) {
		switch (message) {
		case RtspClient.ERROR_CONNECTION_FAILED:
		case RtspClient.ERROR_WRONG_CREDENTIALS:
			alertError(exception.getMessage());
			exception.printStackTrace();
			break;
		}
	}

	@Override
	public void onPreviewStarted() {
	}

	@Override
	public void onSessionConfigured() {
	}

	@Override
	public void onSessionStarted() {
	}

	@Override
	public void onSessionStopped() {
	}

	@Override
	public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
	}

	@Override
	public void onBitrareUpdate(long bitrate) {
	}

}

Now we are done with our android application. Let’s create a web application which displays the streaming video on web page.

4. Creating web app

4.1 Installing WAMP:
I have already explained lot of times what is WAMP and it’s use (here & here). 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 http://localhost/ in the browser.



4.2 jWPlayer
On the web page to play streamed I am using jwplayer which supports RTMP protocol. jwplayer is a flash player with javascript API enabled which means you can control the player with javascript methods.

Create an account at http://www.jwplayer.com/sign-up/ and download the jWplayer. Once you login into jwplayer.com, you can download a copy from https://account.jwplayer.com/#/account. Click on Download Self-Hosted Player at the bottom of the page.



1. Now create a folder in your wamp/www directory (Normally wamp will be located at C:/wamp). I am creating a folder named wowza_web_app inside www.

2. Inside wowza_web_app create two more folders named css and js.

3. Paste jwplayer files inside js folder. You have to copy jwplayer.js, jwplayer.html5.js and jwplayer.flash.swf. Also paste jquery-1.9.1.min. Download jquery from here

4. Create an html file named index.html in wowza_web_app and paste the following content. This page will have jwplayer and other UI to play and stop streaming video.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Android Video Streaming</title>
        <script src="js/jquery-1.9.1.min.js" type="text/javascript"></script>
        <script type="text/javascript" src="js/jwplayer.js"></script>       
        <script src="js/player.js" type="text/javascript"></script>
        <script>jwplayer.key = ""</script>
        <link rel="stylesheet" type="text/css" href="css/style.css" />       
    </head>

    <body>
        <!-- Header -->
        <div id="header">
            <div class="container">
                <h1>Android Video Streaming</h1>
            </div>
        </div>
        <!-- ./Header -->

        <!-- Video Preview -->
        <div class="container">            
            <div id="video_preview">                    
                <div id="player"></div><div class="clear"></div>
                <br/><br/><br/>
                <input type="text" id="stream_url" value="rtmp://192.168.43.233:1935/live/android_test"/><br/>
                <input type="button" id="btn_start" class="" value="Start" />
                <input type="button" id="btn_stop" class="" value="Stop"/>
            </div>
            <div class="clear"></div>
        </div>
        <!-- ./Video Preview -->

        <div class="container">
            <div class="info"><p>
                    Tutorial: <a href="#">http://www.androidhive.info/?p=4449&preview=true</a><br/>
                    <a href="http://www.wowza.com/">Wowza Media Engine</a><br/>
                    Thanks <a href="Fyhertz">@Fyhertz</a> for <a href="https://github.com/fyhertz/libstreaming">libstreaming</a><br/>
                    www.androidhive.info
                </p>
            </div>
        </div>
    </body>
</html>

5. Inside css folder create a file named style.css and paste the following styles.

/* 
    Document   : style
    Created on : May 31, 2014, 2:09:28 AM
    Author     : Ravi Tamada    
*/

body { 
    padding:0;
    margin: 0;
}
.clear{
    clear: both;
}
.container{
    width: 1100px;
    margin: 0 auto;
    padding: 0;
}
#header{
    text-align: left; 
    box-shadow: 0px 3px 3px #e3e3e3;
}
#header h1{
    font:normal 35px arial;
    color: #ed4365;
    margin: 0;
    padding: 15px 0;
}
#video_preview{
    text-align: center;
}
#player, #player_wrapper{
    margin: 0 auto !important;
    margin-bottom: 20px !important;
    margin-top: 60px !important;    
}
input#stream_url{
    background: none;
    border: 2px solid #92d07f;
    outline: none;
    padding: 5px 10px;
    font: 18px arial;
    color: #666;
    width: 600px;
    text-align: center;
}
#btn_start, #btn_stop{
    padding: 8px 30px;
    color: #fff;
    border: none;
    outline: none;
    font: normal 16px arial;
    border-radius: 6px;
    cursor: pointer;
    margin-top: 15px;
}
#btn_start{
    background: #3bbe13;    
}
#btn_stop{
    background: #e6304f;   
}
.info{
    margin-top: 80px;
    text-align: center;    
    font:normal 13px verdana;
}
.info p{
    line-height: 25px;
}
.info a{
    color: #f05539;
}

6. Create player.js file inside js folder and paste following javascript. This script takes care of initializing jwplayer and other button click events.

var data = [];
var jw_width = 640, jw_height = 360;

// Outputs some logs about jwplayer
function print(t, obj) {
    for (var a in obj) {
        if (typeof obj[a] === "object")
            print(t + '.' + a, obj[a]);
        else
            data[t + '.' + a] = obj[a];
    }
}

$(document).ready(function() {

    jwplayer('player').setup({
        wmode: 'transparent',
        width: jw_width,
        height: jw_height,
        stretching: 'exactfit'
    });

    $('#btn_start').click(function() {
        startPlayer($('#stream_url').val());
    });

    $('#btn_stop').click(function() {
        jwplayer('player').stop();
    });



    startPlayer($('#stream_url').val());
});

// Starts the flash player
function startPlayer(stream) {

    jwplayer('player').setup({
        height: jw_height,
        width: jw_width,
        stretching: 'exactfit',
        sources: [{
                file: stream
            }],
        rtmp: {
            bufferlength: 3
        }
    });

    jwplayer("player").onMeta(function(event) {
        var info = "";
        for (var key in data) {
            info += key + " = " + data[key] + "<BR>";
        }
        print("event", event);
    });

    jwplayer('player').play();
}

7. Now if you access http://localhost/wowza_web_app/index.html in browser, the page should render like below.

android video streaming using wowza media engine

Testing the app (localhost)

Start Wowza Enginer Mangaer from Start => All Programs. Once started you should able to access http://localhost:8088/enginemanager/ in PC browser.

Make sure that the mobile and the PC are in the same wifi network.

Use Android Terminal Emulator app to check communication between mobile and PC. If you execute “ping pc_ip_address“, you should receive some data.

Try accessing http://your_pc_ip_address:8088/enginemanager/ in mobile browser. You should see wowza engine manager home page.

Once you are sure that the PC and mobile are in the same network, open both the apps (android & web) and click on Start in web page. You should able to see camera video streamed to webpage.

Exploring the libstreaming

The android app that I have created in this tutorial is very simple which lacks of lot options like start/stop video stream, changing video quality, using camera flash, using front and rare cameras. My intention was to make it as much as simple, so that every beginner can understands it. But the example given by libstreaming covers all these advanced options.

Download the Example3 and do explore yourself.

Hosting the wowza server (going live)

If you are serious about going the app to be public where anybody can stream their camera video over intenet, you need to host the Wowza engine on a server. The following links will gives you a hosting solution to host Wowza Media Engine (personally I haven’t tried though).

Google Cloud Platform Hosting
http://googlecloudplatform.blogspot.in/2013/12/you-can-now-deliver-any-screen.html

Wowza Featured Service Providers
http://www.wowza.com/customers

]]>
http://www.androidhive.info/2014/06/android-streaming-live-camera-video-to-web-page/feed/ 0
Android working with Volley Libraryhttp://www.androidhive.info/2014/05/android-working-with-volley-library-1/ http://www.androidhive.info/2014/05/android-working-with-volley-library-1/#comments Thu, 01 May 2014 06:14:24 +0000 http://www.androidhive.info/?p=4330 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 :)

]]>
http://www.androidhive.info/2014/05/android-working-with-volley-library-1/feed/ 0
Smartphone Management through PC – Go and Grab MoboRobohttp://www.androidhive.info/2014/04/smartphone-management-through-pc-go-and-grab-moborobo/ http://www.androidhive.info/2014/04/smartphone-management-through-pc-go-and-grab-moborobo/#comments Tue, 08 Apr 2014 17:16:16 +0000 http://www.androidhive.info/?p=4310 For most of us, it is a tough job to manage computer and Smartphones simultaneously, as we will have to move/walk a lot to do so. For instance, wouldn’t that be tough for you to take Smartphone in your hand each time someone will be calling you or have texted you? Nevertheless, you can fix all these issues and hence manage both devices with the power of easiness if you use MoboRobo – the free tool for iOS and Android Smartphone management. Here, we will do a review of this mentioned tool to understand how it simplifies process of managing your Smartphone using PC. First, we will tell you about installation and first use with MoboRobo, and then will check out predominant features of this awesome Android & iOS manager.

android login with google plus account


Installation & First Use of MoboRobo

Unlike some heavy Smartphone management tools, installing MoboRobo in your PC is an extremely simple task, as it requires you to have no previous experience at all. Since MoboRobo is a completely free tool, you have to download it from their official website. Size of installation file is 23MB, which is indeed very small when compared to others. If you have completed the download process, it will take only a few seconds for MoboRobo to be installed in your PC.


Connecting

As the first part of using MoboRobo, you have to connect your iPhone or Android Smartphones. There is a couple of ways available for connecting your Smartphone with PC by means of MoboRobo. First, you can use USB Cable of the Smartphone to establish the connection. As this method provides maximum productivity and support for features, it is the most recommended connection method.

Establishing Connection Moborobo

However, if you do not like messing up with wires, but have a Wi-Fi connection at home, you can use that wireless method to establish connection between Smartphone and PC. In this latter method, you will have to scan a QR code with Smartphone camera and do some of such tasks to establish that connection. Once creation of connection is over, rest is fine!


Dashboard

Now, you can see the beautiful dashboard of MoboRobo, which displays several aspects of the device you connected with the PC. Details include storage consumption in device, remaining charge, amount of data (such as contacts, SMS and Apps), live preview of screen, feature to take screenshot of device screen, a link to view advanced information about device such as IMEI number, rooted or not, software info etc. The dashboard also has a link to backup/restore feature of MoboRobo, as both these will be useful for users. Now, we will have a glimpse on major sections of this tremendous tool. Actually, it has a number of sections for advanced management of what you have stored in device but we are now checking noticeable features only.

Phone Connected


Notable Sections

Data
When it comes to MoboRobo, data means contacts you have stored, messages you have sent or received and logs of calls you have made or received. Among these, feature to manage contacts makes more sense, as the same process is tough in small screens of Smartphones. You can add new contacts, edit existing and import or export contacts via your PC screen. In the message section, you can see all messages you have received, and you can reply to messages whenever they arrive [when message arrive, you can see a popup notification from MoboRobo]. In call section, you have an option to prompt a call from device by clicking on ‘Call’ button/icon. Plus, you can export call log information as well.

Apps
Using this section, you can manage applications that you have installed in your Smartphone. There are specific parts for user apps and system apps along with an option to update application bulkily. Also, there are options to transfer apps from SD card to phone or vice versa, export list etc. Yet another addition of MoboRobo when it comes to case of apps is availability of an in-built store. From the store, you will be able to download and install trending Apps into your device. This feature will be very useful if you are using an Android device that does not offer access to Google Play Store. Indeed, this section of MoboRobo makes enough sense, especially if you are addicted to Apps.

App Store Moborobo

Images, Music, Videos & Themes
Even though there are specific tabs for all these, we can combine them under a label ‘Media’ In the images section, you have access to images that you have taken using the camera, device gallery. Plus, it offers a web-based platform to download wallpapers from. In music section, you can see a list of music files from device gallery, and download a lot of ringtones from given platform. In the videos section, apart from viewing gallery contents, you have access to a platform called MMOSITE Video from which you can download videos to the device for later viewing. Obviously, this type of sections is very useful if you are addicted to media.


User Interface

User Interface of MoboRobo is something excellent, as it does not need you to have previous experience with using this kind of management tools. And, we were impressed by the neat layout and design of different sections of tool.


Final Word from us

In light of all sections, we have mentioned earlier, it seems quite clear that MoboRobo is a sort of must-have tool for all those who love to manage Android devices with ease. However, we will love to hear your opinion about this tool through comments!

Download MoboRobo

For more details:
Visit our website: http://www.moborobo.com/
Facebook: https://www.facebook.com/MoboroboEN
Twitter: https://twitter.com/Moborobo_EN

]]>
http://www.androidhive.info/2014/04/smartphone-management-through-pc-go-and-grab-moborobo/feed/ 0
Android Login with Google Plus Accounthttp://www.androidhive.info/2014/02/android-login-with-google-plus-account-1/ http://www.androidhive.info/2014/02/android-login-with-google-plus-account-1/#comments Mon, 17 Feb 2014 19:23:43 +0000 http://www.androidhive.info/?p=4203 Google+ sign-in lets users sign in to your Android app with their existing Google account and get their profile information like name, email, profile pic and other details. By integrating google plus login in your apps, you can get all the user details in one shot. Not only login, you can do other things like posting to their g+ account, getting list of circles, friends list and lot more. The major advantage of integrating G+ login is, you can drive more users to your app by providing quicker & easiest way of registration process.

android login with google plus account

So let’s start by doing required setup.

1. Installing / updating Google Play Services

Google plus is a part of Google Play Services API. So first we need to download the google play services in Android SDK manager. If you have already installed play services, it it very important to update it to latest version. Open the SDK manager and install or update the play services under Extras section.

android sdk manager installing play services


2. Enabling G+ API on google console

In order to consume google plus services, first we need to enable the Google Plus API on google console and we need to register our digitally signed .apk file’s public certificate in the Google APIs Console.

2.1) Java keytool can be used to generate SHA-1 fingerprint. Open your terminal and execute the following command to generate SHA-1 fingerprint. If it ask for password, type android and press enter.

On windows

keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android

On Linux or Mac OS

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

In the output you can notice SHA1 fingerprint.

android generating sha1 fingerprint

2.2) Open Google APIs console

2.3) On the left, under APIs & auth section, click on APIs and on the right enable Google+ API service.

google-console-api-google-plus

2.4) Now again on the left, click on Credentials and on the right, click on CREATE NEW CLIENT ID button. It will open a popup to configure a new client id.

android console api client id

2.5) In the popup, select Installed application as Application type.

2.6) Under Installed application type section select Android and give your project package name. This package name should be equal to your android project. I gave my package name as info.androidhive.gpluslogin

2.7) Enter your SHA1 fingerprint in Signing certificate fingerprint (SHA1) field.

2.8) Enable Deep Linking and click on Create Client ID button. Now you should see a new client created for your android application.

google api console creating new client id
google console api


3. Importing Play Services Library to Eclipse

Before creating a new project, we need to import the play services library into Eclipse workspace. To import, follow below steps.

3.1) In Eclipse goto File ⇒ Import ⇒ Android ⇒ Existing Android Code Into Workspace

3.2) Click on Browse and select Google Play Services project from your android sdk folder. You can locate play services library project from
android-sdk-windows\extras\google\google_play_services\libproject\google-play-services_lib

3.3) And check Copy projects into workspace option as shown in the below image, which places a copy of play services in eclipse workspace.

google api console creating new client id


4. Creating New Project

Until now we are done with all the required setup, now we can create a new project for google plus login.

4.1) In Eclipse create a new project by navigating to File ⇒ New ⇒ Android Application Project and fill required details. Set min SDK version and compile with to API 9: Android 2.3 and max SDK version to latest version.

4.2) Now we need to use Google Play Services project as a library to our project. So right click on project and select properties. In the properties window on left side select Android. On the right you can see a Add button under library section. Click it and select google play services project which we imported previously.

android google play services library project
android google play services library project
android google play services library project

4.3) Open the AndroidManifest.xml file add required permissions. Add INTERNET, GET_ACCOUNTS and USE_CREDENTIALS. Also add the meta-data tag for google play services under application tag.

Permissions:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />

Meta-data:

<meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

After adding, your manifest file should look like this.

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

    <uses-sdk
        android:minSdkVersion="9"
        android:targetSdkVersion="18" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <activity
            android:name="info.androidhive.gpluslogin.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>
    </application>

</manifest>

4.4) Open strings.xml file under res ⇒ values ⇒ strings.xml add following strings for buttons text.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">G+ Login</string>
    <string name="action_settings">Settings</string>
    
    <!-- Button text -->
    <string name="btn_logout_from_google">Logout from Google</string>
    <string name="btn_revoke_access">Revoke Access</string>
</resources>

4.5). Now I am designing a simple layout to display user’s profile picture, name, email and other buttons. Paste the below code in layout file of your main activity. In my case my layout file name is activity_main.xml

<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="16dp"
    tools:context=".MainActivity" >

    <LinearLayout
        android:id="@+id/llProfile"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:orientation="horizontal"
        android:weightSum="3"
        android:visibility="gone">

        <ImageView
            android:id="@+id/imgProfilePic"
            android:layout_width="80dp"
            android:layout_height="wrap_content" 
            android:layout_weight="1"/>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:orientation="vertical"
            android:layout_weight="2" >

            <TextView
                android:id="@+id/txtName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:textSize="20dp" />

            <TextView
                android:id="@+id/txtEmail"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="5dp"
                android:textSize="18dp" />
        </LinearLayout>
    </LinearLayout>

    <com.google.android.gms.common.SignInButton
        android:id="@+id/btn_sign_in"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:layout_marginBottom="20dp"/>

    <Button
        android:id="@+id/btn_sign_out"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn_logout_from_google"
        android:visibility="gone" 
        android:layout_marginBottom="10dp"/>

    <Button
        android:id="@+id/btn_revoke_access"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn_revoke_access"
        android:visibility="gone" />

</LinearLayout>

The above XML will produce following output. Even though we added other buttons too, they are currently hidden and will be shown once user successfully login with their G+ account.

android login with google plus account

4.6) Now open your main activity class and add the basic code like implementing the class from listeners and other required variables. Also we need to initialize the GoogleApiClient which is required to communicate with Google+ APIs.

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.plus.Plus;
import com.google.android.gms.plus.model.people.Person;

public class MainActivity extends Activity implements OnClickListener,
		ConnectionCallbacks, OnConnectionFailedListener {

	private static final int RC_SIGN_IN = 0;
	// Logcat tag
	private static final String TAG = "MainActivity";

	// Profile pic image size in pixels
	private static final int PROFILE_PIC_SIZE = 400;

	// Google client to interact with Google API
	private GoogleApiClient mGoogleApiClient;

	/**
	 * A flag indicating that a PendingIntent is in progress and prevents us
	 * from starting further intents.
	 */
	private boolean mIntentInProgress;

	private boolean mSignInClicked;

	private ConnectionResult mConnectionResult;

	private SignInButton btnSignIn;
	private Button btnSignOut, btnRevokeAccess;
	private ImageView imgProfilePic;
	private TextView txtName, txtEmail;
	private LinearLayout llProfileLayout;

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

		btnSignIn = (SignInButton) findViewById(R.id.btn_sign_in);
		btnSignOut = (Button) findViewById(R.id.btn_sign_out);
		btnRevokeAccess = (Button) findViewById(R.id.btn_revoke_access);
		imgProfilePic = (ImageView) findViewById(R.id.imgProfilePic);
		txtName = (TextView) findViewById(R.id.txtName);
		txtEmail = (TextView) findViewById(R.id.txtEmail);
		llProfileLayout = (LinearLayout) findViewById(R.id.llProfile);

		// Button click listeners
		btnSignIn.setOnClickListener(this);
		btnSignOut.setOnClickListener(this);
		btnRevokeAccess.setOnClickListener(this);

		// Initializing google plus api client
		mGoogleApiClient = new GoogleApiClient.Builder(this)
				.addConnectionCallbacks(this)
				.addOnConnectionFailedListener(this).addApi(Plus.API, null)
				.addScope(Plus.SCOPE_PLUS_LOGIN).build();
	}

	protected void onStart() {
		super.onStart();
		mGoogleApiClient.connect();
	}

	protected void onStop() {
		super.onStop();
		if (mGoogleApiClient.isConnected()) {
			mGoogleApiClient.disconnect();
		}
	}

	/**
	 * Button on click listener
	 * */
	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.btn_sign_in:
			// Signin button clicked
			signInWithGplus();
			break;
		case R.id.btn_sign_out:
			// Signout button clicked
			signOutFromGplus();
			break;
		case R.id.btn_revoke_access:
			// Revoke access button clicked
			revokeGplusAccess();
			break;
		}
	}
}

4.7) Also we need to add few more call back listeners which will be invoked by GoogleApiClient when the connection status changes. The methods like onConnectionFailed(), onConnected(), onConnectionSuspended() has to be added.

Also we need to add onActivityResult() method which receives some data when startIntentSenderForResult called. Add the following code to your main activity.

	@Override
	public void onConnectionFailed(ConnectionResult result) {
		if (!result.hasResolution()) {
			GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this,
					0).show();
			return;
		}

		if (!mIntentInProgress) {
			// Store the ConnectionResult for later usage
			mConnectionResult = result;

			if (mSignInClicked) {
				// The user has already clicked 'sign-in' so we attempt to
				// resolve all
				// errors until the user is signed in, or they cancel.
				resolveSignInError();
			}
		}

	}

	@Override
	protected void onActivityResult(int requestCode, int responseCode,
			Intent intent) {
		if (requestCode == RC_SIGN_IN) {
			if (responseCode != RESULT_OK) {
				mSignInClicked = false;
			}

			mIntentInProgress = false;

			if (!mGoogleApiClient.isConnecting()) {
				mGoogleApiClient.connect();
			}
		}
	}

	@Override
	public void onConnected(Bundle arg0) {
		mSignInClicked = false;
		Toast.makeText(this, "User is connected!", Toast.LENGTH_LONG).show();

		// Get user's information
		getProfileInformation();

		// Update the UI after signin
		updateUI(true);

	}

	@Override
	public void onConnectionSuspended(int arg0) {
		mGoogleApiClient.connect();
		updateUI(false);
	}

	/**
	 * Updating the UI, showing/hiding buttons and profile layout
	 * */
	private void updateUI(boolean isSignedIn) {
		if (isSignedIn) {
			btnSignIn.setVisibility(View.GONE);
			btnSignOut.setVisibility(View.VISIBLE);
			btnRevokeAccess.setVisibility(View.VISIBLE);
			llProfileLayout.setVisibility(View.VISIBLE);
		} else {
			btnSignIn.setVisibility(View.VISIBLE);
			btnSignOut.setVisibility(View.GONE);
			btnRevokeAccess.setVisibility(View.GONE);
			llProfileLayout.setVisibility(View.GONE);
		}
	}


Signing in with Google Plus Account

4.8) If you observe the code in onClick() function, I have called a method signInWithGplus() on sign in button click. Add the following code in your main activity which signs in user using google plus account.

	/**
	 * Sign-in into google
	 * */
	private void signInWithGplus() {
		if (!mGoogleApiClient.isConnecting()) {
			mSignInClicked = true;
			resolveSignInError();
		}
	}

	/**
	 * Method to resolve any signin errors
	 * */
	private void resolveSignInError() {
		if (mConnectionResult.hasResolution()) {
			try {
				mIntentInProgress = true;
				mConnectionResult.startResolutionForResult(this, RC_SIGN_IN);
			} catch (SendIntentException e) {
				mIntentInProgress = false;
				mGoogleApiClient.connect();
			}
		}
	}

If user clicks on sign in button, the following popup will be shown asking user to sign-in using google plus account.

android ogin with google plus account popup


Getting user’s profile information (name, email, profile pic etc.,)

4.9) Add the following method to get user’s profile information once he/she logged in. This method is called in onConnected function. Also I have added an async method to get the user profile picture from an url.

By default the Google plus client gives profile picture url of 50×50 pixel image. We can replace that values with any size that we want. You can do that by replacing last two characters of the profile pic url.

	/**
	 * Fetching user's information name, email, profile pic
	 * */
	private void getProfileInformation() {
		try {
			if (Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null) {
				Person currentPerson = Plus.PeopleApi
						.getCurrentPerson(mGoogleApiClient);
				String personName = currentPerson.getDisplayName();
				String personPhotoUrl = currentPerson.getImage().getUrl();
				String personGooglePlusProfile = currentPerson.getUrl();
				String email = Plus.AccountApi.getAccountName(mGoogleApiClient);

				Log.e(TAG, "Name: " + personName + ", plusProfile: "
						+ personGooglePlusProfile + ", email: " + email
						+ ", Image: " + personPhotoUrl);

				txtName.setText(personName);
				txtEmail.setText(email);

				// by default the profile url gives 50x50 px image only
				// we can replace the value with whatever dimension we want by
				// replacing sz=X
				personPhotoUrl = personPhotoUrl.substring(0,
						personPhotoUrl.length() - 2)
						+ PROFILE_PIC_SIZE;

				new LoadProfileImage(imgProfilePic).execute(personPhotoUrl);

			} else {
				Toast.makeText(getApplicationContext(),
						"Person information is null", Toast.LENGTH_LONG).show();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Background Async task to load user profile picture from url
	 * */
	private class LoadProfileImage extends AsyncTask<String, Void, Bitmap> {
		ImageView bmImage;

		public LoadProfileImage(ImageView bmImage) {
			this.bmImage = bmImage;
		}

		protected Bitmap doInBackground(String... urls) {
			String urldisplay = urls[0];
			Bitmap mIcon11 = null;
			try {
				InputStream in = new java.net.URL(urldisplay).openStream();
				mIcon11 = BitmapFactory.decodeStream(in);
			} catch (Exception e) {
				Log.e("Error", e.getMessage());
				e.printStackTrace();
			}
			return mIcon11;
		}

		protected void onPostExecute(Bitmap result) {
			bmImage.setImageBitmap(result);
		}
	}

After fetching the user’s profile information successfully, you can see the following screen in your application.

android google plus login user profile information


Sign out from Google Plus

4.10) In order to sign out user from google plus account, add the following method in your main activity. This method is called when user presses sign out button.

	/**
	 * Sign-out from google
	 * */
	private void signOutFromGplus() {
		if (mGoogleApiClient.isConnected()) {
			Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
			mGoogleApiClient.disconnect();
			mGoogleApiClient.connect();
			updateUI(false);
		}
	}


Revoking access from Google Plus

4.11) Also if you want to revoke the access from google plus account completely, add the following method. The function is being called when user touches the revoke access button.

	/**
	 * Revoking access from google
	 * */
	private void revokeGplusAccess() {
		if (mGoogleApiClient.isConnected()) {
			Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
			Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient)
					.setResultCallback(new ResultCallback<Status>() {
						@Override
						public void onResult(Status arg0) {
							Log.e(TAG, "User access revoked!");
							mGoogleApiClient.connect();
							updateUI(false);
						}

					});
		}
	}


5. Testing the app

On Real Device:
You can directly test this app on a real devices which is having Google Play Store installed.

On Emulator:
You can also test the application on an emulator which runs Android 4.2.2 or greater. Follow below link to know how to configure an emulator by installing Google Play Services.
Configuring emulator with Google Play Services


Complete Code

Following is the full code of main activity class.

package info.androidhive.gpluslogin;

import java.io.InputStream;

import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.plus.Plus;
import com.google.android.gms.plus.model.people.Person;

public class MainActivity extends Activity implements OnClickListener,
		ConnectionCallbacks, OnConnectionFailedListener {

	private static final int RC_SIGN_IN = 0;
	// Logcat tag
	private static final String TAG = "MainActivity";

	// Profile pic image size in pixels
	private static final int PROFILE_PIC_SIZE = 400;

	// Google client to interact with Google API
	private GoogleApiClient mGoogleApiClient;

	/**
	 * A flag indicating that a PendingIntent is in progress and prevents us
	 * from starting further intents.
	 */
	private boolean mIntentInProgress;

	private boolean mSignInClicked;

	private ConnectionResult mConnectionResult;

	private SignInButton btnSignIn;
	private Button btnSignOut, btnRevokeAccess;
	private ImageView imgProfilePic;
	private TextView txtName, txtEmail;
	private LinearLayout llProfileLayout;

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

		btnSignIn = (SignInButton) findViewById(R.id.btn_sign_in);
		btnSignOut = (Button) findViewById(R.id.btn_sign_out);
		btnRevokeAccess = (Button) findViewById(R.id.btn_revoke_access);
		imgProfilePic = (ImageView) findViewById(R.id.imgProfilePic);
		txtName = (TextView) findViewById(R.id.txtName);
		txtEmail = (TextView) findViewById(R.id.txtEmail);
		llProfileLayout = (LinearLayout) findViewById(R.id.llProfile);

		// Button click listeners
		btnSignIn.setOnClickListener(this);
		btnSignOut.setOnClickListener(this);
		btnRevokeAccess.setOnClickListener(this);

		mGoogleApiClient = new GoogleApiClient.Builder(this)
				.addConnectionCallbacks(this)
				.addOnConnectionFailedListener(this).addApi(Plus.API, null)
				.addScope(Plus.SCOPE_PLUS_LOGIN).build();
	}

	protected void onStart() {
		super.onStart();
		mGoogleApiClient.connect();
	}

	protected void onStop() {
		super.onStop();
		if (mGoogleApiClient.isConnected()) {
			mGoogleApiClient.disconnect();
		}
	}

	/**
	 * Method to resolve any signin errors
	 * */
	private void resolveSignInError() {
		if (mConnectionResult.hasResolution()) {
			try {
				mIntentInProgress = true;
				mConnectionResult.startResolutionForResult(this, RC_SIGN_IN);
			} catch (SendIntentException e) {
				mIntentInProgress = false;
				mGoogleApiClient.connect();
			}
		}
	}

	@Override
	public void onConnectionFailed(ConnectionResult result) {
		if (!result.hasResolution()) {
			GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this,
					0).show();
			return;
		}

		if (!mIntentInProgress) {
			// Store the ConnectionResult for later usage
			mConnectionResult = result;

			if (mSignInClicked) {
				// The user has already clicked 'sign-in' so we attempt to
				// resolve all
				// errors until the user is signed in, or they cancel.
				resolveSignInError();
			}
		}

	}

	@Override
	protected void onActivityResult(int requestCode, int responseCode,
			Intent intent) {
		if (requestCode == RC_SIGN_IN) {
			if (responseCode != RESULT_OK) {
				mSignInClicked = false;
			}

			mIntentInProgress = false;

			if (!mGoogleApiClient.isConnecting()) {
				mGoogleApiClient.connect();
			}
		}
	}

	@Override
	public void onConnected(Bundle arg0) {
		mSignInClicked = false;
		Toast.makeText(this, "User is connected!", Toast.LENGTH_LONG).show();

		// Get user's information
		getProfileInformation();

		// Update the UI after signin
		updateUI(true);

	}

	/**
	 * Updating the UI, showing/hiding buttons and profile layout
	 * */
	private void updateUI(boolean isSignedIn) {
		if (isSignedIn) {
			btnSignIn.setVisibility(View.GONE);
			btnSignOut.setVisibility(View.VISIBLE);
			btnRevokeAccess.setVisibility(View.VISIBLE);
			llProfileLayout.setVisibility(View.VISIBLE);
		} else {
			btnSignIn.setVisibility(View.VISIBLE);
			btnSignOut.setVisibility(View.GONE);
			btnRevokeAccess.setVisibility(View.GONE);
			llProfileLayout.setVisibility(View.GONE);
		}
	}

	/**
	 * Fetching user's information name, email, profile pic
	 * */
	private void getProfileInformation() {
		try {
			if (Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null) {
				Person currentPerson = Plus.PeopleApi
						.getCurrentPerson(mGoogleApiClient);
				String personName = currentPerson.getDisplayName();
				String personPhotoUrl = currentPerson.getImage().getUrl();
				String personGooglePlusProfile = currentPerson.getUrl();
				String email = Plus.AccountApi.getAccountName(mGoogleApiClient);

				Log.e(TAG, "Name: " + personName + ", plusProfile: "
						+ personGooglePlusProfile + ", email: " + email
						+ ", Image: " + personPhotoUrl);

				txtName.setText(personName);
				txtEmail.setText(email);

				// by default the profile url gives 50x50 px image only
				// we can replace the value with whatever dimension we want by
				// replacing sz=X
				personPhotoUrl = personPhotoUrl.substring(0,
						personPhotoUrl.length() - 2)
						+ PROFILE_PIC_SIZE;

				new LoadProfileImage(imgProfilePic).execute(personPhotoUrl);

			} else {
				Toast.makeText(getApplicationContext(),
						"Person information is null", Toast.LENGTH_LONG).show();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void onConnectionSuspended(int arg0) {
		mGoogleApiClient.connect();
		updateUI(false);
	}

	@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;
	}

	/**
	 * Button on click listener
	 * */
	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.btn_sign_in:
			// Signin button clicked
			signInWithGplus();
			break;
		case R.id.btn_sign_out:
			// Signout button clicked
			signOutFromGplus();
			break;
		case R.id.btn_revoke_access:
			// Revoke access button clicked
			revokeGplusAccess();
			break;
		}
	}

	/**
	 * Sign-in into google
	 * */
	private void signInWithGplus() {
		if (!mGoogleApiClient.isConnecting()) {
			mSignInClicked = true;
			resolveSignInError();
		}
	}

	/**
	 * Sign-out from google
	 * */
	private void signOutFromGplus() {
		if (mGoogleApiClient.isConnected()) {
			Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
			mGoogleApiClient.disconnect();
			mGoogleApiClient.connect();
			updateUI(false);
		}
	}

	/**
	 * Revoking access from google
	 * */
	private void revokeGplusAccess() {
		if (mGoogleApiClient.isConnected()) {
			Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
			Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient)
					.setResultCallback(new ResultCallback<Status>() {
						@Override
						public void onResult(Status arg0) {
							Log.e(TAG, "User access revoked!");
							mGoogleApiClient.connect();
							updateUI(false);
						}

					});
		}
	}

	/**
	 * Background Async task to load user profile picture from url
	 * */
	private class LoadProfileImage extends AsyncTask<String, Void, Bitmap> {
		ImageView bmImage;

		public LoadProfileImage(ImageView bmImage) {
			this.bmImage = bmImage;
		}

		protected Bitmap doInBackground(String... urls) {
			String urldisplay = urls[0];
			Bitmap mIcon11 = null;
			try {
				InputStream in = new java.net.URL(urldisplay).openStream();
				mIcon11 = BitmapFactory.decodeStream(in);
			} catch (Exception e) {
				Log.e("Error", e.getMessage());
				e.printStackTrace();
			}
			return mIcon11;
		}

		protected void onPostExecute(Bitmap result) {
			bmImage.setImageBitmap(result);
		}
	}

}
]]>
http://www.androidhive.info/2014/02/android-login-with-google-plus-account-1/feed/ 0
How to create REST API for Android app using PHP, Slim and MySQL – Day 2/2http://www.androidhive.info/2014/01/how-to-create-rest-api-for-android-app-using-php-slim-and-mysql-day-23/ http://www.androidhive.info/2014/01/how-to-create-rest-api-for-android-app-using-php-slim-and-mysql-day-23/#comments Mon, 20 Jan 2014 18:30:35 +0000 http://www.androidhive.info/?p=3781 The previous day How to create REST API for Android app using PHP, Slim and MySQL – Day ½, we have learned fundamental concepts about REST API and preparing your development environment ready by installing the required tools needed. I hope everyone got good knowledge about REST and other technical areas. Also I am assuming that you got all the required tools installed.

Today we are going to learn how to setup a PHP project and writing the actual code for REST API. Also we’ll learn writing necessary SQL queries to perform database CRUD operations.

android rest api design using php mysql slim


8. Starting PHP Project

As we all know IDEs make development process easier. So I recommend you use an IDE for developing the PHP project instead of using plain notepad. You can go for Eclipse, Aptana Studio, PhpStorm or Netbeans. But I personally felt very comfortable using Netbeans for PHP projects.

PHP Project directory structure
The following diagram will give you an idea about the directory structure of the project which we are going to develop now.

task manager rest api php project directory structure

libs – All the third party libraries goes here. In our case we place Slim library here
include – All the helpers classes we build placed here
index.php – Takes care of all the API requests
.htaccess – Rules for url structure and other apache rules

Now let’s start the PHP project

1. Go to the directory where WAMP is installed. In general wamp will be installed in C:\wamp. (If you have installed any other software rather than WAMP, you should go to the directory recommended by that software).

2. As a first step we start with creating required directories. Inside wamp folder go to www folder (c:\wamp\www\) and create a folder named task_manager. This folder will be the parent directory of our project. Inside task_manager create two more folders named libs, include and v1.

3. Now the paste the Slim library inside libs folder. The download link for Slim is provided in previous part.

4. Normally Slim framework works when index.php includes in the url which makes url not well-formed. So using the .htacess rules we can get rid of index.php from the url and make some friendly urls. Inside v1 folder create a file named .htaccess and paste the following code. (Note that this file name shouldn’t include any additional extension in the name like .txt)

RewriteEngine On 
RewriteCond %{REQUEST_FILENAME} !-f 
RewriteRule ^(.*)$ %{ENV:BASE}index.php [QSA,L]


8.1 Preparing Helper Classes

First we start writing set of helper classes required in this project. These helper classes provides necessary functions required to interact with the database.

5. Inside include folder create file named Config.php with following content. This file contains the entire project configuration like database connection parameters and other variables.

<?php
/**
 * Database configuration
 */
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_HOST', 'localhost');
define('DB_NAME', 'task_manager');

define('USER_CREATED_SUCCESSFULLY', 0);
define('USER_CREATE_FAILED', 1);
define('USER_ALREADY_EXISTED', 2);
?>

6. Create another class named DbConnect.php This class file mainly takes care of 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;
    }

}

?>

Encrypting the password
7. The best way to secure the user passwords is not store them as plain text, instead all the passwords should be encrypted before storing in db. The following class takes care of encrypting the user password. Create another file named PassHash.php and paste the following code.

<?php

class PassHash {

    // blowfish
    private static $algo = '$2a';
    // cost parameter
    private static $cost = '$10';

    // mainly for internal use
    public static function unique_salt() {
        return substr(sha1(mt_rand()), 0, 22);
    }

    // this will be used to generate a hash
    public static function hash($password) {

        return crypt($password, self::$algo .
                self::$cost .
                '$' . self::unique_salt());
    }

    // this will be used to compare a password against a hash
    public static function check_password($hash, $password) {
        $full_salt = substr($hash, 0, 29);
        $new_hash = crypt($password, $full_salt);
        return ($hash == $new_hash);
    }

}

?>

8. Now create another class named DbHandler.php This class is one of the important files in our project which provides necessary functions to perform CRUD operations on the database. Every function is self explanatory by it’s name and comments, I don’t have to have to explain much about them.

<?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();
    }

    /* ------------- `users` table method ------------------ */

    /**
     * Creating new user
     * @param String $name User full name
     * @param String $email User login email id
     * @param String $password User login password
     */
    public function createUser($name, $email, $password) {
        require_once 'PassHash.php';
        $response = array();

        // First check if user already existed in db
        if (!$this->isUserExists($email)) {
            // Generating password hash
            $password_hash = PassHash::hash($password);

            // Generating API key
            $api_key = $this->generateApiKey();

            // insert query
            $stmt = $this->conn->prepare("INSERT INTO users(name, email, password_hash, api_key, status) values(?, ?, ?, ?, 1)");
            $stmt->bind_param("ssss", $name, $email, $password_hash, $api_key);

            $result = $stmt->execute();

            $stmt->close();

            // Check for successful insertion
            if ($result) {
                // 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;
    }

    /**
     * Checking user login
     * @param String $email User login email id
     * @param String $password User login password
     * @return boolean User login status success/fail
     */
    public function checkLogin($email, $password) {
        // fetching user by email
        $stmt = $this->conn->prepare("SELECT password_hash FROM users WHERE email = ?");

        $stmt->bind_param("s", $email);

        $stmt->execute();

        $stmt->bind_result($password_hash);

        $stmt->store_result();

        if ($stmt->num_rows > 0) {
            // Found user with the email
            // Now verify the password

            $stmt->fetch();

            $stmt->close();

            if (PassHash::check_password($password_hash, $password)) {
                // User password is correct
                return TRUE;
            } else {
                // user password is incorrect
                return FALSE;
            }
        } else {
            $stmt->close();

            // user not existed with the email
            return FALSE;
        }
    }

    /**
     * Checking for duplicate user by email address
     * @param String $email email to check in db
     * @return boolean
     */
    private function isUserExists($email) {
        $stmt = $this->conn->prepare("SELECT id from users WHERE email = ?");
        $stmt->bind_param("s", $email);
        $stmt->execute();
        $stmt->store_result();
        $num_rows = $stmt->num_rows;
        $stmt->close();
        return $num_rows > 0;
    }

    /**
     * Fetching user by email
     * @param String $email User email id
     */
    public function getUserByEmail($email) {
        $stmt = $this->conn->prepare("SELECT name, email, api_key, status, created_at FROM users WHERE email = ?");
        $stmt->bind_param("s", $email);
        if ($stmt->execute()) {
            $user = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $user;
        } else {
            return NULL;
        }
    }

    /**
     * Fetching user api key
     * @param String $user_id user id primary key in user table
     */
    public function getApiKeyById($user_id) {
        $stmt = $this->conn->prepare("SELECT api_key FROM users WHERE id = ?");
        $stmt->bind_param("i", $user_id);
        if ($stmt->execute()) {
            $api_key = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $api_key;
        } else {
            return NULL;
        }
    }

    /**
     * Fetching user id by api key
     * @param String $api_key user api key
     */
    public function getUserId($api_key) {
        $stmt = $this->conn->prepare("SELECT id FROM users WHERE api_key = ?");
        $stmt->bind_param("s", $api_key);
        if ($stmt->execute()) {
            $user_id = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $user_id;
        } else {
            return NULL;
        }
    }

    /**
     * Validating user api key
     * If the api key is there in db, it is a valid key
     * @param String $api_key user api key
     * @return boolean
     */
    public function isValidApiKey($api_key) {
        $stmt = $this->conn->prepare("SELECT id from users WHERE api_key = ?");
        $stmt->bind_param("s", $api_key);
        $stmt->execute();
        $stmt->store_result();
        $num_rows = $stmt->num_rows;
        $stmt->close();
        return $num_rows > 0;
    }

    /**
     * Generating random Unique MD5 String for user Api key
     */
    private function generateApiKey() {
        return md5(uniqid(rand(), true));
    }

    /* ------------- `tasks` table method ------------------ */

    /**
     * Creating new task
     * @param String $user_id user id to whom task belongs to
     * @param String $task task text
     */
    public function createTask($user_id, $task) {        
        $stmt = $this->conn->prepare("INSERT INTO tasks(task) VALUES(?)");
        $stmt->bind_param("s", $task);
        $result = $stmt->execute();
        $stmt->close();

        if ($result) {
            // task row created
            // now assign the task to user
            $new_task_id = $this->conn->insert_id;
            $res = $this->createUserTask($user_id, $new_task_id);
            if ($res) {
                // task created successfully
                return $new_task_id;
            } else {
                // task failed to create
                return NULL;
            }
        } else {
            // task failed to create
            return NULL;
        }
    }

    /**
     * Fetching single task
     * @param String $task_id id of the task
     */
    public function getTask($task_id, $user_id) {
        $stmt = $this->conn->prepare("SELECT t.id, t.task, t.status, t.created_at from tasks t, user_tasks ut WHERE t.id = ? AND ut.task_id = t.id AND ut.user_id = ?");
        $stmt->bind_param("ii", $task_id, $user_id);
        if ($stmt->execute()) {
            $task = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $task;
        } else {
            return NULL;
        }
    }

    /**
     * Fetching all user tasks
     * @param String $user_id id of the user
     */
    public function getAllUserTasks($user_id) {
        $stmt = $this->conn->prepare("SELECT t.* FROM tasks t, user_tasks ut WHERE t.id = ut.task_id AND ut.user_id = ?");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();
        $tasks = $stmt->get_result();
        $stmt->close();
        return $tasks;
    }

    /**
     * Updating task
     * @param String $task_id id of the task
     * @param String $task task text
     * @param String $status task status
     */
    public function updateTask($user_id, $task_id, $task, $status) {
        $stmt = $this->conn->prepare("UPDATE tasks t, user_tasks ut set t.task = ?, t.status = ? WHERE t.id = ? AND t.id = ut.task_id AND ut.user_id = ?");
        $stmt->bind_param("siii", $task, $status, $task_id, $user_id);
        $stmt->execute();
        $num_affected_rows = $stmt->affected_rows;
        $stmt->close();
        return $num_affected_rows > 0;
    }

    /**
     * Deleting a task
     * @param String $task_id id of the task to delete
     */
    public function deleteTask($user_id, $task_id) {
        $stmt = $this->conn->prepare("DELETE t FROM tasks t, user_tasks ut WHERE t.id = ? AND ut.task_id = t.id AND ut.user_id = ?");
        $stmt->bind_param("ii", $task_id, $user_id);
        $stmt->execute();
        $num_affected_rows = $stmt->affected_rows;
        $stmt->close();
        return $num_affected_rows > 0;
    }

    /* ------------- `user_tasks` table method ------------------ */

    /**
     * Function to assign a task to user
     * @param String $user_id id of the user
     * @param String $task_id id of the task
     */
    public function createUserTask($user_id, $task_id) {
        $stmt = $this->conn->prepare("INSERT INTO user_tasks(user_id, task_id) values(?, ?)");
        $stmt->bind_param("ii", $user_id, $task_id);
        $result = $stmt->execute();
        $stmt->close();
        return $result;
    }

}

?>


8.2 Handling the API calls

Now we have all the required classes for the REST API. Now we can start the code to handle all individual api calls.

8. Inside v1 folder create a file named index.php and add the following code. Here we are including required libraries and other helper functions.

verifyRequiredParams() – This function verifies the mandatory parameters in the request.
validateEmail() – Verifies whether email address is valid one or not.
echoRespnse() – This function will echo the JSON response with a status code.

<?php

require_once '../include/DbHandler.php';
require_once '../include/PassHash.php';
require '.././libs/Slim/Slim.php';

\Slim\Slim::registerAutoloader();

$app = new \Slim\Slim();

// User id from db - Global Variable
$user_id = NULL;

/**
 * Verifying required params posted or not
 */
function verifyRequiredParams($required_fields) {
    $error = false;
    $error_fields = "";
    $request_params = array();
    $request_params = $_REQUEST;
    // Handling PUT request params
    if ($_SERVER['REQUEST_METHOD'] == 'PUT') {
        $app = \Slim\Slim::getInstance();
        parse_str($app->request()->getBody(), $request_params);
    }
    foreach ($required_fields as $field) {
        if (!isset($request_params[$field]) || strlen(trim($request_params[$field])) <= 0) {
            $error = true;
            $error_fields .= $field . ', ';
        }
    }

    if ($error) {
        // Required field(s) are missing or empty
        // echo error json and stop the app
        $response = array();
        $app = \Slim\Slim::getInstance();
        $response["error"] = true;
        $response["message"] = 'Required field(s) ' . substr($error_fields, 0, -2) . ' is missing or empty';
        echoRespnse(400, $response);
        $app->stop();
    }
}

/**
 * Validating email address
 */
function validateEmail($email) {
    $app = \Slim\Slim::getInstance();
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $response["error"] = true;
        $response["message"] = 'Email address is not valid';
        echoRespnse(400, $response);
        $app->stop();
    }
}

/**
 * Echoing json response to client
 * @param String $status_code Http response code
 * @param Int $response Json response
 */
function echoRespnse($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);
}

$app->run();
?>


The JSON response

On calling every API request a JSON response will be issued with a HTTP status code. On the client side you have to verify the response http status code. If the status is 200, the request is processed successfully. Also you can notice a “error” node in the response. If the error value is true, that means some error occurred while processing the user data.



Api Calls without Authentication (without API key in the request header)

These calls don’t have to include Api Key in the request header. The main purpose of these calls is to interact with database without any authentication. User registration and login comes under this category.

⇒ User Registration

In order to interact with the API, the user has to register in our system first. Once he registered an API key will be generated and stored in the database. This API key will be private to that user only.

9. Add the following code in index.php. This function handles user registration.

/**
 * User Registration
 * url - /register
 * method - POST
 * params - name, email, password
 */
$app->post('/register', function() use ($app) {
            // check for required params
            verifyRequiredParams(array('name', 'email', 'password'));

            $response = array();

            // reading post params
            $name = $app->request->post('name');
            $email = $app->request->post('email');
            $password = $app->request->post('password');

            // validating email address
            validateEmail($email);

            $db = new DbHandler();
            $res = $db->createUser($name, $email, $password);

            if ($res == USER_CREATED_SUCCESSFULLY) {
                $response["error"] = false;
                $response["message"] = "You are successfully registered";
                echoRespnse(201, $response);
            } else if ($res == USER_CREATE_FAILED) {
                $response["error"] = true;
                $response["message"] = "Oops! An error occurred while registereing";
                echoRespnse(200, $response);
            } else if ($res == USER_ALREADY_EXISTED) {
                $response["error"] = true;
                $response["message"] = "Sorry, this email already existed";
                echoRespnse(200, $response);
            }
        });

In the following table you can find the API request information about the URL, HTTP method and the parameters needed to be posted.

URL/register
MethodPOST
Paramsname, email, password

Upon the successful registration the following json response will be issued.

{
    "error": false,
    "message": "You are successfully registered"
}

If the request is missing mandatory parameters the following json will be issued.

{
    "error": true,
    "message": "Required field(s) email, password is missing or empty"
}


⇒ User Login

10. Add the following code to handle user login. After verifying user credentials, the API Key for that user will be issued in the json response. The api key should be included in the request header in all remaining api calls.

/**
 * User Login
 * url - /login
 * method - POST
 * params - email, password
 */
$app->post('/login', function() use ($app) {
            // check for required params
            verifyRequiredParams(array('email', 'password'));

            // reading post params
            $email = $app->request()->post('email');
            $password = $app->request()->post('password');
            $response = array();

            $db = new DbHandler();
            // check for correct email and password
            if ($db->checkLogin($email, $password)) {
                // get the user by email
                $user = $db->getUserByEmail($email);

                if ($user != NULL) {
                    $response["error"] = false;
                    $response['name'] = $user['name'];
                    $response['email'] = $user['email'];
                    $response['apiKey'] = $user['api_key'];
                    $response['createdAt'] = $user['created_at'];
                } else {
                    // unknown error occurred
                    $response['error'] = true;
                    $response['message'] = "An error occurred. Please try again";
                }
            } else {
                // user credentials are wrong
                $response['error'] = true;
                $response['message'] = 'Login failed. Incorrect credentials';
            }

            echoRespnse(200, $response);
        });
URL/login
MethodPOST
Paramsemail, password

On successful login the following json will be issued.

{
    "error": false,
    "name": "Ravi Tamada",
    "email": "ravi@gmail.com",
    "apiKey": "940bb12af8d7b040876f60f965c5be6d",
    "createdAt": "2014-01-07 23:38:35"
}

If the credentials are wrong, you can expect the following json.

{
    "error": true,
    "message": "Login failed. Incorrect credentials"
}


⇒ Verifying API Key

While dealing with task data, we need to identify the user using the API key in the request header by reading Authorization field. Basically we’ll look into database for matched API key and get the appropriate user. If the API key not present in users table, then we’ll stop the execution and echo the error json.

11. Add the following method in index.php. The method authenticate() will be executed every time before doing any task related operations on database.

/**
 * Adding Middle Layer to authenticate every request
 * Checking if the request has valid api key in the 'Authorization' header
 */
function authenticate(\Slim\Route $route) {
    // Getting request headers
    $headers = apache_request_headers();
    $response = array();
    $app = \Slim\Slim::getInstance();

    // Verifying Authorization Header
    if (isset($headers['Authorization'])) {
        $db = new DbHandler();

        // get the api key
        $api_key = $headers['Authorization'];
        // validating api key
        if (!$db->isValidApiKey($api_key)) {
            // api key is not present in users table
            $response["error"] = true;
            $response["message"] = "Access Denied. Invalid Api key";
            echoRespnse(401, $response);
            $app->stop();
        } else {
            global $user_id;
            // get user primary key id
            $user = $db->getUserId($api_key);
            if ($user != NULL)
                $user_id = $user["id"];
        }
    } else {
        // api key is missing in header
        $response["error"] = true;
        $response["message"] = "Api key is misssing";
        echoRespnse(400, $response);
        $app->stop();
    }
}

If the api key is missing in the request header, the following json will be echoed with 400 status code.

{
    "error": true,
    "message": "Api key is misssing"
}

If the api key is not valid following json will echoed with 401 status code.

{
    "error": true,
    "message": "Access Denied. Invalid Api key"
}



Api Calls with Authentication (Including API key in the request)
Following are the API calls should have an Api Key in the request header. These api calls primarily deals the user’s task data like creating, reading, updating and deleting.

⇒ Creating New Task

12. Add the follwing method to create a new task. Here you can notice that authenticate method is called to verify the Api key before inserting a new task.

/**
 * Creating new task in db
 * method POST
 * params - name
 * url - /tasks/
 */
$app->post('/tasks', 'authenticate', function() use ($app) {
            // check for required params
            verifyRequiredParams(array('task'));

            $response = array();
            $task = $app->request->post('task');

            global $user_id;
            $db = new DbHandler();

            // creating new task
            $task_id = $db->createTask($user_id, $task);

            if ($task_id != NULL) {
                $response["error"] = false;
                $response["message"] = "Task created successfully";
                $response["task_id"] = $task_id;
            } else {
                $response["error"] = true;
                $response["message"] = "Failed to create task. Please try again";
            }
            echoRespnse(201, $response);
        });
URL/tasks
MethodPOST
Paramstask

On successful creation of new task following json will be issued. If you got this json, you can see new row inserted in tasks and user_tasks tables.

{
    "error": false,
    "message": "Task created successfully",
    "task_id": 1
}


⇒ Getting All Tasks

13. Following method will list down all user’s tasks. We don’t have to submit any params for this api call.

/**
 * Listing all tasks of particual user
 * method GET
 * url /tasks          
 */
$app->get('/tasks', 'authenticate', function() {
            global $user_id;
            $response = array();
            $db = new DbHandler();

            // fetching all user tasks
            $result = $db->getAllUserTasks($user_id);

            $response["error"] = false;
            $response["tasks"] = array();

            // looping through result and preparing tasks array
            while ($task = $result->fetch_assoc()) {
                $tmp = array();
                $tmp["id"] = $task["id"];
                $tmp["task"] = $task["task"];
                $tmp["status"] = $task["status"];
                $tmp["createdAt"] = $task["created_at"];
                array_push($response["tasks"], $tmp);
            }

            echoRespnse(200, $response);
        });
URL/tasks
MethodGET
Params-

Following json will be issued for list of tasks. The “tasks” represents list of tasks as an array. Also if the “status” is 0, that means the task is not done yet.

{
    "error": false,
    "tasks": [
        {
            "id": 1,
            "task": "Complete REST article by Sunday",
            "status": 0,
            "createdAt": "2014-01-08 23:35:45"
        },
        {
            "id": 2,
            "task": "Book bus tickets!",
            "status": 0,
            "createdAt": "2014-01-08 23:56:52"
        }
    ]
}


⇒ Getting Single Task

14. Following method will fetch details of single task. You need to append the task id with a / to url. For an example if you want details of task 15, the url will be /tasks/15.

/**
 * Listing single task of particual user
 * method GET
 * url /tasks/:id
 * Will return 404 if the task doesn't belongs to user
 */
$app->get('/tasks/:id', 'authenticate', function($task_id) {
            global $user_id;
            $response = array();
            $db = new DbHandler();

            // fetch task
            $result = $db->getTask($task_id, $user_id);

            if ($result != NULL) {
                $response["error"] = false;
                $response["id"] = $result["id"];
                $response["task"] = $result["task"];
                $response["status"] = $result["status"];
                $response["createdAt"] = $result["created_at"];
                echoRespnse(200, $response);
            } else {
                $response["error"] = true;
                $response["message"] = "The requested resource doesn't exists";
                echoRespnse(404, $response);
            }
        });
URL/tasks/id (id should be replaced with task id)
MethodGET
Params-

The details of a single task will be in following json format.

{
    "error": false,
    "id": 2,
    "task": "Book bus tickets!",
    "status": 0,
    "createdAt": "2014-01-08 23:56:52"
}

If you pass a task id which is not there in the database, you will get 404 not found error.


⇒ Updating Task

15. Following code will take care of updating a task. The url for this api call is same as getting the details of single task, only difference is we should use PUT method instead of GET.

/**
 * Updating existing task
 * method PUT
 * params task, status
 * url - /tasks/:id
 */
$app->put('/tasks/:id', 'authenticate', function($task_id) use($app) {
            // check for required params
            verifyRequiredParams(array('task', 'status'));

            global $user_id;            
            $task = $app->request->put('task');
            $status = $app->request->put('status');

            $db = new DbHandler();
            $response = array();

            // updating task
            $result = $db->updateTask($user_id, $task_id, $task, $status);
            if ($result) {
                // task updated successfully
                $response["error"] = false;
                $response["message"] = "Task updated successfully";
            } else {
                // task failed to update
                $response["error"] = true;
                $response["message"] = "Task failed to update. Please try again!";
            }
            echoRespnse(200, $response);
        });
URL/tasks/id (id should be replaced with task id)
MethodPUT
Paramstask, status (0 or 1)

Upon successful updation you will get following json.

{
    "error": false,
    "message": "Task updated successfully"
}


⇒ Deleting Task

16. Again delete task url is same as update task, but this requires DELETE method.

/**
 * Deleting task. Users can delete only their tasks
 * method DELETE
 * url /tasks
 */
$app->delete('/tasks/:id', 'authenticate', function($task_id) use($app) {
            global $user_id;

            $db = new DbHandler();
            $response = array();
            $result = $db->deleteTask($user_id, $task_id);
            if ($result) {
                // task deleted successfully
                $response["error"] = false;
                $response["message"] = "Task deleted succesfully";
            } else {
                // task failed to delete
                $response["error"] = true;
                $response["message"] = "Task failed to delete. Please try again!";
            }
            echoRespnse(200, $response);
        });
URL/tasks/id (id should be replaced with task id)
MethodDELETE
Params-

You will get following json if the task is deleted successfully.

{
    "error": false,
    "message": "Task deleted succesfully"
}

Here we completes the PHP and MySQL part. Now it’s time to move on to testing the API just to make sure that whatever code we have written is working.


Testing the API

Following is the list of URL we need to test using Chrome Advanced REST client extension with possible combinations of inputs.

URLMethodParametersDescription
http://localhost/task_manager/v1/registerPOSTname, email, passwordUser registration
http://localhost/task_manager/v1/loginPOSTemail, passwordUser login
http://localhost/task_manager/v1/tasksPOSTtaskTo create new task
http://localhost/task_manager/v1/tasksGETFetching all tasks
http://localhost/task_manager/v1/tasks/:idGETFetching single task
http://localhost/task_manager/v1/tasks/:idPUTUpdating single task
http://localhost/task_manager/v1/tasks/:idDELETEtask, statusDeleting single task

The following video shows you how to test the API thoroughly.

]]>
http://www.androidhive.info/2014/01/how-to-create-rest-api-for-android-app-using-php-slim-and-mysql-day-23/feed/ 0