AndroidHive | Tutorials, Games, Apps, Tips 2015-08-31T10:50:21Z http://www.androidhive.info/feed/atom/WordPress Ravi Tamada <![CDATA[Android Integrating Google Analytics V4]]> http://www.androidhive.info/?p=27098 2015-08-31T10:10:55Z 2015-08-15T11:08:47Z Have you ever wondered how to track your android user activity in real time? If yes, well here is the solution you are looking for. User activity tracking can be done using any good analytics tool. There are lot of tools out there like Mixpanel, Flurry etc., to track the user activity in real time but none of them are totally free. Google analytics is a powerful service which comes with great features like real time user count, event tracking, geo tracking, error & crash reporting and lot more and yet it is completely free.

In this article we are going to learn how to integrate Google Analytics in your android app. Although Google Analytics provides huge number of analytical features, we are going to cover only the essential modules necessary for every android app.

android google analytics integration tutorial



Below is the screenshot of real time users on one of the apps I have worked on.

android-google-analytics-real-time-users


1. Creating Google Analytics Property

Google Analytics tracking ID identifies the analytics property for which mobile tracking is enabled. Follow the below steps to get your property tracking ID.

1. Sign in to your Google Analytics account.

2. Select the Admin tab.

3. In the ACCOUNT dropdown, click on Create new account. (Or select the property if you created one already)

4. Select Mobile App in the new property form.

5. Enter the App Name.

6. Select Industry Category & Report Time Zone and click on Get Tracking ID.

7. In the next screen you will be shown your property Tracking ID.

The tracking ID should look like UA-XXXXXXXX-X. Note this tracking ID somewhere as we’ll need this in our android project.

android-creating-google-analytics-property


2. Creating Android Project

Once you got the tracking ID, let’s start the android project. We’ll create an android app with simple user interface to test the each module.

1. In Android Studio, create a new project by navigating to File ⇒ New Project and fill all the required details. When it prompts to select a default activity, select Blank Activity and proceed.

2. Turn your app into Material Design supported if you want to. But this step is optional.

3. Create two packages named activity and app to keep the project organized. Move the MainActivity.java under activity package.

4. Open strings.xml located under res ⇒ values and add below string values.

<resources>
    <string name="app_name">Google Analytics</string>
    <string name="action_settings">Settings</string>
    <string name="title_activity_second">Second Activity</string>
    <string name="toast_track_exception">Exception is recorded. Check the Crashes and Exceptions on Google Dashboard!</string>
    <string name="toast_app_crash">The app will crash now! Check the Crashes and Exceptions on Google Dashboard!</string>
    <string name="fragment_footer">Fragment is loaded! Check the screen view on Google Analytics Dashboard.</string>
    <string name="msg_instructions">Click on each button to see the appropriate tracking on Google Analytics</string>
    <string name="btn_second_screen">Second Screen</string>
    <string name="btn_send_event">Track Event</string>
    <string name="btn_exception_tracking">Track Exception</string>
    <string name="btn_app_crash">Track App Crash</string>
    <string name="btn_load_fragment">Fragment Tracking</string>
</resources>



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

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#FF6D00</color>
    <color name="colorPrimaryDark">#FF6D00</color>
    <color name="textColorPrimary">#FFFFFF</color>
    <color name="windowBackground">#FFFFFF</color>
    <color name="navigationBarColor">#000000</color>
    <color name="colorAccent">#FFD180</color>
    <color name="btnBackground">#e7e7e7</color>
</resources>


2.1 Adding Google Analytics

Now follow below steps to add the google analytics to your project.

6. Open build.gradle located under app folder and add google analytics dependency com.google.android.gms:play-services-analytics:7.3.0

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.google.android.gms:play-services-analytics:7.3.0'
}


7. Create a folder named xml under res. Inside xml folder, create an xml file named app_tracker.xml and add below analytics configuration.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- End current session if app sleeps for a period of time -->
    <integer name="ga_sessionTimeout">300</integer>

    <!-- Enable automatic Activity measurement -->
    <bool name="ga_autoActivityTracking">true</bool>

    <!--  The property id associated with this analytics tracker -->
    <string name="ga_trackingId">UA-58121605-1</string>

    <string name="ga_sampleFrequency">100.0</string>

    <bool name="ga_reportUncaughtExceptions">true</bool>

    <!--
      See Project Structure -> Analytics -> Google Analytics -> Learn More
      to learn more about configuring this file.
    -->
</resources>

Make sure that you replaced the ga_trackingId with your tracking ID which we retrieved in above section. This step is very important, otherwise you won’t be able to see the stats on your analytics dashboard.

8. Under app package, create a class named AnalyticsTrackers.java with the below code.


import android.content.Context;

import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.Tracker;

import java.util.HashMap;
import java.util.Map;

import info.androidhive.googleanalytics.R;

/**
 * A collection of Google Analytics trackers. Fetch the tracker you need using
 * {@code AnalyticsTrackers.getInstance().get(...)}
 * <p/>
 * This code was generated by Android Studio but can be safely modified by
 * hand at this point.
 * <p/>
 * TODO: Call {@link #initialize(Context)} from an entry point in your app
 * before using this!
 */
public final class AnalyticsTrackers {

    public enum Target {
        APP,
        // Add more trackers here if you need, and update the code in #get(Target) below
    }

    private static AnalyticsTrackers sInstance;

    public static synchronized void initialize(Context context) {
        if (sInstance != null) {
            throw new IllegalStateException("Extra call to initialize analytics trackers");
        }

        sInstance = new AnalyticsTrackers(context);
    }

    public static synchronized AnalyticsTrackers getInstance() {
        if (sInstance == null) {
            throw new IllegalStateException("Call initialize() before getInstance()");
        }

        return sInstance;
    }

    private final Map<Target, Tracker> mTrackers = new HashMap<Target, Tracker>();
    private final Context mContext;

    /**
     * Don't instantiate directly - use {@link #getInstance()} instead.
     */
    private AnalyticsTrackers(Context context) {
        mContext = context.getApplicationContext();
    }

    public synchronized Tracker get(Target target) {
        if (!mTrackers.containsKey(target)) {
            Tracker tracker;
            switch (target) {
                case APP:
                    tracker = GoogleAnalytics.getInstance(mContext).newTracker(R.xml.app_tracker);
                    break;
                default:
                    throw new IllegalArgumentException("Unhandled analytics target " + target);
            }
            mTrackers.put(target, tracker);
        }

        return mTrackers.get(target);
    }
}



9. Create a class named MyApplication.java under app package. This class extends from Application class which needs to be added in AndroidManifest.xml.

This call contains below important methods.

trackScreenView() – Function to track screen view (Activity or Fragment).

trackException() – Function to track exceptions using try & catch.

trackEvent() – Function to track event.


import android.app.Application;

import com.google.android.gms.analytics.GoogleAnalytics;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.StandardExceptionParser;
import com.google.android.gms.analytics.Tracker;

/**
 * Created by Ravi on 13/08/15.
 */
public class MyApplication extends Application {
    public static final String TAG = MyApplication.class
            .getSimpleName();

    private static MyApplication mInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;

        AnalyticsTrackers.initialize(this);
        AnalyticsTrackers.getInstance().get(AnalyticsTrackers.Target.APP);
    }

    public static synchronized MyApplication getInstance() {
        return mInstance;
    }

    public synchronized Tracker getGoogleAnalyticsTracker() {
        AnalyticsTrackers analyticsTrackers = AnalyticsTrackers.getInstance();
        return analyticsTrackers.get(AnalyticsTrackers.Target.APP);
    }

    /***
     * Tracking screen view
     *
     * @param screenName screen name to be displayed on GA dashboard
     */
    public void trackScreenView(String screenName) {
        Tracker t = getGoogleAnalyticsTracker();

        // Set screen name.
        t.setScreenName(screenName);

        // Send a screen view.
        t.send(new HitBuilders.ScreenViewBuilder().build());

        GoogleAnalytics.getInstance(this).dispatchLocalHits();
    }

    /***
     * Tracking exception
     *
     * @param e exception to be tracked
     */
    public void trackException(Exception e) {
        if (e != null) {
            Tracker t = getGoogleAnalyticsTracker();

            t.send(new HitBuilders.ExceptionBuilder()
                            .setDescription(
                                    new StandardExceptionParser(this, null)
                                            .getDescription(Thread.currentThread().getName(), e))
                            .setFatal(false)
                            .build()
            );
        }
    }

    /***
     * Tracking event
     *
     * @param category event category
     * @param action   action of the event
     * @param label    label
     */
    public void trackEvent(String category, String action, String label) {
        Tracker t = getGoogleAnalyticsTracker();

        // Build and send an Event.
        t.send(new HitBuilders.EventBuilder().setCategory(category).setAction(action).setLabel(label).build());
    }

}



10. Open AndroidManifest.xml and do the below changes.

> Add MyApplication to <application> tag.

> Add INTERNET and ACCESS_NETWORK_STATE permissions.

> Add the receivers and services mentioned below. These are optional, but recommended for accurate results.

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

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

    <application
        android:name=".app.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name">
        <activity
            android:name=".activity.MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <!--
          Optionally, register AnalyticsReceiver and AnalyticsService to support background
          dispatching on non-Google Play devices
        -->
        <receiver
            android:name="com.google.android.gms.analytics.AnalyticsReceiver"
            android:enabled="true">
            <intent-filter>
                <action android:name="com.google.android.gms.analytics.ANALYTICS_DISPATCH" />
            </intent-filter>
        </receiver>

        <service
            android:name="com.google.android.gms.analytics.AnalyticsService"
            android:enabled="true"
            android:exported="false" />

        <!--
             Optionally, register CampaignTrackingReceiver and CampaignTrackingService to enable
             installation campaign reporting
        -->
        <receiver
            android:name="com.google.android.gms.analytics.CampaignTrackingReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="com.android.vending.INSTALL_REFERRER" />
            </intent-filter>
        </receiver>

        <service android:name="com.google.android.gms.analytics.CampaignTrackingService" />
    </application>

</manifest>



Congratulations! your Google Analytics setup is done. Now if you run the app, you should see one user on Google Analytics dashboard. You won’t be able to see the user on the dashboard instantly due to slight delay in google analytics. You will have to wait for 2-3 mins to see the users on the dashboard. If no users are displayed, make sure that you followed the above steps correctly.

android-google-analytics-tracking-activity

Before start discussing about the modules, we’ll do the changes needed to create the interface to demonstrate them.

11. Under res ⇒ layout, create an xml layout named toolbar.xml and add below code.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    android:elevation="2dp"
    local:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    local:popupTheme="@style/ThemeOverlay.AppCompat.Light" />



12. Open the layout of your main activity (activity_main.xml) and do the below changes. This layout contains few buttons to invoke different analytics events.

<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:clipToPadding="false"
    tools:context=".MainActivity">

    <include
        android:id="@+id/toolbar"
        layout="@layout/toolbar"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"/>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/toolbar"
        android:orientation="vertical">

        <TextView
            android:layout_width="fill_parent"
            android:gravity="center_horizontal"
            android:padding="10dp"
            android:layout_height="wrap_content"
            android:text="@string/msg_instructions" />

        <Button android:id="@+id/btnSecondScreen"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/btnBackground"
            android:text="@string/btn_second_screen"
            android:textAllCaps="false"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:textColor="#797979"
            android:layout_gravity="center_horizontal"
            android:elevation="2dp"
            android:layout_marginTop="20dp"/>

        <Button android:id="@+id/btnSendEvent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/btnBackground"
            android:text="@string/btn_send_event"
            android:textAllCaps="false"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:textColor="#797979"
            android:layout_gravity="center_horizontal"
            android:elevation="2dp"
            android:layout_marginTop="20dp"/>

        <Button android:id="@+id/btnException"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/btnBackground"
            android:text="@string/btn_exception_tracking"
            android:textAllCaps="false"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:textColor="#797979"
            android:layout_gravity="center_horizontal"
            android:elevation="2dp"
            android:layout_marginTop="20dp"/>

        <Button android:id="@+id/btnAppCrash"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/btnBackground"
            android:text="@string/btn_app_crash"
            android:textAllCaps="false"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:textColor="#797979"
            android:layout_gravity="center_horizontal"
            android:elevation="2dp"
            android:layout_marginTop="20dp"/>

        <Button android:id="@+id/btnLoadFragment"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/btnBackground"
            android:text="@string/btn_load_fragment"
            android:textAllCaps="false"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:textColor="#797979"
            android:layout_gravity="center_horizontal"
            android:elevation="2dp"
            android:layout_marginTop="20dp"/>

    </LinearLayout>

    <FrameLayout
        android:id="@+id/frame_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true" />


</RelativeLayout>



13. Open MainActivity.java and do the below changes. Here we are declaring few buttons those are defined in the xml layout.

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import info.androidhive.googleanalytics.R;
import info.androidhive.googleanalytics.app.MyApplication;

public class MainActivity extends AppCompatActivity {

    private static String TAG = MainActivity.class.getSimpleName();

    private Toolbar mToolbar;

    private Button btnSecondScreen, btnSendEvent, btnException, btnAppCrash, btnLoadFragment;

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

        mToolbar = (Toolbar) findViewById(R.id.toolbar);
        btnSecondScreen = (Button) findViewById(R.id.btnSecondScreen);
        btnSendEvent = (Button) findViewById(R.id.btnSendEvent);
        btnException = (Button) findViewById(R.id.btnException);
        btnAppCrash = (Button) findViewById(R.id.btnAppCrash);
        btnLoadFragment = (Button) findViewById(R.id.btnLoadFragment);

        setSupportActionBar(mToolbar);
        getSupportActionBar().setDisplayShowHomeEnabled(true);

        /**
         * Launching another activity to track the other screen
         */
        btnSecondScreen.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }
}



Now if you run the app, it should look like below.

android-google-analytics-app-tutorial


2.1 Tracking Activity

Tracking activity view is very simple. It can be done in two ways. You can choose whichever way suits to your app requirement.

⇒ Automatic Screen Tracking

If you set ga_autoActivityTracking to true in app_tracker.xml, all the activities tracking will be automatically. However if you want to keep a name to screen, you need to mention the activity screen name in app_tracker.xml. If you have multiple activities, you have to mention the screen names for every activity.

Open app_tracker.xml and add below code. (Replace the package name with yours)

<screenName name="info.androidhive.googleanalytics.activity.MainActivity">Home Screen</screenName>

<screenName name="info.androidhive.googleanalytics.activity.SecondActivity">Second Screen</screenName>


⇒ Manual Screen Tracking

If you want to track the activity view manually, call the trackScreenView() in your activity onResume() method.

MyApplication.getInstance().trackScreenView("Home Screen");


android-google-analytics-tracking-activity-1


2.2 Tracking Fragment

Tacking fragment view is same as activity tracking except fragment can’t be tracking automatically. You have to manually invoke the screen view code in fragment onResume() method. We’ll test this by creating simple fragment and add it in our main activity.

14. Create an xml layout under res ⇒ layout named fragment_footer.xml.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="info.androidhive.googleanalytics.activity.FooterFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/fragment_footer"
        android:background="@color/colorAccent"
        android:padding="10dp"
        android:layout_margin="20dp"/>

</FrameLayout>



15. Create a class named FooterFragment.java and add the below code. You can notice that trackScreenView() is called in onResume() method which records the fragment view whenever the fragment is added or resumed.

MyApplication.getInstance().trackScreenView("Footer Fragment");
package info.androidhive.googleanalytics.activity;

import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import info.androidhive.googleanalytics.R;
import info.androidhive.googleanalytics.app.MyApplication;

public class FooterFragment extends Fragment {

    public FooterFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_footer, container, false);
    }

    @Override
    public void onResume() {
        super.onResume();

        // Tracking the screen view
        MyApplication.getInstance().trackScreenView("Footer Fragment");
    }
}



16. Now open MainActivity.java and add a click event listener to load the fragment.

/**
* Tracking Fragment View
*/
btnLoadFragment.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        FooterFragment footerFragment = new FooterFragment();
        fragmentTransaction.replace(R.id.frame_container, footerFragment);
        fragmentTransaction.commit();
    }
});



Now run the app and press the Fragment Tracking button. And after 2 – 3 mins you can the fragment screen view on Google Analytics dashboard.

android-google-analytics-tracking-fragment


2.3 Tracking Event

Event tracking allows us to record any kind of user interaction within the app. The interaction can be anything like button press, selecting a spinner, swiping gestures, completing game level etc.,

Event tracking accepts four parameters. Category, Action, Label and Value. Below is a simple event example when user presses the book download button.

MyApplication.getInstance().trackEvent("Book", "Download", "Track event example");

17. Open MainActivity.java and add below button click listener and call trackEvent() to track the event.

/**
* Event tracking
* Event(Category, Action, Label)
*/
btnSendEvent.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // Tracking Event
        MyApplication.getInstance().trackEvent("Book", "Download", "Track event example");

        Toast.makeText(getApplicationContext(), "Event \'Book\' \'Download\' \'Event example\' is sent. Check it on Google Analytics Dashboard!", Toast.LENGTH_LONG).show();
    }
});



Now run the app and press the Track Event button. On the Google Analytics dashboard click on Events to see the event sent from the app.

android-google-analytics-tracking-event


2.4 Tracking Exception

Exceptions can also be tracked very easily. This allows us to track the all the known exception which we tried to catch using try & catch block.

18. Open your MainActivity.java and add the below code. In this code we are recording a null pointer exception using try catch block. Call trackException() method to record the exception.

/**
* Tracking Exception Manually
* All known exceptions can be tracking this way
* using Try & Catch
*/
btnException.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        try {
                String name = null;
                if (name.equals("ravi")) {
                    /* Never comes here as it throws null pointer exception */
                }
            } catch (Exception e) {
                // Tracking exception
                MyApplication.getInstance().trackException(e);

                Toast.makeText(getApplicationContext(), getString(R.string.toast_track_exception), Toast.LENGTH_LONG).show();

                Log.e(TAG, "Exception: " + e.getMessage());
        }
    }
});



Now run the app and press the Track Exception button. You won’t be able to see the recorded exception right away on the dashboard. It normally takes 1 day to reflect on Google Analytics dashboard.

To see exceptions, goto Behaviour ⇒ Crashes and Exceptions on Google Analytics.

android-google-analytics-tracking-app-crash


2.5 Tracking App Crashes

App crashes happens due many unknown factors. App crash recorded automatically by setting ga_reportUncaughtExceptions to true in app_tracker.xml. We’ll test this by crashing the app intensionally.

19. Open MainActivity.java and add below code. Here we are dividing a number with zero which throws Arithmetic Exception and crashes the app.

/**
* Tracking App Crashes
* Manually generation app crash by dividing with zero
*/
btnAppCrash.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {

        Toast.makeText(getApplicationContext(), getString(R.string.toast_app_crash), Toast.LENGTH_LONG).show();

        Runnable r = new Runnable() {
            @Override
            public void run() {
                int answer = 12 / 0;
            }
        };

        Handler h = new Handler();
        h.postDelayed(r, 1500);
    }
});



Now run the app and press the Track App Crash. Your app should crash after 1.5 sec. This app crash will be recored and will be shown on Google Analytics dashboard. Goto Behaviour ⇒ Crashes and Exceptions section on dashboard to see the exception. This also takes 1 day to reflect on dashboard.

android-google-analytics-tracking-exception

I hope this articles gave you good start with Google Analytics SDK v4. Go through the docs and try other features offered by google analytics.

]]>
0
Ravi Tamada <![CDATA[Android adding SMS Verification Like WhatsApp – Part 2]]> http://www.androidhive.info/?p=38401 2015-08-01T17:23:12Z 2015-08-01T17:23:12Z In the 1st part of this article we have learned how to create the required REST API for this app. In this part we are going to see how to build the android app that interact with the API to receive the SMS and get it verified. As this project uses volley to make http calls, I suggest you go through my Volley tutorial to know the usage of volley. Also you need to have basic knowledge on android services and broadcast receivers.

We are going to use SMS broadcast receiver to read the sms whenever device receives it and an Intent Service to make the http calls which sends the OTP to server to get it verified.

android-sms-verification-like-whatsapp



Below is the screenshot of the app we are going to build now.

android-sms-verification-like-whatsapp-viber-2


6. Creating the Android App

This app contains two activities. One with a ViewPager with two pages. One page is to enter the mobile number and other page is to enter the OTP. The second activity is to display the logged in user profile information.

1. In Android Studio, create a new project by navigating to File ⇒ New Project and fill all the required details. When it prompts to select a default activity, select Blank Activity and proceed.

2. Open build.gradle located under app folder and add volley library dependency by adding com.mcxiaoke.volley:library-aar:1.0.0.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile "com.android.support:appcompat-v7:22.1.1"
    compile 'com.mcxiaoke.volley:library-aar:1.0.0'
}


6.1 Making the App Material

This step is optional but I recommend you go through it as it improves your knowledge on material design. Follow my Material Design tutorial to get to know how to make your android app material design ready.

3. Open strings.xml located under res ⇒ values and add below string values.

<resources>
    <string name="app_name">SMS Verification</string>

    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="title_activity_sms">SmsActivity</string>
    <string name="action_logout">Logout</string>
    <string name="msg_enter_mobile">Enter your mobile number to get started!</string>
    <string name="lbl_name">Name</string>
    <string name="lbl_email">Email</string>
    <string name="lbl_mobile">Mobile</string>
    <string name="lbl_next">NEXT</string>
    <string name="msg_sit_back">Sit back &amp; Relax! while we verify your mobile number</string>
    <string name="msg_manual_otp">(Enter the OTP below in case if we fail to detect the SMS automatically)</string>
    <string name="lbl_enter_otp">Enter OTP</string>
    <string name="lbl_submit">SUBMIT</string>
</resources>



4. Open colors.xml located under res ⇒ values and add below color values.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3b5bb3</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="textColorPrimary">#FFFFFF</color>
    <color name="windowBackground">#FFFFFF</color>
    <color name="navigationBarColor">#000000</color>
    <color name="colorAccent">#ea5d88</color>

    <color name="bg_view_sms">#ffd423</color>
    <color name="bg_view_otp">#fc6d38</color>
</resources>



4. Now open styles.xml located under res ⇒ values and add below styles.

<resources>

    <style name="MyMaterialTheme" parent="MyMaterialTheme.Base">

    </style>

    <style name="MyMaterialTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="windowNoTitle">true</item>
        <item name="windowActionBar">false</item>
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>



5. Under res directory, create a folder named values-v21. In this folder create another styles.xml and add below code.

<resources>

    <style name="MyMaterialTheme" parent="MyMaterialTheme.Base">
        <item name="android:windowContentTransitions">true</item>
        <item name="android:windowAllowEnterTransitionOverlap">true</item>
        <item name="android:windowAllowReturnTransitionOverlap">true</item>
        <item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
        <item name="android:windowSharedElementExitTransition">@android:transition/move</item>
    </style>

</resources>



7. Finally open the AndroidManifest.xml and add the MyMaterialTheme to <application> tag.

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

    <application
        android:theme="@style/MyMaterialTheme">
        .
        .
    </application>

</manifest>



Now if you run the app, you can see the top notification bar color changed which means the material design theme is applied.

3. Now create five packages named activity, app, helper, receiver and service. These packages helps in keeping the project organized.

Below is screenshot of the final project of this tutorial.

android-sms-verification-project-structure


4. Under app package, create a class named Config.java. This class contains very important app configuration information.

> URL_REQUEST_SMS and URL_VERIFY_OTP should be correct. The ip address should match with your localhost PC.

> SMS_ORIGIN should match the value in your PHP project’s Config.php.

> OTP_DELIMITER should also match the value in your PHP project’s Config.php.

package info.androidhive.smsverification.app;

/**
 * Created by Ravi on 08/07/15.
 */
public class Config {
    // server URL configuration
    public static final String URL_REQUEST_SMS = "http://192.168.0.101/android_sms/msg91/request_sms.php";
    public static final String URL_VERIFY_OTP = "http://192.168.0.101/android_sms/msg91/verify_otp.php";

    // SMS provider identification
    // It should match with your SMS gateway origin
    // You can use  MSGIND, TESTER and ALERTS as sender ID
    // If you want custom sender Id, approve MSG91 to get one
    public static final String SMS_ORIGIN = "ANHIVE";

    // special character to prefix the otp. Make sure this character appears only once in the sms
    public static final String OTP_DELIMITER = ":";
}



5. Under app package, create a class named MyApplication.java. This class initiates the volley core objects. This class extends from Application class which should be added in your AndroidManifest.xml <application> tag.

package info.androidhive.smsverification.app;

import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;

/**
 * Created by Ravi on 13/05/15.
 */

public class MyApplication extends Application {

    public static final String TAG = MyApplication.class
            .getSimpleName();

    private RequestQueue mRequestQueue;

    private static MyApplication mInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }

    public static synchronized MyApplication getInstance() {
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }

        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req, String tag) {
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }

    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        getRequestQueue().add(req);
    }

    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}



6. Open your AndroidManifest.xml and add the MyApplication to <application> tag.

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

    <application
        android:name=".app.MyApplication" ..>
        .
        .

    </application>

</manifest>



7. Under helper package, create a class named MyViewPager.java. This is a custom ViewPager class where we disable the swipe functionality of it.

package info.androidhive.smsverification.helper;

import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;

/**
 * Created by Ravi on 08/07/15.
 */
public class MyViewPager extends ViewPager {

    public MyViewPager(Context context) {
        super(context);
    }

    public MyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        // Never allow swiping to switch between pages
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // Never allow swiping to switch between pages
        return false;
    }
}



8. Create a class named PrefManager.java under helper package. This class contains methods to store user information in Shared Preferences.

package info.androidhive.smsverification.helper;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

import java.util.HashMap;

/**
 * Created by Ravi on 08/07/15.
 */
public class PrefManager {
    // Shared Preferences
    SharedPreferences pref;

    // Editor for Shared preferences
    Editor editor;

    // Context
    Context _context;

    // Shared pref mode
    int PRIVATE_MODE = 0;

    // Shared preferences file name
    private static final String PREF_NAME = "AndroidHive";

    // All Shared Preferences Keys
    private static final String KEY_IS_WAITING_FOR_SMS = "IsWaitingForSms";
    private static final String KEY_MOBILE_NUMBER = "mobile_number";
    private static final String KEY_IS_LOGGED_IN = "isLoggedIn";
    private static final String KEY_NAME = "name";
    private static final String KEY_EMAIL = "email";
    private static final String KEY_MOBILE = "mobile";

    public PrefManager(Context context) {
        this._context = context;
        pref = _context.getSharedPreferences(PREF_NAME, PRIVATE_MODE);
        editor = pref.edit();
    }

    public void setIsWaitingForSms(boolean isWaiting) {
        editor.putBoolean(KEY_IS_WAITING_FOR_SMS, isWaiting);
        editor.commit();
    }

    public boolean isWaitingForSms() {
        return pref.getBoolean(KEY_IS_WAITING_FOR_SMS, false);
    }

    public void setMobileNumber(String mobileNumber) {
        editor.putString(KEY_MOBILE_NUMBER, mobileNumber);
        editor.commit();
    }

    public String getMobileNumber() {
        return pref.getString(KEY_MOBILE_NUMBER, null);
    }

    public void createLogin(String name, String email, String mobile) {
        editor.putString(KEY_NAME, name);
        editor.putString(KEY_EMAIL, email);
        editor.putString(KEY_MOBILE, mobile);
        editor.putBoolean(KEY_IS_LOGGED_IN, true);
        editor.commit();
    }

    public boolean isLoggedIn() {
        return pref.getBoolean(KEY_IS_LOGGED_IN, false);
    }

    public void clearSession() {
        editor.clear();
        editor.commit();
    }

    public HashMap<String, String> getUserDetails() {
        HashMap<String, String> profile = new HashMap<>();
        profile.put("name", pref.getString(KEY_NAME, null));
        profile.put("email", pref.getString(KEY_EMAIL, null));
        profile.put("mobile", pref.getString(KEY_MOBILE, null));
        return profile;
    }
}


6.1 Creating SMS Receiver

Now we’ll see how to add a receiver which will be triggered whenever the device receives an SMS. Also we’ll add an Intent Service to make the http calls when the app is not running.

9. Under service package, create a class named HttpService.java and extend this class from IntentService. This service is useful to make the HTTP calls when the app is in background or killed. We’ll use this Intent Service to send the OTP to our server if the app is killed before receiving the SMS.

package info.androidhive.smsverification.service;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;

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

import java.util.HashMap;
import java.util.Map;

import info.androidhive.smsverification.activity.MainActivity;
import info.androidhive.smsverification.app.Config;
import info.androidhive.smsverification.app.MyApplication;
import info.androidhive.smsverification.helper.PrefManager;

/**
 * Created by Ravi on 04/04/15.
 */
public class HttpService extends IntentService {

    private static String TAG = HttpService.class.getSimpleName();

    public HttpService() {
        super(HttpService.class.getSimpleName());
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            String otp = intent.getStringExtra("otp");
            verifyOtp(otp);
        }
    }

    /**
     * Posting the OTP to server and activating the user
     *
     * @param otp otp received in the SMS
     */
    private void verifyOtp(final String otp) {
        StringRequest strReq = new StringRequest(Request.Method.POST,
                Config.URL_VERIFY_OTP, new Response.Listener<String>() {

            @Override
            public void onResponse(String response) {
                Log.d(TAG, response.toString());

                try {

                    JSONObject responseObj = new JSONObject(response);

                    // Parsing json object response
                    // response will be a json object
                    boolean error = responseObj.getBoolean("error");
                    String message = responseObj.getString("message");

                    if (!error) {
                        // parsing the user profile information
                        JSONObject profileObj = responseObj.getJSONObject("profile");

                        String name = profileObj.getString("name");
                        String email = profileObj.getString("email");
                        String mobile = profileObj.getString("mobile");

                        PrefManager pref = new PrefManager(getApplicationContext());
                        pref.createLogin(name, email, mobile);

                        Intent intent = new Intent(HttpService.this, MainActivity.class);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);

                        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();

                    } else {
                        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
                    }

                } catch (JSONException e) {
                    Toast.makeText(getApplicationContext(),
                            "Error: " + e.getMessage(),
                            Toast.LENGTH_LONG).show();
                }

            }
        }, new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(TAG, "Error: " + error.getMessage());
                Toast.makeText(getApplicationContext(),
                        error.getMessage(), Toast.LENGTH_SHORT).show();
            }
        }) {

            @Override
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<String, String>();
                params.put("otp", otp);

                Log.e(TAG, "Posting params: " + params.toString());
                return params;
            }

        };

        // Adding request to request queue
        MyApplication.getInstance().addToRequestQueue(strReq);
    }

}



9. Now under receiver package, create a class named SmsReceiver.java and extend the class from BroadcastReceiver. This is a broadcast receiver class which will be triggered whenever user device receives the SMS.

package info.androidhive.smsverification.receiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;

import info.androidhive.smsverification.app.Config;
import info.androidhive.smsverification.service.HttpService;

/**
 * Created by Ravi on 09/07/15.
 */
public class SmsReceiver extends BroadcastReceiver {
    private static final String TAG = SmsReceiver.class.getSimpleName();

    @Override
    public void onReceive(Context context, Intent intent) {

        final Bundle bundle = intent.getExtras();
        try {
            if (bundle != null) {
                Object[] pdusObj = (Object[]) bundle.get("pdus");
                for (Object aPdusObj : pdusObj) {
                    SmsMessage currentMessage = SmsMessage.createFromPdu((byte[]) aPdusObj);
                    String senderAddress = currentMessage.getDisplayOriginatingAddress();
                    String message = currentMessage.getDisplayMessageBody();

                    Log.e(TAG, "Received SMS: " + message + ", Sender: " + senderAddress);

                    // if the SMS is not from our gateway, ignore the message
                    if (!senderAddress.toLowerCase().contains(Config.SMS_ORIGIN.toLowerCase())) {
                        return;
                    }

                    // verification code from sms
                    String verificationCode = getVerificationCode(message);

                    Log.e(TAG, "OTP received: " + verificationCode);

                    Intent hhtpIntent = new Intent(context, HttpService.class);
                    hhtpIntent.putExtra("otp", verificationCode);
                    context.startService(hhtpIntent);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Exception: " + e.getMessage());
        }
    }

    /**
     * Getting the OTP from sms message body
     * ':' is the separator of OTP from the message
     *
     * @param message
     * @return
     */
    private String getVerificationCode(String message) {
        String code = null;
        int index = message.indexOf(Config.OTP_DELIMITER);

        if (index != -1) {
            int start = index + 2;
            int length = 6;
            code = message.substring(start, start + length);
            return code;
        }

        return code;
    }
}



10. To make the service and receiver working, open the AndroidManifest.xml and do the below changes.

> Add INTERNET, RECEIVE_SMS and READ_SMS permissions.

> Add MyApplication to <application> tag.

> Make SmsActivity as launcher activity. (We’ll create this activity shortly)

> Add SmsReceiver class using <receiver> tag.

> Add HttpService using <service> tag.

This is how your manifest file should look like.

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />

    <application
        android:name=".app.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/MyMaterialTheme">

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

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

        <activity
            android:name=".activity.MainActivity"
            android:label="@string/app_name"
            android:windowSoftInputMode="adjustResize">

        </activity>

        <!-- SMS Receiver -->
        <receiver android:name=".receiver.SmsReceiver">
            <intent-filter android:priority="99999">
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>


        <!-- Intent service -->
        <service
            android:name=".service.HttpService"
            android:exported="false" />

    </application>

</manifest>


6.2 Creating Mobile Login Screen

Now we have all the core logic ready. Let’s add the first activity to enter the mobile number and OTP.

12. Under res ⇒ layout, create an xml layout named activity_sms.xml and add below code. This layout contains a ViewPager with two pages. In one page we ask the user to enter his mobile number. In the second page we prompt the user to enter the OTP if the automatic sms verification fails.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/viewContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="info.androidhive.smsverification.activity.SmsActivity">

    <info.androidhive.smsverification.helper.MyViewPager
        android:id="@+id/viewPagerVertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <LinearLayout
            android:id="@+id/layout_sms"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorPrimary"
            android:gravity="center_horizontal"
            android:orientation="vertical">

            <ImageView
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:layout_gravity="center_horizontal"
                android:layout_marginBottom="25dp"
                android:layout_marginTop="100dp"
                android:src="@mipmap/ic_launcher" />

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="25dp"
                android:gravity="center_horizontal"
                android:inputType="textCapWords"
                android:paddingLeft="40dp"
                android:paddingRight="40dp"
                android:text="@string/msg_enter_mobile"
                android:textColor="@android:color/white"
                android:textSize="14dp" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <EditText
                    android:id="@+id/inputName"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="15dp"
                    android:background="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:hint="@string/lbl_name"
                    android:padding="5dp"
                    android:singleLine="true"
                    android:textColor="@color/colorPrimary"
                    android:textSize="18dp" />

                <EditText
                    android:id="@+id/inputEmail"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="15dp"
                    android:background="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:hint="@string/lbl_email"
                    android:inputType="textEmailAddress"
                    android:padding="5dp"
                    android:textColor="@color/colorPrimary"
                    android:textSize="18dp" />

                <EditText
                    android:id="@+id/inputMobile"
                    android:layout_width="240dp"
                    android:layout_height="wrap_content"
                    android:background="@android:color/white"
                    android:fontFamily="sans-serif-light"
                    android:hint="@string/lbl_mobile"
                    android:inputType="phone"
                    android:maxLength="10"
                    android:padding="5dp"
                    android:textColor="@color/colorPrimary"
                    android:textCursorDrawable="@null"
                    android:textSize="18dp" />

                <Button
                    android:id="@+id/btn_request_sms"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginTop="25dp"
                    android:background="@color/colorPrimaryDark"
                    android:text="@string/lbl_next"
                    android:textColor="@android:color/white"
                    android:textSize="14dp" />
            </LinearLayout>


        </LinearLayout>

        <LinearLayout
            android:id="@+id/layout_otp"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:background="@color/colorPrimary"
            android:gravity="center_horizontal"
            android:orientation="vertical">

            <ImageView
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:layout_gravity="center_horizontal"
                android:layout_marginBottom="25dp"
                android:layout_marginTop="100dp"
                android:src="@mipmap/ic_launcher" />

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="15dp"
                android:gravity="center_horizontal"
                android:paddingLeft="40dp"
                android:paddingRight="40dp"
                android:text="@string/msg_sit_back"
                android:textColor="@android:color/white"
                android:textSize="16dp" />

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="25dp"
                android:gravity="center_horizontal"
                android:paddingLeft="40dp"
                android:paddingRight="40dp"
                android:text="@string/msg_manual_otp"
                android:textColor="@android:color/white"
                android:textSize="12dp" />

            <EditText
                android:id="@+id/inputOtp"
                android:layout_width="120dp"
                android:layout_height="wrap_content"
                android:background="@android:color/white"
                android:fontFamily="sans-serif-light"
                android:gravity="center_horizontal"
                android:hint="@string/lbl_enter_otp"
                android:inputType="number"
                android:maxLength="6"
                android:padding="10dp"
                android:textCursorDrawable="@null"
                android:textSize="18dp" />

            <Button
                android:id="@+id/btn_verify_otp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginTop="25dp"
                android:background="@color/colorPrimaryDark"
                android:paddingLeft="20dp"
                android:paddingRight="20dp"
                android:text="@string/lbl_submit"
                android:textColor="@android:color/white"
                android:textSize="14dp" />

        </LinearLayout>

    </info.androidhive.smsverification.helper.MyViewPager>


    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_gravity="center"
        android:layout_marginBottom="60dp"
        android:indeterminateTint="@color/colorAccent"
        android:indeterminateTintMode="src_atop"
        android:visibility="gone" />

    <LinearLayout
        android:id="@+id/layout_edit_mobile"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="50dp"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/txt_edit_mobile"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="16dp" />

        <ImageButton
            android:id="@+id/btn_edit_mobile"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_marginLeft="10dp"
            android:background="@null"
            android:src="@drawable/ic_edit_mobile" />
    </LinearLayout>

</RelativeLayout>



13. Create an activity class named SmsActivity.java under activity package. In the below code

> requestForSMS() method make a call to server by passing name, email and mobile requesting for sms.

> verifyOtp() methods passes the otp received in the SMS to server to verify it.

package info.androidhive.smsverification.activity;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;

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

import java.util.HashMap;
import java.util.Map;

import info.androidhive.smsverification.R;
import info.androidhive.smsverification.app.Config;
import info.androidhive.smsverification.app.MyApplication;
import info.androidhive.smsverification.helper.PrefManager;
import info.androidhive.smsverification.service.HttpService;

public class SmsActivity extends AppCompatActivity implements View.OnClickListener {

    private static String TAG = SmsActivity.class.getSimpleName();

    private ViewPager viewPager;
    private ViewPagerAdapter adapter;
    private Button btnRequestSms, btnVerifyOtp;
    private EditText inputName, inputEmail, inputMobile, inputOtp;
    private ProgressBar progressBar;
    private PrefManager pref;
    private ImageButton btnEditMobile;
    private TextView txtEditMobile;
    private LinearLayout layoutEditMobile;


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

        viewPager = (ViewPager) findViewById(R.id.viewPagerVertical);
        inputName = (EditText) findViewById(R.id.inputName);
        inputEmail = (EditText) findViewById(R.id.inputEmail);
        inputMobile = (EditText) findViewById(R.id.inputMobile);
        inputOtp = (EditText) findViewById(R.id.inputOtp);
        btnRequestSms = (Button) findViewById(R.id.btn_request_sms);
        btnVerifyOtp = (Button) findViewById(R.id.btn_verify_otp);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);
        btnEditMobile = (ImageButton) findViewById(R.id.btn_edit_mobile);
        txtEditMobile = (TextView) findViewById(R.id.txt_edit_mobile);
        layoutEditMobile = (LinearLayout) findViewById(R.id.layout_edit_mobile);

        // view click listeners
        btnEditMobile.setOnClickListener(this);
        btnRequestSms.setOnClickListener(this);
        btnVerifyOtp.setOnClickListener(this);

        // hiding the edit mobile number
        layoutEditMobile.setVisibility(View.GONE);

        pref = new PrefManager(this);

        // Checking for user session
        // if user is already logged in, take him to main activity
        if (pref.isLoggedIn()) {
            Intent intent = new Intent(SmsActivity.this, MainActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(intent);

            finish();
        }

        adapter = new ViewPagerAdapter();
        viewPager.setAdapter(adapter);
        viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }

            @Override
            public void onPageSelected(int position) {
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });


        /**
         * Checking if the device is waiting for sms
         * showing the user OTP screen
         */
        if (pref.isWaitingForSms()) {
            viewPager.setCurrentItem(1);
            layoutEditMobile.setVisibility(View.VISIBLE);
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_request_sms:
                validateForm();
                break;

            case R.id.btn_verify_otp:
                verifyOtp();
                break;

            case R.id.btn_edit_mobile:
                viewPager.setCurrentItem(0);
                layoutEditMobile.setVisibility(View.GONE);
                pref.setIsWaitingForSms(false);
                break;
        }
    }

    /**
     * Validating user details form
     */
    private void validateForm() {
        String name = inputName.getText().toString().trim();
        String email = inputEmail.getText().toString().trim();
        String mobile = inputMobile.getText().toString().trim();

        // validating empty name and email
        if (name.length() == 0 || email.length() == 0) {
            Toast.makeText(getApplicationContext(), "Please enter your details", Toast.LENGTH_SHORT).show();
            return;
        }

        // validating mobile number
        // it should be of 10 digits length
        if (isValidPhoneNumber(mobile)) {

            // request for sms
            progressBar.setVisibility(View.VISIBLE);

            // saving the mobile number in shared preferences
            pref.setMobileNumber(mobile);

            // requesting for sms
            requestForSMS(name, email, mobile);

        } else {
            Toast.makeText(getApplicationContext(), "Please enter valid mobile number", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Method initiates the SMS request on the server
     *
     * @param name   user name
     * @param email  user email address
     * @param mobile user valid mobile number
     */
    private void requestForSMS(final String name, final String email, final String mobile) {
        StringRequest strReq = new StringRequest(Request.Method.POST,
                Config.URL_REQUEST_SMS, new Response.Listener<String>() {

            @Override
            public void onResponse(String response) {
                Log.d(TAG, response.toString());

                try {
                    JSONObject responseObj = new JSONObject(response);

                    // Parsing json object response
                    // response will be a json object
                    boolean error = responseObj.getBoolean("error");
                    String message = responseObj.getString("message");

                    // checking for error, if not error SMS is initiated
                    // device should receive it shortly
                    if (!error) {
                        // boolean flag saying device is waiting for sms
                        pref.setIsWaitingForSms(true);

                        // moving the screen to next pager item i.e otp screen
                        viewPager.setCurrentItem(1);
                        txtEditMobile.setText(pref.getMobileNumber());
                        layoutEditMobile.setVisibility(View.VISIBLE);

                        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();

                    } else {
                        Toast.makeText(getApplicationContext(),
                                "Error: " + message,
                                Toast.LENGTH_LONG).show();
                    }

                    // hiding the progress bar
                    progressBar.setVisibility(View.GONE);

                } catch (JSONException e) {
                    Toast.makeText(getApplicationContext(),
                            "Error: " + e.getMessage(),
                            Toast.LENGTH_LONG).show();

                    progressBar.setVisibility(View.GONE);
                }

            }
        }, new Response.ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(TAG, "Error: " + error.getMessage());
                Toast.makeText(getApplicationContext(),
                        error.getMessage(), Toast.LENGTH_SHORT).show();
                progressBar.setVisibility(View.GONE);
            }
        }) {

            /**
             * Passing user parameters to our server
             * @return
             */
            @Override
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<String, String>();
                params.put("name", name);
                params.put("email", email);
                params.put("mobile", mobile);

                Log.e(TAG, "Posting params: " + params.toString());

                return params;
            }

        };

        // Adding request to request queue
        MyApplication.getInstance().addToRequestQueue(strReq);
    }

    /**
     * sending the OTP to server and activating the user
     */
    private void verifyOtp() {
        String otp = inputOtp.getText().toString().trim();

        if (!otp.isEmpty()) {
            Intent grapprIntent = new Intent(getApplicationContext(), HttpService.class);
            grapprIntent.putExtra("otp", otp);
            startService(grapprIntent);
        } else {
            Toast.makeText(getApplicationContext(), "Please enter the OTP", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Regex to validate the mobile number
     * mobile number should be of 10 digits length
     *
     * @param mobile
     * @return
     */
    private static boolean isValidPhoneNumber(String mobile) {
        String regEx = "^[0-9]{10}$";
        return mobile.matches(regEx);
    }


    class ViewPagerAdapter extends PagerAdapter {

        @Override
        public int getCount() {
            return 2;
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == ((View) object);
        }

        public Object instantiateItem(View collection, int position) {

            int resId = 0;
            switch (position) {
                case 0:
                    resId = R.id.layout_sms;
                    break;
                case 1:
                    resId = R.id.layout_otp;
                    break;
            }
            return findViewById(resId);
        }
    }

}



Now run the app and test it once. Make sure that you have correct ip address of your localhost in Config.java

android-sms-verification-like-whatsapp-viber
android-sms-verification-like-whatsapp-viber-otp


6.2 Displaying logged in User Profile

Displaying the logged in user information is pretty straight forward. Previously the user information is store in Shared Preferences in HttpService.class. In this activity we have to read the information from shared preferences and display it on the screen.

14. Create an xml layout named toolbar.xml under res ⇒ layout.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    local:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    local:popupTheme="@style/ThemeOverlay.AppCompat.Light" />



15. Open the layout file your main activity (activity_main.xml) and do the below changes. This layout contains few TextViews to display the logged in user information.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/layout_toolbar"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:orientation="vertical">

        <include
            android:id="@+id/toolbar"
            layout="@layout/toolbar" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/layout_mobile"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:orientation="vertical">

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:textSize="18dp" />

        <TextView
            android:id="@+id/email"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:textSize="18dp" />

        <TextView
            android:id="@+id/mobile"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:textSize="18dp" />

    </LinearLayout>
</RelativeLayout>



17. Open menu_main.xml located under res ⇒ menu and add an action item to provide the logout option.

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".MainActivity">
    <item
        android:id="@+id/action_logout"
        android:orderInCategory="100"
        android:title="@string/action_logout"
        app:showAsAction="always" />
</menu>



16. Open MainActivity.java and do the below changes. In this activity we just read the user information stored in shared preferences and display it on the screen.

package info.androidhive.smsverification.activity;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

import java.util.HashMap;

import info.androidhive.smsverification.R;
import info.androidhive.smsverification.helper.PrefManager;


public class MainActivity extends AppCompatActivity {

    private Toolbar toolbar;
    private PrefManager pref;
    private TextView name, email, mobile;

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

        toolbar = (Toolbar) findViewById(R.id.toolbar);
        name = (TextView) findViewById(R.id.name);
        email = (TextView) findViewById(R.id.email);
        mobile = (TextView) findViewById(R.id.mobile);

        // enabling toolbar
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayShowHomeEnabled(true);

        pref = new PrefManager(getApplicationContext());


        // Checking if user session
        // if not logged in, take user to sms screen
        if (!pref.isLoggedIn()) {
            logout();
        }

        // Displaying user information from shared preferences
        HashMap<String, String> profile = pref.getUserDetails();
        name.setText("Name: " + profile.get("name"));
        email.setText("Email: " + profile.get("email"));
        mobile.setText("Mobile: " + profile.get("mobile"));
    }

    /**
     * Logging out user
     * will clear all user shared preferences and navigate to
     * sms activation screen
     */
    private void logout() {
        pref.clearSession();

        Intent intent = new Intent(MainActivity.this, SmsActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);

        startActivity(intent);

        finish();
    }


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

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_logout) {
            logout();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}


android-sms-verification-like-whatsapp-viber-1


7. Testing the App

To test the app, carefully follow the below steps.

> Make sure that your device and the PC running the php project are under the same wifi network.

> Keep correct username, password and database name in Config.php

> Give the correct MSG91_AUTH_KEY Key in Config.php

> Make sure that MSG91_SENDER_ID in Config.java and SMS_ORIGIN in Config.java are equal.

> Keep the correct ip address of your localhost in Config.java. On windows run ipconfig in command prompt to get the ip address.

I know running this app is little tricky for a beginner. But the following the above steps correctly will help you a lot. If you are still facing the problem in running this app, please do comment below.

]]>
0
Ravi Tamada <![CDATA[Android adding SMS Verification Like WhatsApp – Part 1]]> http://www.androidhive.info/?p=38258 2015-08-01T17:22:54Z 2015-08-01T17:22:54Z My previous tutorial explains how to implement the user login / registration by collecting user name, email and password. But what if you want to sign up an user using their mobile number like WhatsApp, Viber by verifying their mobile number in order to get genuine users.

Before going further make sure that you have basic knowledge about android client-sever communication. If you are new to this, don’t worry, these articles (MySQL Spinner & PHP, MySQL – Android) gives you a good start.

As this article seems lengthy, I have divided it into two parts. In this part we’ll learn the server side part i.e building the PHP, MySQL project by integrating the SMS gateway. In the 2nd part we cover the implementation of android app.

android-sms-verification-like-whatsapp


1. How It Works

Below is the simple architecture diagram that explains the process of complete registration which involves the PHP, MySQL server and a SMS gateway.

android-sms-verification-php-mysql

1. First user mobile number will be sent to our server where new user row will be created.

2. Our server requests the SMS gateway for an sms to the mobile number with a verification code.

3. SMS gateway sends an SMS to the user device with the verification code.

4. The verification code will be sent back our server again for verification. Our server verifies it and activates the user.

2. Choosing the SMS Gateway

SMS gateways are helpful in sending an SMS to any mobile number that comes under any mobile network. Basically gateways have a contract with all the mobile networks to send the SMS to a particular mobile number. In INDIA we have lot of SMS providers, but choosing the best SMS service provider is difficult. I found Solutions Infini, SMS Gupshup, Value First and Msg91 are quite popular. But the most important thing keep in mind while choosing the provider is make sure that they do support REST API as our server needs to trigger an SMS whenever a request comes from the android app.

Unfortunately there are no free SMS gateways to test your android app. You have to contact any of the service providers seeking for a demo account or buy the SMS credits.


3. Signing up with MSG91 (API Key)

To demonstrate the tutorial, I have selected Msg91 gateway for their simplicity. Follow the below steps to get register and obtain your REST API Key. Please be aware that this gateway is for INDIAN users only. If you are not from INDIA, choose the gateway which is available in your country.

1. Signup with Msg91.

2. Buy the SMS credits by selecting the appropriate details. In the form select SMS Type as
TRANSACTIONAL ROUTE and give no.of SMS as 2500 which is minimum credits to buy. The cost would be around ₹600.

3. Proceed with the payment. Once the payment was done, you can see the SMS credits credited into your account.

4. To obtain your API key, goto the API doc and click on KEY on the top right corner. You will be shown your API Key in a popup dialog. Copy this API Key, we’re gonna need it in our PHP project.

Check the below video demonstrating buying the SMS credits on Msg91.


4. Building the PHP, MySQL REST API

Now we have the API Key needed to send the SMS. Let’s start building the PHP project needed for the android app. We are going to build a simple REST API to manage the users who signs up using the android app. At the end of this project, we should be able to build two end points needed for the user registration. One is to register user which sends an SMS with a verification code. Second endpoint is to verify the code and completes the user registration.


4.1 Installing WAMP software

Download & Install WAMP server from www.wampserver.com/en/. Once WAMP is installed, launch the program from Start ⇒ All Programs ⇒ WampServer ⇒ StartWampServer. If you are on Mac, MAMP will be useful.

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


4.2 Creating MySQL Database

For this project we need two tables. One is users table to store the user information and second one is sms_codes to store the user’s sms verification codes.

Below are the columns needed in each table.

android sms verification mysql database

Open http://localhost/phpmyadmin and execute below queries to create required database and tables.

Create a database named android_sms.

CREATE DATABASE android_sms;

USE android_sms;

Create required tables and relationship by executing below sql queries.

CREATE TABLE `sms_codes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `code` varchar(6) NOT NULL,
  `status` int(1) NOT NULL DEFAULT '0',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;


CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `email` varchar(250) NOT NULL,
  `mobile` varchar(10) NOT NULL,
  `apikey` varchar(32) NOT NULL,
  `status` int(1) NOT NULL DEFAULT '0',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=64 ;


ALTER TABLE `sms_codes`
  ADD CONSTRAINT `sms_codes_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;


4.3 Creating PHP Project

Once you are done with database setup, let’s move to php project development. You can use any IDE like Netbeans to develop this project or just the notepad++ is enough.

1. Goto the WAMP project directory i.e C:/wamp/www and create a folder named android_sms. This will be the root directory of our PHP project.

2. Inside android_sms, create a folder named include. In this folder we keep all the configuration like database credentials, Msg91 API Key etc.,

3. Create a file under include folder named Config.php and add below code. Replace the MySQL credentials and Msg91 API Key in the below code with yours.

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


/**
 * MSG91 configuration
 */
define('MSG91_AUTH_KEY', "88276AGwzewOEdFs559d2888");
// sender id should 6 character long
define('MSG91_SENDER_ID', 'ANHIVE');

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



4. Under include folder, create another php file named DbConnect.php This file handles the database connection.

<?php

/**
 * Handling database connection
 *
 * @author Ravi Tamada
 * @link URL Tutorial link
 */
class DbConnect {

    private $conn;

    function __construct() {        
    }

    /**
     * Establishing database connection
     * @return database connection handler
     */
    function connect() {
        include_once dirname(__FILE__) . '/Config.php';

        // Connecting to mysql database
        $this->conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);

        // Check for database connection error
        if (mysqli_connect_errno()) {
            echo "Failed to connect to MySQL: " . mysqli_connect_error();
            exit;
        }

        // returing connection resource
        return $this->conn;
    }

}

?>



5. Create another php file named DbHandler.php under include folder. This file contains the major functions to perform the read, write operations onto database.

<?php

/**
 * Class to handle all db operations
 * This class will have CRUD methods for database tables
 *
 * @author Ravi Tamada
 * @link URL Tutorial link
 */
class DbHandler {

    private $conn;

    function __construct() {
        require_once dirname(__FILE__) . '/DbConnect.php';
        // opening db connection
        $db = new DbConnect();
        $this->conn = $db->connect();
    }

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

    /**
     * Creating new user
     * @param String $name User full name
     * @param String $email User login email id
     * @param String $mobile User mobile number
     * @param String $otp user verificaiton code
     */
    public function createUser($name, $email, $mobile, $otp) {
        $response = array();

        // First check if user already existed in db
        if (!$this->isUserExists($mobile)) {

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

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

            $result = $stmt->execute();

            $new_user_id = $stmt->insert_id;

            $stmt->close();

            // Check for successful insertion
            if ($result) {

                $otp_result = $this->createOtp($new_user_id, $otp);

                // User successfully inserted
                return USER_CREATED_SUCCESSFULLY;
            } else {
                // Failed to create user
                return USER_CREATE_FAILED;
            }
        } else {
            // User with same email already existed in the db
            return USER_ALREADY_EXISTED;
        }

        return $response;
    }

    public function createOtp($user_id, $otp) {

        // delete the old otp if exists
        $stmt = $this->conn->prepare("DELETE FROM sms_codes where user_id = ?");
        $stmt->bind_param("i", $user_id);
        $stmt->execute();


        $stmt = $this->conn->prepare("INSERT INTO sms_codes(user_id, code, status) values(?, ?, 0)");
        $stmt->bind_param("is", $user_id, $otp);

        $result = $stmt->execute();

        $stmt->close();

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

    public function activateUser($otp) {
        $stmt = $this->conn->prepare("SELECT u.id, u.name, u.email, u.mobile, u.apikey, u.status, u.created_at FROM users u, sms_codes WHERE sms_codes.code = ? AND sms_codes.user_id = u.id");
        $stmt->bind_param("s", $otp);

        if ($stmt->execute()) {
            // $user = $stmt->get_result()->fetch_assoc();
            $stmt->bind_result($id, $name, $email, $mobile, $apikey, $status, $created_at);
            
            $stmt->store_result();

            if ($stmt->num_rows > 0) {
                
                $stmt->fetch();
                
                // activate the user
                $this->activateUserStatus($id);
                
                $user = array();
                $user["name"] = $name;
                $user["email"] = $email;
                $user["mobile"] = $mobile;
                $user["apikey"] = $apikey;
                $user["status"] = $status;
                $user["created_at"] = $created_at;
                
                $stmt->close();
                
                return $user;
            } else {
                return NULL;
            }
        } else {
            return NULL;
        }

        return $result;
    }
    
    public function activateUserStatus($user_id){
        $stmt = $this->conn->prepare("UPDATE users set status = 1 where id = ?");
        $stmt->bind_param("i", $user_id);
        
        $stmt->execute();
        
        $stmt = $this->conn->prepare("UPDATE sms_codes set status = 1 where user_id = ?");
        $stmt->bind_param("i", $user_id);
        
        $stmt->execute();
    }

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



6. Now we have all the core logic ready. Let’s create an endpoint to register the user. In your project’s root directory, create a file named request_sms.php.

In below code

> First we receive the name, email and mobile number those were sent from the android device as a POST parameters.

> We create an entry in users table by calling createUser() function. Initially the user status will be 0 which indicate the user is inactive. This status will be made to 1 when the user verifies the OTP.

> Once the user row is created, we request for an SMS to the mobile number by calling sendSms() method.

> sendSms() method will make a call to Msg91 REST API to send SMS with a 6 digits OTP to the users mobile number.

Below is the sample SMS message the user will receive to their mobile. The OTP should be prefixed by : and space in the message.

Hello! Welcome to AndroidHive. Your OPT is : 228767
<?php

include './include/DbHandler.php';
$db = new DbHandler();


$response = array();

if (isset($_POST['mobile']) && $_POST['mobile'] != '') {

    $name = $_POST['name'];
    $email = $_POST['email'];
    $mobile = $_POST['mobile'];

    $otp = rand(100000, 999999);

    $res = $db->createUser($name, $email, $mobile, $otp);

    if ($res == USER_CREATED_SUCCESSFULLY) {
        
        // send sms
        sendSms($mobile, $otp);
        
        $response["error"] = false;
        $response["message"] = "SMS request is initiated! You will be receiving it shortly.";
    } else if ($res == USER_CREATE_FAILED) {
        $response["error"] = true;
        $response["message"] = "Sorry! Error occurred in registration.";
    } else if ($res == USER_ALREADY_EXISTED) {
        $response["error"] = true;
        $response["message"] = "Mobile number already existed!";
    }
} else {
    $response["error"] = true;
    $response["message"] = "Sorry! mobile number is not valid or missing.";
}

echo json_encode($response);

function sendSms($mobile, $otp) {
    
    $otp_prefix = ':';

    //Your message to send, Add URL encoding here.
    $message = urlencode("Hello! Welcome to AndroidHive. Your OPT is '$otp_prefix $otp'");

    $response_type = 'json';

    //Define route 
    $route = "4";
    
    //Prepare you post parameters
    $postData = array(
        'authkey' => MSG91_AUTH_KEY,
        'mobiles' => $mobile,
        'message' => $message,
        'sender' => MSG91_SENDER_ID,
        'route' => $route,
        'response' => $response_type
    );

//API URL
    $url = "https://control.msg91.com/sendhttp.php";

// init the resource
    $ch = curl_init();
    curl_setopt_array($ch, array(
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $postData
            //,CURLOPT_FOLLOWLOCATION => true
    ));


    //Ignore SSL certificate verification
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);


    //get response
    $output = curl_exec($ch);

    //Print error if any
    if (curl_errno($ch)) {
        echo 'error:' . curl_error($ch);
    }

    curl_close($ch);
}
?>



7. Now we need to create another endpoint to verify the OTP. Create a php file named verify_otp.php with below content.

In the below code

> The OTP will be received from the android device as a POST param.

> The user who matches with OTP will fetched from the users table.

> Then the user status will be set to 1 in both users and sms_codes table which makes the user active.

> The above two steps are implemented in activateUser() function.

<?php

include './include/DbHandler.php';
$db = new DbHandler();


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

if (isset($_POST['otp']) && $_POST['otp'] != '') {
    $otp = $_POST['otp'];


    $user = $db->activateUser($otp);

    if ($user != NULL) {

        $response["message"] = "User created successfully!";
        $response["profile"] = $user;
    } else {
        $response["message"] = "Sorry! Failed to create your account.";
    }
    
    
} else {
    $response["message"] = "Sorry! OTP is missing.";
}


echo json_encode($response);
?>


5. Testing the REST API

To test our API, install Postman chrome extension which allows you to test the endpoints by passing the parameters required.

Request SMS

URLMethodParametersDescription
http://localhost/android_sms/request_sms.phpPOSTname, email, mobileRequest SMS

The below json should be produced when SMS sent successfully.

{
    "error": false,
    "message": "SMS request is initiated! You will be receiving it shortly."
}



Verifying user OTP

URLMethodParametersDescription
http://localhost/android_sms/verify_otp.phpPOSTotpverifying user verification code

When the OTP is verified successfully, the complete user profile information should be produced in the json.

{
    "error": false,
    "message": "User created successfully!",
    "profile": {
        "name": "Ravi Tamada",
        "email": "ravi@androidhive.info",
        "mobile": "0000000000",
        "apikey": "088d196bacbe6bf08657720c9d562390",
        "status": 0,
        "created_at": "2015-07-30 22:18:59"
    }
}

Watch the below video know how to use Postman to test the endpoints.



This completes the server side part. In the 2nd part we are going to learn how to create the android app by integrating the API we have just built.

]]>
0
Ravi Tamada <![CDATA[Android Push Notifications using Parse.com]]> http://www.androidhive.info/?p=38330 2015-06-22T19:10:11Z 2015-06-22T19:10:11Z We use GCM in our apps to receive push notifications which is little tricky to implement. You can do the same using the free push service provided by Parse.com also. Compared to GCM, using parse is very simple. You can implement and test the push notifications in just 2 mins by following steps mentioned here. But this tutorial is aimed to customize the parse push service as much as possible.

In this tutorial we’ll learn how to customize the default push broadcast receiver, handling the json push messages, using the parse dashboard to send messages, customizing the push icons and lot more.

Android Push Notifications using Parse.com



We’ll also build a simple app with a login and home screen. Login screen prompts users to enter their email address which will be used to send personalized messages individually. Home screen is to show the push messages in a list view. So let’s get started with basic parse setup.

1. Parse Applciation ID, Client Key & parse-x.x.x.jar

To integrate parse in your app, you need Application ID, Client Key and parse jar library. Follow the below steps to get your app keys.

1. Sign Up by entering the details required.

2. Create a new parse app.

3. Once the app is created, go to Settings of the app and grab the Application ID and Client Key.

4. Download latest version of parse-1.9.2.jar. This library contains necessary functions to interact with parse API.

android parse application id and client key

Check out the below video demonstrating the parse setup.


2. Push Notification JSON

While we send push notification, we use json to interact with the android app. Below is the structure of the json which we use in this tutorial. This json structured varies from app to app depending upon the requirement.

is_background – flag decides whether to show the notification or not. This flag will be useful to do any background jobs on receiving push notification. When this flag is set to true, notification won’t to be shown to user.

{
    "data": {
        "message": "Hello! Welcome to parse notifications.",
        "title": "AndroidHive"
    },
    "is_background": false
}


3. Creating Android Project

1. In Android Studio, create a new project by navigating to File ⇒ New Project and fill all the required details.

2. Follow my material design tutorial to convert this app into material app. (But this step is optional)

3. In project’s app ⇒ libs folder, paste the Parse-1.9.2.jar file. You can find this jar file in the downloaded parse library that we have downloaded in above step.

4. Open build.gradle located under app folder and add below dependencies.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile "com.android.support:appcompat-v7:22.1.1"

    compile 'com.parse.bolts:bolts-android:1.+'
    compile fileTree(dir: 'libs', include: 'Parse-*.jar')
}



5. Open strings.xml and add below string values.

<resources>
    <string name="app_name">Parse Push</string>

    <string name="action_settings">Settings</string>
    <string name="title_activity_login">LoginActivity</string>
    <string name="action_logout">Logout</string>
</resources>



6. Open colors.xml and add below color values. If you did’t find colors.xml, create a new file with the name.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#218eed</color>
    <color name="colorPrimaryDark">#218eed</color>
    <color name="textColorPrimary">#FFFFFF</color>
    <color name="windowBackground">#FFFFFF</color>
    <color name="navigationBarColor">#000000</color>
    <color name="colorAccent">#8e8e8e</color>
</resources>



7. Under res ⇒ layout, create an xml named toolbar.xml. This adds an action bar to our app.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/colorPrimary"
    android:minHeight="?attr/actionBarSize"
    local:popupTheme="@style/ThemeOverlay.AppCompat.Light"
    local:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />



8. Now quickly create 5 packages named activity, app, helper, model and receiver under your project. These packages will helps to you keep your project organized.

android-parse-push-notifications-project-structure

9. Under app package, create a class named AppConfig.java and add below configuration. Replace parse keys with your Parse Application Key and Client Id.

The channel name ‘AndroidHive‘ is the channel to which every user will be subscribed to. So that you can send a generic message all the users subscribed to that channel.

package info.androidhive.parsenotifications.app;

/**
 * Created by Ravi on 15/05/15.
 */
public class AppConfig {
    public static final String PARSE_CHANNEL = "AndroidHive";
    public static final String PARSE_APPLICATION_ID = "ZUSZNPfsop5GnsjZPc4ajRittIUj3ilvC8dPLbC0";
    public static final String PARSE_CLIENT_KEY = "3kCVPLxA09hFUCkWB9Hc5isS6lOb6iYtG2gIcf1k";
    public static final int NOTIFICATION_ID = 100;
}



10. Now we’ll quickly create three helper classes required for this project. Create a class named ParseUtils.java under helper package. This class contains utility methods to interact with parse API like initializing the parse, subscribing using email to send individual notifications.

package info.androidhive.parsenotifications.helper;

import android.app.Activity;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import com.parse.Parse;
import com.parse.ParseException;
import com.parse.ParseInstallation;
import com.parse.ParsePush;
import com.parse.SaveCallback;

import info.androidhive.parsenotifications.app.AppConfig;

/**
 * Created by Ravi on 01/06/15.
 */
public class ParseUtils {

    private static String TAG = ParseUtils.class.getSimpleName();

    public static void verifyParseConfiguration(Context context) {
        if (TextUtils.isEmpty(AppConfig.PARSE_APPLICATION_ID) || TextUtils.isEmpty(AppConfig.PARSE_CLIENT_KEY)) {
            Toast.makeText(context, "Please configure your Parse Application ID and Client Key in AppConfig.java", Toast.LENGTH_LONG).show();
            ((Activity) context).finish();
        }
    }

    public static void registerParse(Context context) {
        // initializing parse library
        Parse.initialize(context, AppConfig.PARSE_APPLICATION_ID, AppConfig.PARSE_CLIENT_KEY);
        ParseInstallation.getCurrentInstallation().saveInBackground();

        ParsePush.subscribeInBackground(AppConfig.PARSE_CHANNEL, new SaveCallback() {
            @Override
            public void done(ParseException e) {
                Log.e(TAG, "Successfully subscribed to Parse!");
            }
        });
    }

    public static void subscribeWithEmail(String email) {
        ParseInstallation installation = ParseInstallation.getCurrentInstallation();

        installation.put("email", email);

        installation.saveInBackground();
    }
}


11. Create a class named NotificationUtils.java under helper package. This class contains below useful methods to handle the notifications.

> showNotificationMessage() – shows the notification in notification area by setting appropriate title, message, icon and intent.

> isAppIsInBackground() – checks whether your app is in background or foreground.

package info.androidhive.parsenotifications.helper;

import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.widget.Toast;

import java.util.List;

import info.androidhive.parsenotifications.R;
import info.androidhive.parsenotifications.activity.MainActivity;
import info.androidhive.parsenotifications.app.AppConfig;

/**
 * Created by Ravi on 01/06/15.
 */
public class NotificationUtils {

    private String TAG = NotificationUtils.class.getSimpleName();

    private Context mContext;

    public NotificationUtils() {
    }

    public NotificationUtils(Context mContext) {
        this.mContext = mContext;
    }

    public void showNotificationMessage(String title, String message, Intent intent) {

        // Check for empty push message
        if (TextUtils.isEmpty(message))
            return;

        if (isAppIsInBackground(mContext)) {
            // notification icon
            int icon = R.mipmap.ic_launcher;

            int mNotificationId = AppConfig.NOTIFICATION_ID;

            PendingIntent resultPendingIntent =
                    PendingIntent.getActivity(
                            mContext,
                            0,
                            intent,
                            PendingIntent.FLAG_CANCEL_CURRENT
                    );

            NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();

            NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
                    mContext);
            Notification notification = mBuilder.setSmallIcon(icon).setTicker(title).setWhen(0)
                    .setAutoCancel(true)
                    .setContentTitle(title)
                    .setStyle(inboxStyle)
                    .setContentIntent(resultPendingIntent)
                    .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
                    .setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), icon))
                    .setContentText(message)
                    .build();

            NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.notify(mNotificationId, notification);
        } else {
            intent.putExtra("title", title);
            intent.putExtra("message", message);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
            mContext.startActivity(intent);
        }
    }

    /**
     * Method checks if the app is in background or not
     *
     * @param context
     * @return
     */
    public static boolean isAppIsInBackground(Context context) {
        boolean isInBackground = true;
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT_WATCH) {
            List<ActivityManager.RunningAppProcessInfo> runningProcesses = am.getRunningAppProcesses();
            for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
                if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                    for (String activeProcess : processInfo.pkgList) {
                        if (activeProcess.equals(context.getPackageName())) {
                            isInBackground = false;
                        }
                    }
                }
            }
        } else {
            List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
            ComponentName componentInfo = taskInfo.get(0).topActivity;
            if (componentInfo.getPackageName().equals(context.getPackageName())) {
                isInBackground = false;
            }
        }

        return isInBackground;
    }
}



12. Create another class named PrefManager.java under helper package. This class handles the user’s session with the help of shared preferences.

package info.androidhive.parsenotifications.helper;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

/**
 * Created by Ravi on 01/06/15.
 */
public class PrefManager {
    // Shared Preferences
    SharedPreferences pref;

    // Editor for Shared preferences
    Editor editor;

    // Context
    Context _context;

    // Shared pref mode
    int PRIVATE_MODE = 0;

    // Shared pref file name
    private static final String PREF_NAME = "AndroidHive";

    // All Shared Preferences Keys
    private static final String IS_LOGIN = "IsLoggedIn";

    // Email address
    private static final String KEY_EMAIL = "email";

    // Constructor
    public PrefManager(Context context) {
        this._context = context;
        pref = _context.getSharedPreferences(PREF_NAME, PRIVATE_MODE);
        editor = pref.edit();
    }

    /**
     * Create login session
     */
    public void createLoginSession(String email) {
        // Storing login value as TRUE
        editor.putBoolean(IS_LOGIN, true);

        // Storing email in pref
        editor.putString(KEY_EMAIL, email);

        // commit changes
        editor.commit();
    }

    public String getEmail() {
        return pref.getString(KEY_EMAIL, null);
    }

    public boolean isLoggedIn() {
        return pref.getBoolean(IS_LOGIN, false);
    }

    public void logout() {
        editor.clear();
        editor.commit();
    }
}


13. Now under app package, create another class named MyApplication.java and add below code. This is an Application class which will be executed on app launch. So we initialize the parse by calling ParseUtils.registerParse() method which initializes the parse and subscribe the user to AndroidHive channel.

package info.androidhive.parsenotifications.app;


import android.app.Application;

import info.androidhive.parsenotifications.helper.ParseUtils;

public class MyApplication extends Application {

    private static MyApplication mInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;

        // register with parse
        ParseUtils.registerParse(this);
    }


    public static synchronized MyApplication getInstance() {
        return mInstance;
    }
}


3.1 Creating Custom Parse Push Receiver

Parse comes with a default broadcast push receiver, but the options are limited. If you want to fully utilize the parse notifications, the best option is create a custom broad cast receiver.

14. Under receiver package, create a class named CustomPushReceiver.java which extends ParsePushBroadcastReceiver.

onPushReceive() – method will be called whenever push message is received. In this message the json message will be parsed and shown to user.

package info.androidhive.parsenotifications.receiver;

import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.parse.ParsePushBroadcastReceiver;

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

import info.androidhive.parsenotifications.activity.MainActivity;
import info.androidhive.parsenotifications.helper.NotificationUtils;

/**
 * Created by Ravi on 01/06/15.
 */
public class CustomPushReceiver extends ParsePushBroadcastReceiver {
    private final String TAG = CustomPushReceiver.class.getSimpleName();

    private NotificationUtils notificationUtils;

    private Intent parseIntent;

    public CustomPushReceiver() {
        super();
    }

    @Override
    protected void onPushReceive(Context context, Intent intent) {
        super.onPushReceive(context, intent);

        if (intent == null)
            return;

        try {
            JSONObject json = new JSONObject(intent.getExtras().getString("com.parse.Data"));

            Log.e(TAG, "Push received: " + json);

            parseIntent = intent;

            parsePushJson(context, json);

        } catch (JSONException e) {
            Log.e(TAG, "Push message json exception: " + e.getMessage());
        }
    }

    @Override
    protected void onPushDismiss(Context context, Intent intent) {
        super.onPushDismiss(context, intent);
    }

    @Override
    protected void onPushOpen(Context context, Intent intent) {
        super.onPushOpen(context, intent);
    }

    /**
     * Parses the push notification json
     *
     * @param context
     * @param json
     */
    private void parsePushJson(Context context, JSONObject json) {
        try {
            boolean isBackground = json.getBoolean("is_background");
            JSONObject data = json.getJSONObject("data");
            String title = data.getString("title");
            String message = data.getString("message");

            if (!isBackground) {
                Intent resultIntent = new Intent(context, MainActivity.class);
                showNotificationMessage(context, title, message, resultIntent);
            }

        } catch (JSONException e) {
            Log.e(TAG, "Push message json exception: " + e.getMessage());
        }
    }


    /**
     * Shows the notification message in the notification bar
     * If the app is in background, launches the app
     *
     * @param context
     * @param title
     * @param message
     * @param intent
     */
    private void showNotificationMessage(Context context, String title, String message, Intent intent) {

        notificationUtils = new NotificationUtils(context);

        intent.putExtras(parseIntent.getExtras());

        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

        notificationUtils.showNotificationMessage(title, message, intent);
    }
}



15. Now we have all the helper classes required. Open AndroidManifest.xml and do the below changes.

> Add MyApplication to your <application> tag

> Make LoginActivity as launcher activity. We’ll create this activity shortly.

> Replace info.androidhive.parsenotifications with your project’s package name.

> Add the custom receiver class CustomPushReceiver using <receiver> tag.

> Add the necessary permissions mentioned.

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

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

    <!--
      IMPORTANT: Change "info.androidhive.parsenotifications.permission.C2D_MESSAGE" in the lines below
      to match your app's package name + ".permission.C2D_MESSAGE".
    -->
    <permission
        android:name="info.androidhive.parsenotifications.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

    <uses-permission android:name="info.androidhive.parsenotifications.permission.C2D_MESSAGE" />

    <application
        android:name=".app.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/MyMaterialTheme">
        <activity
            android:name=".activity.LoginActivity"
            android:label="@string/app_name"
            android:windowSoftInputMode="stateHidden|adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".activity.MainActivity"
            android:label="@string/app_name" />

        <!-- Added for Parse push notifications -->

        <service android:name="com.parse.PushService" />

        <receiver
            android:name=".receiver.CustomPushReceiver"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.USER_PRESENT" />
                <action android:name="com.parse.push.intent.RECEIVE" />
                <action android:name="com.parse.push.intent.DELETE" />
                <action android:name="com.parse.push.intent.OPEN" />
            </intent-filter>
        </receiver>
        <receiver
            android:name="com.parse.GcmBroadcastReceiver"
            android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

                <!-- IMPORTANT: Change "info.androidhive.parsenotifications" to match your app's package name. -->
                <category android:name="info.androidhive.parsenotifications" />
            </intent-filter>
        </receiver>

        <!-- /Added for Parse push notifications -->
    </application>

</manifest>


3.1 Creating Login Activity

Now we’ll add the login screen to our app. This will be the launcher activity which prompts user to enter the email to login. Using this email address, we’ll subscribe the user to parse channel email.

16. Create an xml layout named activity_login.xml under res ⇒ layout folder.

<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="#ffffff"
    tools:context="info.androidhive.parsenotifications.activity.LoginActivity">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:orientation="vertical"
        android:paddingBottom="10dp"
        android:paddingTop="10dp">

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:fontFamily="sans-serif-light"
            android:gravity="center_horizontal"
            android:text="Parse Notifications"
            android:textColor="#218eed"
            android:textSize="24dp" />

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp"
            android:fontFamily="sans-serif-light"
            android:gravity="center_horizontal"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:text="Enter your email address to subscribe to parse channel!"
            android:textColor="#9d9d9d"
            android:textSize="16dp" />

        <EditText
            android:id="@+id/email"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="40dp"
            android:background="@android:color/white"
            android:hint="Your email address"
            android:gravity="center_horizontal"
            android:inputType="textEmailAddress"
            android:padding="10dp"
            android:textColor="#444444"
            android:textColorHint="#888888"
            android:textSize="18dp" />

        <View
            android:layout_width="fill_parent"
            android:layout_height="1dp"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="40dp"
            android:background="#dedede"/>

        <Button
            android:id="@+id/btnLogin"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="40dp"
            android:layout_marginRight="40dp"
            android:layout_marginTop="20dp"
            android:background="#218eed"
            android:text="Login"
            android:textColor="@android:color/white" />
    </LinearLayout>


</RelativeLayout>



17. Under activity package, create a new activity named LoginActivity.java and add below code. This activity get the email address and stores it in shared preferences.

package info.androidhive.parsenotifications.activity;

import android.content.Intent;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import info.androidhive.parsenotifications.R;
import info.androidhive.parsenotifications.app.AppConfig;
import info.androidhive.parsenotifications.helper.ParseUtils;
import info.androidhive.parsenotifications.helper.PrefManager;

public class LoginActivity extends ActionBarActivity implements View.OnClickListener {

    private EditText inputEmail;
    private Button btnLogin;
    private PrefManager pref;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Verifying parse configuration. This is method is for developers only.
        ParseUtils.verifyParseConfiguration(this);

        pref = new PrefManager(getApplicationContext());
        if (pref.isLoggedIn()) {
            Intent intent = new Intent(LoginActivity.this, MainActivity.class);
            startActivity(intent);

            finish();
        }

        setContentView(R.layout.activity_login);

        inputEmail = (EditText) findViewById(R.id.email);
        btnLogin = (Button) findViewById(R.id.btnLogin);

        btnLogin.setOnClickListener(this);
    }


    private void login() {
        String email = inputEmail.getText().toString();

        if (isValidEmail(email)) {

            pref.createLoginSession(email);

            Intent intent = new Intent(LoginActivity.this, MainActivity.class);
            startActivity(intent);

            finish();
        } else {
            Toast.makeText(getApplicationContext(), "Please enter valid email address!", Toast.LENGTH_LONG).show();
        }
    }

    public final static boolean isValidEmail(CharSequence target) {
        return !TextUtils.isEmpty(target) && android.util.Patterns.EMAIL_ADDRESS.matcher(target).matches();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnLogin:
                login();
                break;
            default:
        }
    }
}


android-push-notifications-email-subscription


3.2 Showing the Push Messages in List View

18. Under model package, create a class named Message.java. This class is used to pass the message objects to list adapter.

package info.androidhive.parsenotifications.model;

/**
 * Created by Ravi on 01/06/15.
 */
public class Message {
    private String message;
    private long timestamp;

    public Message() {
    }

    public Message(String message, long timestamp) {
        this.message = message;
        this.timestamp = timestamp;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(long timestamp) {
        this.timestamp = timestamp;
    }
}



19. Create an xml layout named list_row.xml. This layout renders the single list item row.

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

    <TextView
        android:id="@+id/message"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="5dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="10dp"
        android:textSize="16dp" />

    <TextView
        android:id="@+id/timestamp"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="15dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textColor="#666666" />

</LinearLayout>



20. Open layout file of main activity (activity_main.xml) and add a ListView.

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

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <include
            android:id="@+id/toolbar"
            layout="@layout/toolbar" />
    </LinearLayout>

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

</LinearLayout>



21. Finally open your main activity class MainActivity.java and do the below changes. Here onNewIntent() method will be called whenever a new push message is received. We’ll add the new message to list data source and refresh the list view.

package info.androidhive.parsenotifications.activity;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

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

import info.androidhive.parsenotifications.R;
import info.androidhive.parsenotifications.helper.ParseUtils;
import info.androidhive.parsenotifications.helper.PrefManager;
import info.androidhive.parsenotifications.model.Message;


public class MainActivity extends AppCompatActivity {

    private static String TAG = MainActivity.class.getSimpleName();

    private Toolbar mToolbar;
    private ListView listView;
    private List<Message> listMessages = new ArrayList<>();
    private MessageAdapter adapter;
    private PrefManager pref;

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

        listView = (ListView) findViewById(R.id.list_view);
        mToolbar = (Toolbar) findViewById(R.id.toolbar);

        setSupportActionBar(mToolbar);
        getSupportActionBar().setDisplayShowHomeEnabled(true);

        adapter = new MessageAdapter(this);
        pref = new PrefManager(getApplicationContext());

        listView.setAdapter(adapter);

        Intent intent = getIntent();

        String email = intent.getStringExtra("email");

        if (email != null) {
            ParseUtils.subscribeWithEmail(pref.getEmail());
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        String message = intent.getStringExtra("message");

        Message m = new Message(message, System.currentTimeMillis());
        listMessages.add(0, m);
        adapter.notifyDataSetChanged();
    }

    private class MessageAdapter extends BaseAdapter {

        LayoutInflater inflater;

        public MessageAdapter(Activity activity) {
            inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

        @Override
        public int getCount() {
            return listMessages.size();
        }

        @Override
        public Object getItem(int position) {
            return listMessages.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = convertView;
            if (view == null) {
                view = inflater.inflate(R.layout.list_row, null);
            }

            TextView txtMessage = (TextView) view.findViewById(R.id.message);
            TextView txtTimestamp = (TextView) view.findViewById(R.id.timestamp);

            Message message = listMessages.get(position);
            txtMessage.setText(message.getMessage());

            CharSequence ago = DateUtils.getRelativeTimeSpanString(message.getTimestamp(), System.currentTimeMillis(),
                    0L, DateUtils.FORMAT_ABBREV_ALL);

            txtTimestamp.setText(String.valueOf(ago));

            return view;
        }
    }

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

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_logout) {
            pref.logout();
            Intent intent = new Intent(MainActivity.this, LoginActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            startActivity(intent);
            finish();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}


This completes the android part. Now we’ll learn how to use the parse dashboard to send push messages.

android-push-notifications-list-view


4. Sending Messages using Parse Dashboard

Parse provides the push dashboard to send messages to all your app users. Login to parse, select the app and click on + Send a push. You can find necessary filters to send push notifications like by platform, channel etc., Checkout the demo video above demonstrating sending the push messages to all users, by a channel or by email.


5. Sending Messages using Parse REST API

Parse also provides a REST API for various languages to send the messages from your server directly instead of using the parse dashboard. This REST API is very useful when you want to automate the push messages when user is interacting with the android app.

Go through the REST API guide to integrate the API with the language you choose.

]]>
0
Ravi Tamada <![CDATA[Android Swipe Down to Refresh ListView Tutorial]]> http://www.androidhive.info/?p=38270 2015-05-14T11:30:52Z 2015-05-14T11:30:52Z You might have noticed that lot of android apps like Twitter, Google+ provides an option to swipe / pull down to refresh it’s content. Whenever user swipes down from top, a loader will be shown and will disappear once the new content is loaded. In this tutorial we are going to learn how to provide the same option to your apps too.

Previously we used to implement a custom swipe view to detect the swipe down. But android made our day easier by introducing SwipeRefreshLayout in android.support.v4 to detect the vertical swipe on any view.

android-swipe-down-to-refresh-list-view-tutorial


1. Android SwipeRefreshLayout

Implementing SwipeRefreshLayout is very easy. Whenever you want to detect the swipe down on any view, just the wrap the view around SwipeRefreshLayout element. In our case, we are going to use it with ListView. And implement your activity class from SwipeRefreshLayout.OnRefreshListener. When user swipes down the view, onRefresh() method will be triggered. In you need to take appropriate action in that function like making an http call and fetch the latest data.

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipe_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <!-- place your view here -->

</android.support.v4.widget.SwipeRefreshLayout>


2. Example JSON

To demonstrate this tutorial, I am showing IMDB top 250 movies in a List View. For this I have created a json service which gives 20 movies in each request. You need to pass offset param to get the next set of results. Initially offset value should be 0, whenever the list is swiped down, we make an http call to get the next 20 movies and will update the ListView.

URL: http://api.androidhive.info/json/imdb_top_250.php?offset=0


3. Creating Android Project

1. In Android Studio, create a new project by navigating to File ⇒ New Project and fill all the required details. When it prompts to select a default activity, select Blank Activity and proceed.

2. Open build.gradle located under app folder and add volley library dependency. We are going to use volley to make HTTP calls to fetch the json.

com.mcxiaoke.volley:library-aar:1.0.0

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.1.0'
    compile 'com.mcxiaoke.volley:library-aar:1.0.0'
}



3. Open colors.xml under res ⇒ values and add below color resources. If you don’t find colors.xml, create a new file with the name. The color resources added below are used to set background color for movies rank in list view.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="movie_serial_bg">
        <item>#24c6d5</item>
        <item>#57dd86</item>
        <item>#ad7dcf</item>
        <item>#ff484d</item>
        <item>#fcba59</item>
        <item>#24c6d5</item>
    </string-array>
</resources>



3. Now under your project’s package, create three packages named app, activity and helper.

4. Under app package, create a class named MyApplication.java and add below code. This is a singleton Application class which initiates volley core objects on app launch.

package info.androidhive.swiperefresh.app;

import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;

/**
 * Created by Ravi on 13/05/15.
 */

public class MyApplication extends Application {

    public static final String TAG = MyApplication.class
            .getSimpleName();

    private RequestQueue mRequestQueue;

    private static MyApplication mInstance;

    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }

    public static synchronized MyApplication getInstance() {
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }

        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req, String tag) {
        req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
        getRequestQueue().add(req);
    }

    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        getRequestQueue().add(req);
    }

    public void cancelPendingRequests(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}



5. Open AndroidManifest.xml and add MyApplication.java class to <application> tag. Also you need to add INTERNET permission as we need to make http calls.

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

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

    <application
        android:name=".app.MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity
            android:name=".activity.MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>



6. Now let’s create a custom adapter class for our list view. Under res ⇒ layout folder, create an xml layout named list_row.xml. This xml renders single list row in the ListView.

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

    <TextView
        android:id="@+id/serial"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="25dp"
        android:layout_margin="5dp"
        android:layout_alignParentLeft="true"
        android:textSize="20dp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/title"
        android:layout_toRightOf="@id/serial"
        android:layout_centerVertical="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:paddingLeft="20dp"
        android:textSize="18dp" />

</RelativeLayout>



7. Under helper package, create a java class named Movie.java and add below code. This is a model class required to create movie objects to provide data to the List View

package info.androidhive.swiperefresh.helper;

/**
 * Created by Ravi on 13/05/15.
 */
public class Movie {
    public int id;
    public String title;

    public Movie() {
    }

    public Movie(int id, String title) {
        this.title = title;
        this.id = id;
    }
}



8. Under helper package, create another class named SwipeListAdapter.java. This class is a custom adapter class which inflates the list_row.xml by applying proper data.

package info.androidhive.swiperefresh.helper;

import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.List;

import info.androidhive.swiperefresh.R;

/**
 * Created by Ravi on 13/05/15.
 */
public class SwipeListAdapter extends BaseAdapter {
    private Activity activity;
    private LayoutInflater inflater;
    private List<Movie> movieList;
    private String[] bgColors;

    public SwipeListAdapter(Activity activity, List<Movie> movieList) {
        this.activity = activity;
        this.movieList = movieList;
        bgColors = activity.getApplicationContext().getResources().getStringArray(R.array.movie_serial_bg);
    }

    @Override
    public int getCount() {
        return movieList.size();
    }

    @Override
    public Object getItem(int location) {
        return movieList.get(location);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (inflater == null)
            inflater = (LayoutInflater) activity
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (convertView == null)
            convertView = inflater.inflate(R.layout.list_row, null);

        TextView serial = (TextView) convertView.findViewById(R.id.serial);
        TextView title = (TextView) convertView.findViewById(R.id.title);

        serial.setText(String.valueOf(movieList.get(position).id));
        title.setText(movieList.get(position).title);

        String color = bgColors[position % bgColors.length];
        serial.setBackgroundColor(Color.parseColor(color));

        return convertView;
    }

}



10. Now we have all the required files in place, let’s start implementing the actual swipe refresh view. Open the layout file of your main activity (activity_main.xml) and modify the layout as shown below. I have added a ListView to show list of movies and wrapped it around SwipeRefreshLayout to get the swipe to refresh.

<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipe_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

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

    </ListView>

</android.support.v4.widget.SwipeRefreshLayout>



11. Finally open MainActivity.java and do the below changes to achieve the swipe refresh list view.

> Implement the activity from SwipeRefreshLayout.OnRefreshListener and override the onRefresh() method.

> Call fetchMovies() which is a volley’s json array call to fetch the json and update the list view.

> onRefresh() is triggered whenever user swipes down the view. So call fetchMovies() inside this method to get the next set of movies response.

package info.androidhive.swiperefresh.activity;

import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.widget.ListView;
import android.widget.Toast;

import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;

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

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

import info.androidhive.swiperefresh.R;
import info.androidhive.swiperefresh.app.MyApplication;
import info.androidhive.swiperefresh.helper.Movie;
import info.androidhive.swiperefresh.helper.SwipeListAdapter;


public class MainActivity extends ActionBarActivity implements SwipeRefreshLayout.OnRefreshListener {

    private String TAG = MainActivity.class.getSimpleName();

    private String URL_TOP_250 = "http://api.androidhive.info/json/imdb_top_250.php?offset=";

    private SwipeRefreshLayout swipeRefreshLayout;
    private ListView listView;
    private SwipeListAdapter adapter;
    private List<Movie> movieList;

    // initially offset will be 0, later will be updated while parsing the json
    private int offSet = 0;

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

        listView = (ListView) findViewById(R.id.listView);
        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);

        movieList = new ArrayList<>();
        adapter = new SwipeListAdapter(this, movieList);
        listView.setAdapter(adapter);

        swipeRefreshLayout.setOnRefreshListener(this);

        /**
         * Showing Swipe Refresh animation on activity create
         * As animation won't start on onCreate, post runnable is used
         */
        swipeRefreshLayout.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        swipeRefreshLayout.setRefreshing(true);

                                        fetchMovies();
                                    }
                                }
        );

    }

    /**
     * This method is called when swipe refresh is pulled down
     */
    @Override
    public void onRefresh() {
        fetchMovies();
    }

    /**
     * Fetching movies json by making http call
     */
    private void fetchMovies() {

        // showing refresh animation before making http call
        swipeRefreshLayout.setRefreshing(true);

        // appending offset to url
        String url = URL_TOP_250 + offSet;

        // Volley's json array request object
        JsonArrayRequest req = new JsonArrayRequest(url,
                new Response.Listener<JSONArray>() {
                    @Override
                    public void onResponse(JSONArray response) {
                        Log.d(TAG, response.toString());

                        if (response.length() > 0) {

                            // looping through json and adding to movies list
                            for (int i = 0; i < response.length(); i++) {
                                try {
                                    JSONObject movieObj = response.getJSONObject(i);

                                    int rank = movieObj.getInt("rank");
                                    String title = movieObj.getString("title");

                                    Movie m = new Movie(rank, title);

                                    movieList.add(0, m);

                                    // updating offset value to highest value
                                    if (rank >= offSet)
                                        offSet = rank;

                                } catch (JSONException e) {
                                    Log.e(TAG, "JSON Parsing error: " + e.getMessage());
                                }
                            }

                            adapter.notifyDataSetChanged();
                        }

                        // stopping swipe refresh
                        swipeRefreshLayout.setRefreshing(false);

                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e(TAG, "Server Error: " + error.getMessage());

                Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_LONG).show();

                // stopping swipe refresh
                swipeRefreshLayout.setRefreshing(false);
            }
        });

        // Adding request to request queue
        MyApplication.getInstance().addToRequestQueue(req);
    }

}



Run the project and test it. You should able see the swipe refresh animation on app launch and list view updated each time you swipe down it.

android swipe down to refresh listview


4. PHP Class to Generate JSON

As lot of people are requesting me to provide the PHP code to generate the json, I am giving the code to generate the json used in this article. You can run this code using WAMP or XAMP softwares. Checkout Video 1 and Video 2 for installation and running PHP project in WAMP.

<?php

// sleep for 2 sec show that the androd swipe refresh will be visible for sometime
sleep(2);

// all top 250 movies
$movies = array("The Shawshank Redemption", "The Godfather", "The Godfather: Part II", "The Dark Knight", "Pulp Fiction", "Schindler's List", "12 Angry Men", "The Good, the Bad and the Ugly", "The Lord of the Rings: The Return of the King", "Fight Club", "The Lord of the Rings: The Fellowship of the Ring", "Star Wars: Episode V - The Empire Strikes Back", "Forrest Gump", "Inception", "One Flew Over the Cuckoo's Nest", "The Lord of the Rings: The Two Towers", "Goodfellas", "The Matrix", "Star Wars", "Seven Samurai", "City of God", "Se7en", "The Usual Suspects", "The Silence of the Lambs", "It's a Wonderful Life", "Interstellar", "Léon: The Professional", "Life Is Beautiful", "Once Upon a Time in the West", "Casablanca", "American History X", "Saving Private Ryan", "Spirited Away", "Raiders of the Lost Ark", "City Lights", "Psycho", "Rear Window", "The Intouchables", "Whiplash", "Modern Times", "The Green Mile", "Terminator 2: Judgment Day", "Memento", "The Pianist", "The Departed", "Apocalypse Now", "Gladiator", "Sunset Blvd.", "Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb", "Back to the Future", "Alien", "The Prestige", "The Lion King", "The Great Dictator", "The Lives of Others", "Cinema Paradiso", "Django Unchained", "The Shining", "Paths of Glory", "The Dark Knight Rises", "American Beauty", "WALL·E", "North by Northwest", "Aliens", "Citizen Kane", "Grave of the Fireflies", "Vertigo", "M", "Oldboy", "Das Boot", "Amélie", "Princess Mononoke", "Star Wars: Episode VI - Return of the Jedi", "Once Upon a Time in America", "Toy Story 3", "Reservoir Dogs", "A Clockwork Orange", "Braveheart", "Taxi Driver", "Double Indemnity", "Witness for the Prosecution", "Requiem for a Dream", "To Kill a Mockingbird", "Lawrence of Arabia", "Eternal Sunshine of the Spotless Mind", "Full Metal Jacket", "Bicycle Thieves", "The Sting", "Singin' in the Rain", "Amadeus", "Monty Python and the Holy Grail", "Snatch.", "2001: A Space Odyssey", "For a Few Dollars More", "Rashomon", "L.A. Confidential", "The Kid", "All About Eve", "The Apartment", "Inglourious Basterds", "Toy Story", "The Treasure of the Sierra Madre", "A Separation", "Indiana Jones and the Last Crusade", "Yojimbo", "The Third Man", "Some Like It Hot", "Metropolis", "Batman Begins", "Unforgiven", "Scarface", "Like Stars on Earth", "Raging Bull", "Up", "3 Idiots", "Downfall", "Chinatown", "The Great Escape", "Die Hard", "The Hunt", "On the Waterfront", "Heat", "Mr. Smith Goes to Washington", "Pan's Labyrinth", "Good Will Hunting", "The Bridge on the River Kwai", "My Neighbor Totoro", "Ikiru", "The Seventh Seal", "The Gold Rush", "Ran", "Wild Strawberries", "The General", "Blade Runner", "The Elephant Man", "Lock, Stock and Two Smoking Barrels", "The Secret in Their Eyes", "The Wolf of Wall Street", "Casino", "Gran Torino", "Howl's Moving Castle", "Warrior", "The Big Lebowski", "V for Vendetta", "Rebecca", "The Bandit", "Gone Girl", "The Deer Hunter", "Judgment at Nuremberg", "Cool Hand Luke", "How to Train Your Dragon", "It Happened One Night", "Fargo", "A Beautiful Mind", "Gone with the Wind", "Trainspotting", "Into the Wild", "Rush", "Dial M for Murder", "The Maltese Falcon", "The Sixth Sense", "Mary and Max", "Finding Nemo", "The Thing", "The Wages of Fear", "Hotel Rwanda", "No Country for Old Men", "Incendies", "Rang De Basanti", "Kill Bill: Vol. 1", "Platoon", "Life of Brian", "Butch Cassidy and the Sundance Kid", "Network", "A Wednesday", "Munna Bhai M.B.B.S.", "Touch of Evil", "There Will Be Blood", "12 Years a Slave", "Annie Hall", "The 400 Blows", "Stand by Me", "The Princess Bride", "Persona", "The Grand Budapest Hotel", "Amores Perros", "Ben-Hur", "Diabolique", "In the Name of the Father", "The Grapes of Wrath", "Million Dollar Baby", "Sin City", "Hachi: A Dog's Tale", "Nausicaä of the Valley of the Wind", "The Wizard of Oz", "The Best Years of Our Lives", "Gandhi", "The Avengers", "The Bourne Ultimatum", "Donnie Darko", "Shutter Island", "Stalker", "8½", "Guardians of the Galaxy", "Strangers on a Train", "Infernal Affairs", "Twelve Monkeys", "Fanny and Alexander", "Before Sunrise", "Boyhood", "Jaws", "The Imitation Game", "The Battle of Algiers", "The Terminator", "High Noon", "Groundhog Day", "Harry Potter and the Deathly Hallows: Part 2", "Memories of Murder", "The King's Speech", "Ip Man", "Monsters, Inc.", "Notorious", "Rocky", "Dog Day Afternoon", "Barry Lyndon", "La Haine", "The Truman Show", "Who's Afraid of Virginia Woolf?", "A Fistful of Dollars", "Dil Chahta Hai", "The Night of the Hunter", "Pirates of the Caribbean: The Curse of the Black Pearl", "Lagaan: Once Upon a Time in India", "Castle in the Sky", "Jurassic Park", "X-Men: Days of Future Past", "La Strada", "The Help", "Roman Holiday", "Wild Tales", "The Big Sleep", "Spring, Summer, Fall, Winter... and Spring", "Le Samouraï", "Prisoners", "Underground", "The Graduate", "Paris, Texas", "Solaris", "Three Colors: Red", "Papillon");

// reading offset from get parameter
$offset = isset($_GET['offset']) && $_GET['offset'] != '' ? $_GET['offset'] : 0;

// page limit
$limit = 20;


$movies_array = array();

// loop through page movies
for ($j = $offset; $j < $offset + $limit && $j < sizeof($movies); $j++) {
    $tmp = array();
    $tmp['rank'] = $j + 1;
    $tmp['title'] = $movies[$j];

    array_push($movies_array, $tmp);
}

// printing json response
echo json_encode($movies_array);
?>
]]>
0
Ravi Tamada <![CDATA[Android Getting Started with Material Design]]> http://www.androidhive.info/?p=37987 2015-07-29T05:39:02Z 2015-04-11T12:57:18Z You might have heard of android Material Design which was introduced in Android Lollipop version. In Material Design lot of new things were introduced like Material Theme, new widgets, custom shadows, vector drawables and custom animations. If you haven’t working on Material Design yet, this article will give you a good start.

In this tutorial we are going to learn the basic steps of Material Design development i.e writing the custom theme and implementing the navigation drawer using the RecyclerView.

Go through the below links which give you much knowledge over Material Design.

> Material Design Specifications

> Creating Apps with Material Design

android getting started with material design


1. Downloading Android Studio

Before going further, download the Android Studio and do the necessary setup as I am going to use Android Studio for all my tutorial from now on. If you are trying the Android Studio for the first time, go the overview doc to get complete overview of android studio.


2. Material Design Color Customization

Material Design provides set of properties to customize the Material Design Color theme. But we use five primary attributes to customize overall theme.

colorPrimaryDark – This is darkest primary color of the app mainly applies to notification bar background.

colorPrimary – This is the primary color of the app. This color will be applied as toolbar background.

textColorPrimary – This is the primary color of text. This applies to toolbar title.

windowBackground – This is the default background color of the app.

navigationBarColor – This color defines the background color of footer navigation bar.

android-material-design-color-schema

You can go through this material design color patterns and choose the one that suits your app.

3. Creating Material Design Theme

1. In Android Studio, go to File ⇒ New Project and fill all the details required to create a new project. When it prompts to select a default activity, select Blank Activity and proceed.

2. Open res ⇒ values ⇒ strings.xml and add below string values.

<resources>
    <string name="app_name">Material Design</string>
    <string name="action_settings">Settings</string>
    <string name="action_search">Search</string>
    <string name="drawer_open">Open</string>
    <string name="drawer_close">Close</string>

    <string name="nav_item_home">Home</string>
    <string name="nav_item_friends">Friends</string>
    <string name="nav_item_notifications">Messages</string>

    <!-- navigation drawer item labels  -->
    <string-array name="nav_drawer_labels">
        <item>@string/nav_item_home</item>
        <item>@string/nav_item_friends</item>
        <item>@string/nav_item_notifications</item>
    </string-array>

    <string name="title_messages">Messages</string>
    <string name="title_friends">Friends</string>
    <string name="title_home">Home</string>
</resources>



3. Open res ⇒ values ⇒ colors.xml and add the below color values. If you don’t find colors.xml, create a new resource file with the name.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#F50057</color>
    <color name="colorPrimaryDark">#C51162</color>
    <color name="textColorPrimary">#FFFFFF</color>
    <color name="windowBackground">#FFFFFF</color>
    <color name="navigationBarColor">#000000</color>
    <color name="colorAccent">#FF80AB</color>
</resources>



4. Open res ⇒ values ⇒ dimens.xml and add below dimensions.

<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="nav_drawer_width">260dp</dimen>
</resources>



5. Open styles.xml under res ⇒ values and add below styles. The styles defined in this styles.xml are common to all the android versions. Here I am naming my theme as MyMaterialTheme.

<resources>

    <style name="MyMaterialTheme" parent="MyMaterialTheme.Base">

    </style>

    <style name="MyMaterialTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="windowNoTitle">true</item>
        <item name="windowActionBar">false</item>
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    
</resources>



6. Now under res, create a folder named values-v21. Inside values-v21, create another styles.xml with the below styles. These styles are specific to Android Lollipop only.

<resources>

    <style name="MyMaterialTheme" parent="MyMaterialTheme.Base">
        <item name="android:windowContentTransitions">true</item>
        <item name="android:windowAllowEnterTransitionOverlap">true</item>
        <item name="android:windowAllowReturnTransitionOverlap">true</item>
        <item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
        <item name="android:windowSharedElementExitTransition">@android:transition/move</item>
    </style>

</resources>



7. Now we have the basic Material Design styles ready. In order to apply the theme, open AndroidManifest.xml and modify the android:theme attribute of <application> tag.

android:theme="@style/MyMaterialTheme"

So after applying the theme, your AndroidManifest.xml should look like below.

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/MyMaterialTheme" >
        <activity
            android:name=".activity.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

Now if you run the app, you can see the notification bar color changed to the color that we have mentioned in our styles.

android-material-design-notification-bar


3.1 Adding the Toolbar (Action Bar)

Adding the toolbar is very easy. All you have to do is, create a separate layout for the toolbar and include it in other layout wherever you want the toolbar to be displayed.

8. Create an xml file named toolbar.xml under res ⇒ layout and add android.support.v7.widget.Toolbar element. This create the toolbar with specific height and theming.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:local="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    local:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    local:popupTheme="@style/ThemeOverlay.AppCompat.Light" />



9. Open the layout file of your main activity (activity_main.xml) and add the toolbar using <include/> tag.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:orientation="vertical">

        <include
            android:id="@+id/toolbar"
            layout="@layout/toolbar" />
    </LinearLayout>


</RelativeLayout>

Run the app and see if the toolbar displayed on the screen or not.

android-material-design-toolbar

Now let’s try to add a toolbar title and enable the action items.

10. Download this search icon and import it into Android Studio as a Image Asset.

11. To import the Image Asset in Android Studio, right click on res ⇒ New ⇒ Image Asset. It will show you a popup window to import the resource. Browse the search icon that you have downloaded in the above step, select Action Bar and Tab Icons for Asset Type and give the resource name as ic_search_action and proceed.

android-studio-importing-image-asset

12. Once the icon is imported, open menu_main.xml located under res ⇒ menu and add the search menu item as mentioned below.



13. Now open your MainActivity.java and do the below changes.

> Extend the activity from AppCompatActivity

> Enable the toolbar by calling setSupportActionBar() by passing the toolbar object.

> Override onCreateOptionsMenu() and onOptionsItemSelected() methods to enable toolbar action items.

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity {

    private Toolbar mToolbar;

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

        mToolbar = (Toolbar) findViewById(R.id.toolbar);

        setSupportActionBar(mToolbar);
        getSupportActionBar().setDisplayShowHomeEnabled(true);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}



After doing the above changes, if you run the app, you should see the search icon and action overflow icon.

android-material-design-toolbar-action-items

3.2 Adding Navigation Drawer

Adding navigation drawer is same as that we do before lollipop, but instead if using ListView for menu items, we use RecyclerView in material design. So let’s see how to implement the navigation drawer with RecyclerView.

14. In your project’s java folder, create three packages named activity, adapter, model and move your MainActivity.java to activity package. This will keep your project organized.

15. Open build.gradle located under your app module and add below dependencies. After adding the dependencies, goto Build ⇒ Rebuild Project to download required libraries.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile 'com.android.support:recyclerview-v7:22.2.+'
}



16. Under model package, create a class named NavDrawerItem.java with the below code. This model class is POJO class that defines each row in navigation drawer menu.



17. Under res ⇒ layout, create an xml layout named nav_drawer_row.xml and add the below code. The layout renders each row in navigation drawer menu. If you want to customize the navigation drawer menu item, you have to do the changes in this file. For now it has only one TextView.



18. Download this profile icon and paste it in your drawable folder. This step is optional, but this icon used in the navigation drawer header part.

19. Create another xml layout named fragment_navigation_drawer.xml and add the below code. This layout renders the complete navigation drawer view. This layout contains a header section to display the user information and a RecyclerView to display the list view.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white">


    <RelativeLayout
        android:id="@+id/nav_header_container"
        android:layout_width="match_parent"
        android:layout_height="140dp"
        android:layout_alignParentTop="true"
        android:background="@color/colorPrimary">

        <ImageView
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:src="@drawable/ic_profile"
            android:scaleType="fitCenter"
            android:layout_centerInParent="true" />

    </RelativeLayout>


    <android.support.v7.widget.RecyclerView
        android:id="@+id/drawerList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/nav_header_container"
        android:layout_marginTop="15dp" />


</RelativeLayout>



20. As the RecyclerView is customized, we need an adapter class to render the custom xml layout. So under adapter package, create a class named NavigationDrawerAdapter.java and paste the below code. This adapter class inflates nav_drawer_row.xml and renders the RecycleView drawer menu.

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.Collections;
import java.util.List;

/**
 * Created by Ravi Tamada on 12-03-2015.
 */
public class NavigationDrawerAdapter extends RecyclerView.Adapter<NavigationDrawerAdapter.MyViewHolder> {
    List<NavDrawerItem> data = Collections.emptyList();
    private LayoutInflater inflater;
    private Context context;

    public NavigationDrawerAdapter(Context context, List<NavDrawerItem> data) {
        this.context = context;
        inflater = LayoutInflater.from(context);
        this.data = data;
    }

    public void delete(int position) {
        data.remove(position);
        notifyItemRemoved(position);
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.nav_drawer_row, parent, false);
        MyViewHolder holder = new MyViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        NavDrawerItem current = data.get(position);
        holder.title.setText(current.getTitle());
    }

    @Override
    public int getItemCount() {
        return data.size();
    }

    class MyViewHolder extends RecyclerView.ViewHolder {
        TextView title;

        public MyViewHolder(View itemView) {
            super(itemView);
            title = (TextView) itemView.findViewById(R.id.title);
        }
    }
}



21. Under activity package, create a fragment named FragmentDrawer.java. In Android Studio, to create a new fragment, Right click on adapter ⇒ New ⇒ Fragment ⇒ Fragment (Blank) and give your fragment class name.


/**
 * Created by Ravi on 29/07/15.
 */

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

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

import info.androidhive.materialdesign.R;
import info.androidhive.materialdesign.adapter.NavigationDrawerAdapter;
import info.androidhive.materialdesign.model.NavDrawerItem;

public class FragmentDrawer extends Fragment {

    private static String TAG = FragmentDrawer.class.getSimpleName();

    private RecyclerView recyclerView;
    private ActionBarDrawerToggle mDrawerToggle;
    private DrawerLayout mDrawerLayout;
    private NavigationDrawerAdapter adapter;
    private View containerView;
    private static String[] titles = null;
    private FragmentDrawerListener drawerListener;

    public FragmentDrawer() {

    }

    public void setDrawerListener(FragmentDrawerListener listener) {
        this.drawerListener = listener;
    }

    public static List<NavDrawerItem> getData() {
        List<NavDrawerItem> data = new ArrayList<>();


        // preparing navigation drawer items
        for (int i = 0; i < titles.length; i++) {
            NavDrawerItem navItem = new NavDrawerItem();
            navItem.setTitle(titles[i]);
            data.add(navItem);
        }
        return data;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // drawer labels
        titles = getActivity().getResources().getStringArray(R.array.nav_drawer_labels);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflating view layout
        View layout = inflater.inflate(R.layout.fragment_navigation_drawer, container, false);
        recyclerView = (RecyclerView) layout.findViewById(R.id.drawerList);

        adapter = new NavigationDrawerAdapter(getActivity(), getData());
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        recyclerView.addOnItemTouchListener(new RecyclerTouchListener(getActivity(), recyclerView, new ClickListener() {
            @Override
            public void onClick(View view, int position) {
                drawerListener.onDrawerItemSelected(view, position);
                mDrawerLayout.closeDrawer(containerView);
            }

            @Override
            public void onLongClick(View view, int position) {

            }
        }));

        return layout;
    }


    public void setUp(int fragmentId, DrawerLayout drawerLayout, final Toolbar toolbar) {
        containerView = getActivity().findViewById(fragmentId);
        mDrawerLayout = drawerLayout;
        mDrawerToggle = new ActionBarDrawerToggle(getActivity(), drawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close) {
            @Override
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActivity().invalidateOptionsMenu();
            }

            @Override
            public void onDrawerClosed(View drawerView) {
                super.onDrawerClosed(drawerView);
                getActivity().invalidateOptionsMenu();
            }

            @Override
            public void onDrawerSlide(View drawerView, float slideOffset) {
                super.onDrawerSlide(drawerView, slideOffset);
                toolbar.setAlpha(1 - slideOffset / 2);
            }
        };

        mDrawerLayout.setDrawerListener(mDrawerToggle);
        mDrawerLayout.post(new Runnable() {
            @Override
            public void run() {
                mDrawerToggle.syncState();
            }
        });

    }

    public static interface ClickListener {
        public void onClick(View view, int position);

        public void onLongClick(View view, int position);
    }

    static class RecyclerTouchListener implements RecyclerView.OnItemTouchListener {

        private GestureDetector gestureDetector;
        private ClickListener clickListener;

        public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) {
            this.clickListener = clickListener;
            gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
                @Override
                public boolean onSingleTapUp(MotionEvent e) {
                    return true;
                }

                @Override
                public void onLongPress(MotionEvent e) {
                    View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
                    if (child != null && clickListener != null) {
                        clickListener.onLongClick(child, recyclerView.getChildPosition(child));
                    }
                }
            });
        }

        @Override
        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {

            View child = rv.findChildViewUnder(e.getX(), e.getY());
            if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
                clickListener.onClick(child, rv.getChildPosition(child));
            }
            return false;
        }

        @Override
        public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        }

        @Override
        public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        }


    }

    public interface FragmentDrawerListener {
        public void onDrawerItemSelected(View view, int position);
    }
}



22. Finally open main activity layout (activity_main.xml) and modify the layout as below. In this layout we are adding android.support.v4.widget.DrawerLayout to display the navigation drawer menu.

Also you have to give the correct path of your FragmentDrawer in <fragment> element.

<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:id="@+id/container_toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <include
                android:id="@+id/toolbar"
                layout="@layout/toolbar" />
        </LinearLayout>

        <FrameLayout
            android:id="@+id/container_body"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />


    </LinearLayout>


    <fragment
        android:id="@+id/fragment_navigation_drawer"
        android:name="info.androidhive.materialdesign.activity.FragmentDrawer"
        android:layout_width="@dimen/nav_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:layout="@layout/fragment_navigation_drawer"
        tools:layout="@layout/fragment_navigation_drawer" />

</android.support.v4.widget.DrawerLayout>



Now we have all the layout files and java classes ready in place. Let’s do the necessary changes in MainActivity to make the navigation drawer functioning.

23. Open your MainActivity.java and do the below changes.

> Implement the activity from FragmentDrawer.FragmentDrawerListener and add the onDrawerItemSelected() override method.

> Create an instance of FragmentDrawer and set the drawer selected listeners.


import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

public class MainActivity extends AppCompatActivity implements FragmentDrawer.FragmentDrawerListener {

    private Toolbar mToolbar;
    private FragmentDrawer drawerFragment;

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

        mToolbar = (Toolbar) findViewById(R.id.toolbar);

        setSupportActionBar(mToolbar);
        getSupportActionBar().setDisplayShowHomeEnabled(true);

        drawerFragment = (FragmentDrawer)
                getSupportFragmentManager().findFragmentById(R.id.fragment_navigation_drawer);
        drawerFragment.setUp(R.id.fragment_navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout), mToolbar);
        drawerFragment.setDrawerListener(this);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onDrawerItemSelected(View view, int position) {

    }
}



Now if you run the app, you can see the navigation drawer with a header and few list items in it.

androd-material-design-navigation-drawer

3.3 Implementing Navigation Drawer Item Selection

Although navigation drawer is functioning, you can see the selection of drawer list items not working. This is because we are yet to implement the click listener on RecyclerView items.

As we have three menu items in navigation drawer (Home, Friends & Messages), we need to create three separate fragment classes for each menu item.

24. Under res layout, create an xml layout named fragment_home.xml and add below code.

<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:orientation="vertical"
    tools:context="info.androidhive.materialdesign.activity.HomeFragment">


    <TextView
        android:id="@+id/label"
        android:layout_alignParentTop="true"
        android:layout_marginTop="100dp"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:textSize="45dp"
        android:text="HOME"
        android:textStyle="bold"/>

    <TextView
        android:layout_below="@id/label"
        android:layout_centerInParent="true"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="12dp"
        android:layout_marginTop="10dp"
        android:gravity="center_horizontal"
        android:text="Edit fragment_home.xml to change the appearance" />

</RelativeLayout>


25. Under activity package, create a fragment class named HomeFragment.java and add below code.

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;


public class HomeFragment extends Fragment {

    public HomeFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_home, container, false);


        // Inflate the layout for this fragment
        return rootView;
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }
}



26. Create two more fragment classes named FriendsFragment.java, MessagesFragment.java and respected layout files named fragment_friends.xml and fragment_messages.xml and add the code from above two steps.

27. Now open MainActivity.java and do the below changes. In the below code

> displayView() method displays the fragment view respected the navigation menu item selection. This method should be called in onDrawerItemSelected() to render the respected view when a navigation menu item is selected.

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends ActionBarActivity implements FragmentDrawer.FragmentDrawerListener {

    private static String TAG = MainActivity.class.getSimpleName();

    private Toolbar mToolbar;
    private FragmentDrawer drawerFragment;

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

        mToolbar = (Toolbar) findViewById(R.id.toolbar);

        setSupportActionBar(mToolbar);
        getSupportActionBar().setDisplayShowHomeEnabled(true);

        drawerFragment = (FragmentDrawer)
                getSupportFragmentManager().findFragmentById(R.id.fragment_navigation_drawer);
        drawerFragment.setUp(R.id.fragment_navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout), mToolbar);
        drawerFragment.setDrawerListener(this);

        // display the first navigation drawer view on app launch
        displayView(0);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        if(id == R.id.action_search){
            Toast.makeText(getApplicationContext(), "Search action is selected!", Toast.LENGTH_SHORT).show();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onDrawerItemSelected(View view, int position) {
            displayView(position);
    }

    private void displayView(int position) {
        Fragment fragment = null;
        String title = getString(R.string.app_name);
        switch (position) {
            case 0:
                fragment = new HomeFragment();
                title = getString(R.string.title_home);
                break;
            case 1:
                fragment = new FriendsFragment();
                title = getString(R.string.title_friends);
                break;
            case 2:
                fragment = new MessagesFragment();
                title = getString(R.string.title_messages);
                break;
            default:
                break;
        }

        if (fragment != null) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.replace(R.id.container_body, fragment);
            fragmentTransaction.commit();

            // set the toolbar title
            getSupportActionBar().setTitle(title);
        }
    }
}



Now if you run the app, you can see the selection of navigation drawer menu is working and respected view displayed below the toolbar.

android-material-design-navigation-drawer-1
android-material-design-navigation-drawer-2
android-material-design-navigation-drawer-3
Change Log

Updated On29th July 2015 (Latest support library)
]]>
0
Ravi Tamada <![CDATA[MoboMarket Shrinks Android Smartphone Management into a Few Clicks]]> http://www.androidhive.info/?p=36638 2015-03-30T09:21:58Z 2015-03-27T07:59:36Z Despite the increasing screen size of Android Smartphones, PC screen is always the best when you want to manage a number of various aspects of the Smartphone. It is the same reason why there was a demand for a universal & easy-to-use solution for Smartphone management, since most Smartphone manufacturers do not provide dedicated tools to manage those devices using a computer. In such a situation, we’re quite sure that you will definitely have enough reasons to love MoboMarket, which is an Android app store at the same time Smartphone management solution for PCs. Here, we shall have an in-detail review of MoboMarket, discussing how it becomes somewhat an essential tool for almost every Smartphone user out there. So, shall we start our review of MoboMarket with an introduction that will help you have a clear idea about day-to-day uses of MoboMarket?

MoboMarket Shrinks Android Smartphone Management


An Introduction to MoboMarket

MoboMarket come up with an Android version, an App Market on your Android. And a Windows PC version, a comprehensive Android manager tool on PC. On March. 19, MoboMarket (for PC) has released its V5.X beta Version with fully new UI design. Today, we will take a deep look at MoboMarket (for PC) and see what it can do for your beloved Android.
As we said, MoboMarket can, at the same time, act as a Smartphone management solution and an unofficial yet safe way to download your favourite Android applications, either through PC or Smartphone itself. Even when we accept the fact that most of you might not need a parallel android app store, the Smartphone management aspect of tool is somewhat integral for all Smartphone users out there. Because of this, even though we will be talking about the app store, our primary focus will be on Smartphone management side of MoboMarket. We will go through various parts of the tool such as connection, user interface and all.

Connecting your Device

In order to connect your Android Smartphone with your PC in which you have installed MoboMarket, you can select a method from two — USB or Bluetooth. Considering availability of features and simplicity, we prefer using the USB-powered connection, unless you are desperate enough to leave wires. On the other hand, if you can connect both of your devices into same Wi-Fi network, you can make use of the Wi-Fi connection method that makes sense as far as you want to leave wires. While using the USB method, you will have to enable USB Debugging, which will install that MoboMarket application in your device automatically. Apart from the time taken for installation of proper drivers, it will take only a few seconds to connect your Android Smartphone with MoboMarket for PC. Once you have finished the connection process, you can see a small yet effective dashboard of MoboMarket.


The Dashboard that Makes Sense

Compared to the previous version of MoboMarket — which was named MoboRobo —, the new version boasts user-friendly and simple dashboard that gives easier access to different sections of the tool. As you can see in screenshot given below, it has shortcuts such as Device, Media and Resources etc. Also, there are options to boost you device by clearing junk files and other unnecessary stuff out there. Now, we shall check out features of each section of the tool such as Media and Resources.

MoboMarket Small Dash


• Device

This section of MoboMarket is meant to give you an overview of the device, covering basic details such as version of Android it is running, whether it has root access, IMEI number, storage details etc. Apart from all these, this section is having a simulation of your device display using which you can take screenshots in a click. If you click on its sidebar menu, you can access other sub sections like contacts, messages and list of installed applications of your device.

Device Info


• Media

Media section of MoboMarket will be useful in managing media files your device has in it. For instance, you’ll have a clear list of music files in the device along with videos and all. Apart from an integrated player, you’ve one-click ways to add media to the device, which makes sense.

Media Management


• Resource

Resources section of MoboMarket makes enough sense when you want to install applications in your device without having to use Play Store, suppose when you do not have an internet connection or Wi-Fi connection in device. Using this section, you can download and install apps in a comparatively simpler manner.

Resources


• Toolbox

If the above sections were just giving you insights, Toolbox section of MoboMarket will be powerful enough in letting you manage your Smartphone. From this section, you can backup different kinds of information such as contacts and apps from your device, restore backups you have created earlier, manage files through a custom-made browser and clean device and save up space using the Space cleaner. Among these, we loved the Backup feature very much, as we are living with data that is worthier than the device itself.

Toolbox



Our Verdict
In light our experience, we can say that MoboMarket is one of the best Android PC managers you will stumble upon! Particularly, we loved its UI and the way of arranging stuff, and plus, those noteworthy features.


Download MoboMarket

]]>
0
Ravi Tamada <![CDATA[Android Hosting PHP, MySQL RESTful services to DigitalOcean]]> http://www.androidhive.info/?p=33894 2015-03-05T19:18:37Z 2015-03-05T19:18:37Z Few months back I wrote an article about developing RESTful services using PHP, Slim and MySQL. But the services are not accessible to public as they are limited to localhost. In this article we are going to learn how to host the services on a realtime server, so that, they can be accessible from anywhere.

I tried hosting the services on free hosting spaces (like 000webhost.com), but because of limitations (like not supporting PUT, DELETE methods and mysqlnd) I couldn’t able to run the services. So I decided to move to a VPS hosting like Digitalocean to host the services. Digitalocean provides very high hardware configuration in cheaper prices (which is almost equal to shared hosting).

digitalocean deploying android php mysql restful services


Prerequisite

As this tutorial is continuation of below two articles, make sure you learn them well to know how to develop RESTful services for your android app.

How to create REST API for Android app using PHP, Slim and MySQL – Day 1/2

How to create REST API for Android app using PHP, Slim and MySQL – Day 2/2

Download the code from this link as we are going to see hosting this project into digitalocean.


1. Buying & Setting Up DigitalOcean (Get $10 free credit)

DigitalOcean offers various pricing plans ranging from $5/m to $640/m. For an entry level app, $5/m plan (which comes with 512MB Memory, 1 Core Processor, 20GB SSD & 1TB Transfer) is powerful enough to get started. If you think this configuration is not enough for your app, you can always upgrade to other plans.

Register your self with DigitalOcean by creating an account. Use this link to get $10 free credit in your account. In other words you don’t have to pay for the first two months (assuming you are choosing $5 plan).

Create a new account here. Give your email and password. You’ll receive the confirmation mail to your email account.

Activate the account by verifying the email sent to you.

Once the account is activated, you will be prompted to give your credit card details. Without giving the credit card details you can’t create the droplet. Don’t worry about giving the credit card information, there won’t be any money deducted from your card right away.

Check the below video to know the registration process.



2. Getting the Droplet Ready

Once you gave the credit card details, you will be allowed to create a droplet. Droplet is nothing but an instance of a server. Follow below steps to configure your droplet.

2.1 Click on Create Droplet on left sidebar or click this link to go to the page directly.

2.2 Give your droplet a name

2.3 Select the size. I selected $5/m plan.

2.4 Select the region. I selected New York.

2.5 Select the distribution image. I selected Ubuntu 14.04×64

2.6 Under applications tab, select LAMP on 14.04. This will install Apache, MySQL and PHP on creation of the droplet.

2.7 Click on Create Droplet to complete.

It will take few minutes to create the droplet. Once the droplet is created, your new server instance details (ip, username and password) will be mailed to your email id.

If you visit the ip address in browser, you can see apache welcome page. Now follow below steps to complete the droplet setup.


2.8 Logging into droplet via SSH
You can login into your server via ssh. Mac users can directly connect using terminal tool. But windows users needs download ssh client like Putty to login. Download Putty and execute below commands to login into your server.

In putty, give your server ip address, port number as 22 and click open. You will be asked to give your username and password. Use the credentials those sent to your email.

Mac users, run the below command directly in terminal.

ssh root@YOUR_IP_ADDRESS



2.9 Resetting Root Password
When you are logging in for the first time, you will be asked to change your password. Give your old password and new password to change.


3.0 Resetting MySQL Password
As MySQL is installed by selecting the LAMP app while configuring the droplet, we are not sure about the mysql password. You can see the current MySQL password in the console when you are logging in. This password is randomly generated and kept. In order to change the MySQL password, follow below steps.

sudo service mysql stop
sudo /usr/sbin/mysqld --skip-grant-tables --skip-networking &
mysql -u root
FLUSH PRIVILEGES;

Replace ‘newpassword’ with your new mysql password.

SET PASSWORD FOR root@'localhost' = PASSWORD('newpassword');
FLUSH PRIVILEGES;
exit

You can find complete article about resetting MySQL password here.


3.1 Enabling mod rewrite
Apache rewrite module helps in writing the urls to another url. This is useful for us to shorting the Slim urls. So enable it by running the below command.

sudo a2enmod rewrite



3.2 Enabling mysqlnd
MySQL native driver is needed to run the mysqli functions. So install mysqlnd by executing the below commands.

sudo apt-get update
sudo apt-get install php5-mysqlnd

Now restart the apache to take the changes effected.

sudo service apache2 restart

Checkout the below video that will take you through completing the droplet setup.



3. Installing phpmyadmin

You can create database using the mysql command line tool or using the phpmyadmin. I prefer using phpmyadmin as it is lot more easier than the command line tool. Follow the below steps to install phpmyadmin.

sudo apt-get install phpmyadmin apache2-utils

While installing you need do below steps.

Select Apache2 by pressing the SPACE key. When selected, you should see a start (*) indicating Apache2 is selected.

Choose YES whenever it prompts to configure the database for phpmyadmin

Enter MySQL password when prompted

Enter phpmyadmin password that you want to login with

Now you can test your phpmyadmin by visiting http://_YOUR_IP_ADRESS_/phpmyadmin (Replace _YOUR_IP_ADRESS_ with your actual server ip)

The below video will take you through installation of phpmyadmin.



4. Creating the Database

Goto your phpmyadmin and execute the below SQL to create the required database and tables. Once executed, you can see the newly created database on the left panel.

CREATE DATABASE task_manager;
 
USE task_manager;
 
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(250) DEFAULT NULL,
  `email` varchar(255) NOT NULL,
  `password_hash` text NOT NULL,
  `api_key` varchar(32) NOT NULL,
  `status` int(1) NOT NULL DEFAULT '1',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`)
);
 
CREATE TABLE IF NOT EXISTS `tasks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `task` text NOT NULL,
  `status` int(1) NOT NULL DEFAULT '0',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
);
 
CREATE TABLE IF NOT EXISTS `user_tasks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `task_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`),
  KEY `task_id` (`task_id`)
);
 
ALTER TABLE  `user_tasks` ADD FOREIGN KEY (  `user_id` ) REFERENCES  `task_manager`.`users` (
`id`
) ON DELETE CASCADE ON UPDATE CASCADE ;
 
ALTER TABLE  `user_tasks` ADD FOREIGN KEY (  `task_id` ) REFERENCES  `task_manager`.`tasks` (
`id`
) ON DELETE CASCADE ON UPDATE CASCADE ;

The below video will show you creating database and tables.



5. Uploading your PHP Project

Now we’ll upload our project to the server. For this you need a FTP software like FileZilla. Download FileZilla and login to your server using the credentials as Host (your ip address), Username, Password and Port (give 22 as port number).

Once connected, goto the directory /var/www/html on the right panel and delete index.html and info.php. This is the root directory for all our php projects.

In your php task_manager project, goto include and change the credentials of MySQL in Config.php with your database username, password and database name.

Now upload the task_manager folder to html directory.

Goto /etc/apache2, open apache2.conf and change AllowOverride to All under <Directory /var/www/>. This step is very important to eliminate index.php from our api urls.

<Directory /var/www/>
	Options Indexes FollowSymLinks
	AllowOverride All
	Require all granted
</Directory>

The below video will show you how to upload your project using FileZilla.



6. Testing the API

Now we have everything in place, let’s test the API using Chrome Advanced REST client. Install the extension and test your url endpoints as shown in the below video.

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


7. Mapping the Server with a Real Domain URL (Godaddy)

You can buy the domain from whichever service you like. In this tutorial I used Godaddy.com to buy my domain url.

Goto godaddy.com and search for the domain you want to buy. If it is available, complete the checkout process.

Launch the Zone File Editor for your domain.

Click Add New Record. A popup will be shown to edit the record.

From the Record type list, select A(Host)

Enter host value as @ and points to value is ip address of the droplet and click on finish.

It will take sometime to get the changes reflected depending upon your service provider. After sometime, if you visit the domain, it should points to our droplet. Once mapped, you can change the ip address to your domain url in the REST API urls.

Check the below video to get clear understanding of mapping the domain with droplet.


]]> 0 Ravi Tamada <![CDATA[Android Integrating PayPal using PHP, MySQL – Part 2]]> http://www.androidhive.info/?p=31622 2015-02-24T09:09:05Z 2015-02-24T09:09:05Z In the previous part Android Integrating PayPal using PHP, MySQL – Part 1, we have covered building the PayPal server side part i.e creating mysql database and writing the PHP services those interacts with android app and PayPal REST API.

In this part we are going to cover the remaining things like building the android app and integrating the PayPal payment gateway. Finally we conclude this by doing few tests in local environment.

android paypal integration php mysql


9. Downloading PayPal Android SDK

Just like PayPal PHP REST API SDK, PayPal provides SDK for mobile platforms too. Download the PayPal Android SDK and extract it. It comes with a Sample App, docs and libs. We would be interested in using the libs folder. If you want to try Future Payments and Profile Sharing, you can find example code in the sample app.


10. Creating the Android Project

Now let’s start building the android app.

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

I gave my project name as PayPalClient and package name as info.androidhive.paypalclient

2. Now quickly create two packages named app and helper.

3. Paste all the contents of PayPal Android SDK’s libs folder into our project’s libs folder.

Below is the final project structure that we’re going to create. You should have all the PayPal lib files and the packages placed as shown below.

android paypal project structure

4. Open colors.xml under res ⇒ value and add below color resources.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="btn_bg_checkout">#428bca</color>
    <color name="list_divider">#dedede</color>
    <color name="white">#ffffff</color>
    <color name="lbl_product_name">#333333</color>
    <color name="lbl_product_description">#444444</color>
    <color name="bg_msg_you">#5eb964</color>
    <color name="bg_msg_from">#e5e7eb</color>
    <color name="msg_border_color">#a1a1a1</color>
    <color name="bg_btn_join">#1e6258</color>
    <color name="bg_msg_input">#e8e8e8</color>
    <color name="text_msg_input">#626262</color>
    <color name="lblFromName">#777777</color>
</resources>



5. Open strings.xml under res ⇒ values and add below string values.

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

    <string name="app_name">PalPal Client</string>
    <string name="checkout">Checkout</string>
    <string name="add_to_cart">Add to Cart</string>

</resources>



6. I am using volley to make network calls. Download the volley.jar and paste it in libs folder. (If you are new to volley, this tutorial will give you a good overview about volley library).

7. Create class named LruBitmapCache.java under helper package. The purpose of this class to cache the downloaded images.

package info.androidhive.palpalclient.helper;

import com.android.volley.toolbox.ImageLoader.ImageCache;

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class LruBitmapCache extends LruCache<String, Bitmap> implements
		ImageCache {
	public static int getDefaultLruCacheSize() {
		final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
		final int cacheSize = maxMemory / 8;

		return cacheSize;
	}

	public LruBitmapCache() {
		this(getDefaultLruCacheSize());
	}

	public LruBitmapCache(int sizeInKiloBytes) {
		super(sizeInKiloBytes);
	}

	@Override
	protected int sizeOf(String key, Bitmap value) {
		return value.getRowBytes() * value.getHeight() / 1024;
	}

	@Override
	public Bitmap getBitmap(String url) {
		return get(url);
	}

	@Override
	public void putBitmap(String url, Bitmap bitmap) {
		put(url, bitmap);
	}
}



8. Crete a class named AppController.java under app package. This is a singleton class that extends from Application which will be executed on app launch. All the initialization of volley objects will be done here.

package info.androidhive.palpalclient.app;

import info.androidhive.palpalclient.helper.LruBitmapCache;
import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

public class AppController extends Application {

	public static final String TAG = AppController.class.getSimpleName();

	private RequestQueue mRequestQueue;
	private ImageLoader mImageLoader;

	private static AppController mInstance;

	@Override
	public void onCreate() {
		super.onCreate();
		mInstance = this;
	}

	public static synchronized AppController getInstance() {
		return mInstance;
	}

	public RequestQueue getRequestQueue() {
		if (mRequestQueue == null) {
			mRequestQueue = Volley.newRequestQueue(getApplicationContext());
		}

		return mRequestQueue;
	}

	public ImageLoader getImageLoader() {
		getRequestQueue();
		if (mImageLoader == null) {
			mImageLoader = new ImageLoader(this.mRequestQueue,
					new LruBitmapCache());
		}
		return this.mImageLoader;
	}

	public <T> void addToRequestQueue(Request<T> req, String tag) {
		// set the default tag if tag is empty
		req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
		getRequestQueue().add(req);
	}

	public <T> void addToRequestQueue(Request<T> req) {
		req.setTag(TAG);
		getRequestQueue().add(req);
	}

	public void cancelPendingRequests(Object tag) {
		if (mRequestQueue != null) {
			mRequestQueue.cancelAll(tag);
		}
	}
}



9. Now edit the AndroidManifest.xml and do below changes.

> Add the AppController class to <application> tag using android:name property.

> Add CAMERA, VIBRATE, ACCESS_NETWORK_STATE and INTERNET permissions.

> Add the camera feature. This should be added when user wants to pay using card.io feature.

> Add the PayPalService

> Add the PayPal SDK activities (PaymentActivity, LoginActivity, PaymentMethodActivity, PaymentConfirmActivity and CardIOActivity). These are necessary activities to make paypal payment.

Finally the AndroidManifest.xml should look like below.

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

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

    <!-- for card.io card scanning -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.VIBRATE" />

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

    <!-- for most things, including card.io & paypal -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name="info.androidhive.palpalclient.app.AppController"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name="info.androidhive.palpalclient.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <service
            android:name="com.paypal.android.sdk.payments.PayPalService"
            android:exported="false" /> 

        <activity android:name="com.paypal.android.sdk.payments.PaymentActivity" />
        <activity android:name="com.paypal.android.sdk.payments.LoginActivity" />
        <activity android:name="com.paypal.android.sdk.payments.PaymentMethodActivity" />
        <activity android:name="com.paypal.android.sdk.payments.PaymentConfirmActivity" />
        <activity
            android:name="io.card.payment.CardIOActivity"
            android:configChanges="keyboardHidden|orientation" />
        <activity android:name="io.card.payment.DataEntryActivity" />
    </application>

</manifest>



10. Crete a class named Config.java under app package. This class contains all the configuration variables like PayPal client id & secret, paypal environment and url endpoints to our server which we built in the first part of this series.

package info.androidhive.palpalclient.app;

import com.paypal.android.sdk.payments.PayPalConfiguration;
import com.paypal.android.sdk.payments.PayPalPayment;

public class Config {

	// PayPal app configuration
	public static final String PAYPAL_CLIENT_ID = "AbLgy0hRsq0PmoGK-ws2-jlBIeBVKUUU0xRjbfW1-GAckylz_TDNsh1cMrIiSksc2wpqYC2PisTrKhko";
	public static final String PAYPAL_CLIENT_SECRET = "";

	public static final String PAYPAL_ENVIRONMENT = PayPalConfiguration.ENVIRONMENT_SANDBOX;
	public static final String PAYMENT_INTENT = PayPalPayment.PAYMENT_INTENT_SALE;
	public static final String DEFAULT_CURRENCY = "USD";

	// PayPal server urls
	public static final String URL_PRODUCTS = "http://192.168.0.103/PayPalServer/v1/products";
	public static final String URL_VERIFY_PAYMENT = "http://192.168.0.103/PayPalServer/v1/verifyPayment";

}



11. Create a model class named Product.java under helper package. This class will be used while parsing the products json.

package info.androidhive.palpalclient.helper;

import java.math.BigDecimal;

public class Product {
	private String id, name, description, image, sku;
	private BigDecimal price;

	public Product() {

	}

	public Product(String id, String name, String description, String image,
			BigDecimal price, String sku) {
		this.id = id;
		this.name = name;
		this.description = description;
		this.image = image;
		this.price = price;
		this.sku = sku;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public String getImage() {
		return image;
	}

	public void setImage(String image) {
		this.image = image;
	}

	public BigDecimal getPrice() {
		return price;
	}

	public void setPrice(BigDecimal price) {
		this.price = price;
	}

	public String getSku() {
		return sku;
	}

	public void setSku(String sku) {
		this.sku = sku;
	}

}



12. Under res ⇒ layout, create a file named list_item_product.xml. This layout file renders a single list item in products list view. This layout contains the product image on the left and other details like product name, description, price on the right.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:paddingBottom="10dp" >

    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/productImage"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_margin="8dp"
        android:scaleType="fitCenter" >
    </com.android.volley.toolbox.NetworkImageView>

    <TextView
        android:id="@+id/productName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/productImage"
        android:padding="5dp"
        android:textColor="@color/lbl_product_name"
        android:textSize="16dp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/productDescription"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/productName"
        android:layout_toRightOf="@id/productImage"
        android:padding="5dp"
        android:textColor="@color/lbl_product_description" />

    <TextView
        android:id="@+id/productPrice"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/productDescription"
        android:layout_toRightOf="@id/productImage"
        android:padding="5dp"
        android:textColor="@color/lbl_product_description" />

    <Button
        android:id="@+id/btnAddToCart"
        android:layout_width="wrap_content"
        android:layout_height="30dp"
        android:layout_below="@id/productPrice"
        android:layout_margin="5dp"
        android:layout_toRightOf="@id/productImage"
        android:background="#64d048"
        android:paddingLeft="5dp"
        android:paddingRight="5dp"
        android:text="@string/add_to_cart"
        android:textColor="@color/white" />

</RelativeLayout>



13. As the product list view is customized, we need to write the custom list adapter class. Create a class named ProductListAdapter.java under helper package. This adapter class renders list_item_product.xml by filling appropriate product data in a single list row.

package info.androidhive.palpalclient.helper;

import info.androidhive.palpalclient.R;
import info.androidhive.palpalclient.app.AppController;

import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.TextView;

import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;

public class ProductListAdapter extends BaseAdapter {
	private Activity activity;
	private LayoutInflater inflater;
	private List<Product> products;
	private ProductListAdapterListener listener;
	ImageLoader imageLoader = AppController.getInstance().getImageLoader();

	public ProductListAdapter(Activity activity, List<Product> feedItems,
			ProductListAdapterListener listener) {
		this.activity = activity;
		this.products = feedItems;
		this.listener = listener;
	}

	@Override
	public int getCount() {
		return products.size();
	}

	@Override
	public Object getItem(int location) {
		return products.get(location);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		if (inflater == null)
			inflater = (LayoutInflater) activity
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		if (convertView == null)
			convertView = inflater.inflate(R.layout.list_item_product, null);

		if (imageLoader == null)
			imageLoader = AppController.getInstance().getImageLoader();

		TextView name = (TextView) convertView.findViewById(R.id.productName);
		TextView description = (TextView) convertView
				.findViewById(R.id.productDescription);
		TextView price = (TextView) convertView.findViewById(R.id.productPrice);

		NetworkImageView image = (NetworkImageView) convertView
				.findViewById(R.id.productImage);

		Button btnAddToCart = (Button) convertView
				.findViewById(R.id.btnAddToCart);

		final Product product = products.get(position);

		name.setText(product.getName());

		description.setText(product.getDescription());

		price.setText("Price: $" + product.getPrice());

		// user profile pic
		image.setImageUrl(product.getImage(), imageLoader);

		btnAddToCart.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				listener.onAddToCartPressed(product);
			}
		});

		return convertView;
	}

	public interface ProductListAdapterListener {
		public void onAddToCartPressed(Product product);
	}

}



14. Now we have all the helpers classes ready. Let’s move to main activity and start adding the PayPal code. Create a layout file under res layout named activity_main.xml and add below code. This layout contains a ListView to display the product list and a button to do the ckeckout.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white" >

    <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:paddingBottom="20dp"
        android:divider="@color/list_divider"
        android:dividerHeight="1dp">
    </ListView>

    <Button
        android:id="@+id/btnCheckout"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="@string/checkout" 
        android:background="@color/btn_bg_checkout"/>

</RelativeLayout>



15. Open your MainActivity.java and do below changes. Basically to add PayPal support, we need to take below simple steps.

> Create a PayPalConfiguration object by setting necessary configuration like environment and client id.

> Start the PayPalService in onCreate()

> When user presses the checkout button, start the PaymentActivity by passing necessary information like final items, price, description etc.

> Once the payment is done, receive the PaymentConfirmation in onActivityResult(). This is where you will receives paypal response like payment id and other important information.

> Finally send the payment id and response json to our server for server side verification.

In the below code

fetchProducts() – Fetches the products json from our server.

verifyPaymentOnServer() – Verifies the mobile payment on the server.

prepareFinalCart() – Prepare the final cart information like total amount, items that needs to be submitted to paypal payment activity.

launchPayPalPayment() – Launches the PayPal payment activity

onAddToCartPressed() – Adds the item to cart when add to cart button is pressed.

package info.androidhive.palpalclient;

import info.androidhive.palpalclient.app.AppController;
import info.androidhive.palpalclient.app.Config;
import info.androidhive.palpalclient.helper.Product;
import info.androidhive.palpalclient.helper.ProductListAdapter;
import info.androidhive.palpalclient.helper.ProductListAdapter.ProductListAdapterListener;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

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

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.RetryPolicy;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.StringRequest;
import com.paypal.android.sdk.payments.PayPalConfiguration;
import com.paypal.android.sdk.payments.PayPalItem;
import com.paypal.android.sdk.payments.PayPalPayment;
import com.paypal.android.sdk.payments.PayPalPaymentDetails;
import com.paypal.android.sdk.payments.PayPalService;
import com.paypal.android.sdk.payments.PaymentActivity;
import com.paypal.android.sdk.payments.PaymentConfirmation;

public class MainActivity extends Activity implements
		ProductListAdapterListener {
	private static final String TAG = MainActivity.class.getSimpleName();

	private ListView listView;
	private Button btnCheckout;

	// To store all the products
	private List<Product> productsList;

	// To store the products those are added to cart
	private List<PayPalItem> productsInCart = new ArrayList<PayPalItem>();

	private ProductListAdapter adapter;

	// Progress dialog
	private ProgressDialog pDialog;

	private static final int REQUEST_CODE_PAYMENT = 1;

	// PayPal configuration
	private static PayPalConfiguration paypalConfig = new PayPalConfiguration()
			.environment(Config.PAYPAL_ENVIRONMENT).clientId(
					Config.PAYPAL_CLIENT_ID);

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

		listView = (ListView) findViewById(R.id.list);
		btnCheckout = (Button) findViewById(R.id.btnCheckout);

		productsList = new ArrayList<Product>();
		adapter = new ProductListAdapter(this, productsList, this);

		listView.setAdapter(adapter);

		pDialog = new ProgressDialog(this);
		pDialog.setCancelable(false);

		// Starting PayPal service
		Intent intent = new Intent(this, PayPalService.class);
		intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, paypalConfig);
		startService(intent);

		// Checkout button click listener
		btnCheckout.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {

				// Check for empty cart
				if (productsInCart.size() > 0) {
					launchPayPalPayment();
				} else {
					Toast.makeText(getApplicationContext(), "Cart is empty! Please add few products to cart.",
							Toast.LENGTH_SHORT).show();
				}

			}
		});

		// Fetching products from server
		fetchProducts();
	}

	/**
	 * Fetching the products from our server
	 * */
	private void fetchProducts() {
		// Showing progress dialog before making request

		pDialog.setMessage("Fetching products...");

		showpDialog();

		// Making json object request
		JsonObjectRequest jsonObjReq = new JsonObjectRequest(Method.GET,
				Config.URL_PRODUCTS, null, new Response.Listener<JSONObject>() {

					@Override
					public void onResponse(JSONObject response) {
						Log.d(TAG, response.toString());

						try {
							JSONArray products = response
									.getJSONArray("products");

							// looping through all product nodes and storing
							// them in array list
							for (int i = 0; i < products.length(); i++) {

								JSONObject product = (JSONObject) products
										.get(i);

								String id = product.getString("id");
								String name = product.getString("name");
								String description = product
										.getString("description");
								String image = product.getString("image");
								BigDecimal price = new BigDecimal(product
										.getString("price"));
								String sku = product.getString("sku");

								Product p = new Product(id, name, description,
										image, price, sku);

								productsList.add(p);
							}

							// notifying adapter about data changes, so that the
							// list renders with new data
							adapter.notifyDataSetChanged();

						} catch (JSONException e) {
							e.printStackTrace();
							Toast.makeText(getApplicationContext(),
									"Error: " + e.getMessage(),
									Toast.LENGTH_LONG).show();
						}

						// hiding the progress dialog
						hidepDialog();
					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						VolleyLog.d(TAG, "Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						// hide the progress dialog
						hidepDialog();
					}
				});

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(jsonObjReq);
	}

	/**
	 * Verifying the mobile payment on the server to avoid fraudulent payment
	 * */
	private void verifyPaymentOnServer(final String paymentId,
			final String payment_client) {
		// Showing progress dialog before making request
		pDialog.setMessage("Verifying payment...");
		showpDialog();

		StringRequest verifyReq = new StringRequest(Method.POST,
				Config.URL_VERIFY_PAYMENT, new Response.Listener<String>() {

					@Override
					public void onResponse(String response) {
						Log.d(TAG, "verify payment: " + response.toString());

						try {
							JSONObject res = new JSONObject(response);
							boolean error = res.getBoolean("error");
							String message = res.getString("message");

							// user error boolean flag to check for errors

							Toast.makeText(getApplicationContext(), message,
									Toast.LENGTH_SHORT).show();

							if (!error) {
								// empty the cart
								productsInCart.clear();
							}

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

						// hiding the progress dialog
						hidepDialog();

					}
				}, new Response.ErrorListener() {

					@Override
					public void onErrorResponse(VolleyError error) {
						Log.e(TAG, "Verify Error: " + error.getMessage());
						Toast.makeText(getApplicationContext(),
								error.getMessage(), Toast.LENGTH_SHORT).show();
						// hiding the progress dialog
						hidepDialog();
					}
				}) {

			@Override
			protected Map<String, String> getParams() {

				Map<String, String> params = new HashMap<String, String>();
				params.put("paymentId", paymentId);
				params.put("paymentClientJson", payment_client);

				return params;
			}
		};

		// Setting timeout to volley request as verification request takes sometime
		int socketTimeout = 60000;
		RetryPolicy policy = new DefaultRetryPolicy(socketTimeout,
				DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
				DefaultRetryPolicy.DEFAULT_BACKOFF_MULT);
		verifyReq.setRetryPolicy(policy);

		// Adding request to request queue
		AppController.getInstance().addToRequestQueue(verifyReq);
	}

	/**
	 * Preparing final cart amount that needs to be sent to PayPal for payment
	 * */
	private PayPalPayment prepareFinalCart() {

		PayPalItem[] items = new PayPalItem[productsInCart.size()];
		items = productsInCart.toArray(items);

		// Total amount
		BigDecimal subtotal = PayPalItem.getItemTotal(items);

		// If you have shipping cost, add it here
		BigDecimal shipping = new BigDecimal("0.0");

		// If you have tax, add it here
		BigDecimal tax = new BigDecimal("0.0");

		PayPalPaymentDetails paymentDetails = new PayPalPaymentDetails(
				shipping, subtotal, tax);

		BigDecimal amount = subtotal.add(shipping).add(tax);

		PayPalPayment payment = new PayPalPayment(
				amount,
				Config.DEFAULT_CURRENCY,
				"Description about transaction. This will be displayed to the user.",
				Config.PAYMENT_INTENT);

		payment.items(items).paymentDetails(paymentDetails);

		// Custom field like invoice_number etc.,
		payment.custom("This is text that will be associated with the payment that the app can use.");

		return payment;
	}

	/**
	 * Launching PalPay payment activity to complete the payment
	 * */
	private void launchPayPalPayment() {

		PayPalPayment thingsToBuy = prepareFinalCart();

		Intent intent = new Intent(MainActivity.this, PaymentActivity.class);

		intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, paypalConfig);

		intent.putExtra(PaymentActivity.EXTRA_PAYMENT, thingsToBuy);

		startActivityForResult(intent, REQUEST_CODE_PAYMENT);
	}

	/**
	 * Receiving the PalPay payment response
	 * */
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (requestCode == REQUEST_CODE_PAYMENT) {
			if (resultCode == Activity.RESULT_OK) {
				PaymentConfirmation confirm = data
						.getParcelableExtra(PaymentActivity.EXTRA_RESULT_CONFIRMATION);
				if (confirm != null) {
					try {
						Log.e(TAG, confirm.toJSONObject().toString(4));
						Log.e(TAG, confirm.getPayment().toJSONObject()
								.toString(4));

						String paymentId = confirm.toJSONObject()
								.getJSONObject("response").getString("id");

						String payment_client = confirm.getPayment()
								.toJSONObject().toString();

						Log.e(TAG, "paymentId: " + paymentId
								+ ", payment_json: " + payment_client);

						// Now verify the payment on the server side
						verifyPaymentOnServer(paymentId, payment_client);

					} catch (JSONException e) {
						Log.e(TAG, "an extremely unlikely failure occurred: ",
								e);
					}
				}
			} else if (resultCode == Activity.RESULT_CANCELED) {
				Log.e(TAG, "The user canceled.");
			} else if (resultCode == PaymentActivity.RESULT_EXTRAS_INVALID) {
				Log.e(TAG,
						"An invalid Payment or PayPalConfiguration was submitted.");
			}
		}
	}

	private void showpDialog() {
		if (!pDialog.isShowing())
			pDialog.show();
	}

	private void hidepDialog() {
		if (pDialog.isShowing())
			pDialog.dismiss();
	}

	@Override
	public void onAddToCartPressed(Product product) {

		PayPalItem item = new PayPalItem(product.getName(), 1,
				product.getPrice(), Config.DEFAULT_CURRENCY, product.getSku());

		productsInCart.add(item);

		Toast.makeText(getApplicationContext(),
				item.getName() + " added to cart!", Toast.LENGTH_SHORT).show();

	}

}


Here we completed android part too. In order to test both the server and mobile apps in the local environment, follow below steps.

11. Testing the App

> Make sure that both devices (the device running the php project and the android device) are connected to same wifi network.

> Replace the PAYPAL_CLIENT_ID & PAYPAL_SECRET in Config.php with your paypal keys in your php project.

> Replace the PAYPAL_CLIENT_ID in Config.java with your paypal client id

> Run the WAMP server and get the ip address of the machine by executing ipconfig in cmd. (On mac, use ifconfig to get the ip address). We need to use this ip address in the url instead of localhost.

> Replace the ip address of URL_PRODUCTS and URL_VERIFY_PAYMENT with your ip address in Config.java.

> Deploy and run the android app on device. If you get the products list displayed, your app successfully connecting with the php server.

> Try selecting the products you want to buy and proceed with the checkout. You should able to see PayPal payment screens and do the payment. When it ask for paypal credentials, use the sandbox credentials.

android paypal listing the shopping cart
android paypal payment gateway


12. Final Thoughts

⇒ Making Your App Live
When you are making your app live, read the guidelines ‘Going Live with Your Application‘ provided by PayPal to move your app to production environment.

In our app, change the environment from PayPalConfiguration.ENVIRONMENT_SANDBOX to PayPalConfiguration.ENVIRONMENT_PRODUCTION in Config.java


⇒ Supported Currencies
PayPal is not supporting all the currencies as of now. Here is the list of currencies PayPal is supporting. Unfortunately, for indian users INR is not in the list. But you can use google currency calculator to get realtime INR to USD currency convert rate.


References:
> Goto through PayPal REST API doc for detailed explanation of each REST API call.

> PayPal Android Mobile SDK

> PayPal PHP REST API SDK

> Server image used in the illustration.

]]>
0
Ravi Tamada <![CDATA[Android Integrating PayPal using PHP, MySQL – Part 1]]> http://www.androidhive.info/?p=30932 2015-02-24T09:09:04Z 2015-02-20T07:19:45Z When you are building an eCommerce app, integrating the payment gateway is one of the most important building blocks of an app. Today we are going to learn how to integrate most popular payment gateway PayPal in our android app.

For an eCommerce app, building android app is not enough. We need to have a server side component too to maintain the inventory, customers, transactions and other important things. So this tutorial covers both the topics by diving the article into two parts. In this part, we are going to learn how to build the PHP, MySQL server and integrating the PayPal REST API. In the next part Android Integrating PayPal using PHP, MySQL – Part 2 we’ll learn how to build the android app and integrate the PayPal gateway.

android paypal integration php mysql



1. Overview
2. Creating PayPal App (Client ID & Secret)
3. PayPal Sandbox Test Account
4. Downloading PayPal PHP Rest API SDK
5. Downloading Slim Framework
6. Downloading & Installing WAMP
7. Creating MySQL Database
8. Creating PHP Project
9. Downloading PayPal Android SDK (Part 2)
10. Creating the Android Project (Part 2)
11. Testing the App (Part 2)
12. Final Thoughts (Part 2)


Below are the screenshots of the final app.

android paypal listing the shopping cart
android paypal payment gateway


1. Overview

PayPal provides multiple payment options such as Single Payment (Receives the payment immediately), Future Payments (Can be used for recurring payments) and Profile Sharing (Obtains information about customer). For our use case Single payment is best suited options as we need to get the payment immediately once user purchases some products.

Below diagram explains the complete app flow from listing the products to completing the checkout.

1. First the android app connects with server and fetches the products json. Products json will be parsed and all the products will be displayed on the android app.

2. User selects the products and make the payment using PayPal payment option.

3. After successful payment, PayPal issues the json that contains the payment id.

4. Android app sends the payment id to our server for verification.

5. Server make a REST API call to PayPal along with payment id to verify the payment.

6. PayPal responds a json in which we have to check for “state”: “approved” (and few other flags) for a successful payment.


2. Creating PayPal App (Client ID & Secret)

In order to make the calls to PayPal API we need to create an app in developer.paypal.com and obtain Client ID & Secret.

1. Log into PayPal’s Developer account. If you are visiting for the first time, register and create a new account.

2. Once logged in, you will be redirected to my apps page where you can create a new app.

3. Give your app name, select developer account and click on create app. Once the app is created, you can notice the Client id & Secret. We’re gonna need these keys in our both server and client apps.

pay pal creating new app
pay pal client id and secret


3. PayPal Sandbox Test Account

Paypal provides a test environment called sandbox to test the app before going live. These test accounts comes with paypal balance credited into it using which you can make test purchases. To get your sandbox account credentials, follow below steps.

1. Go to PayPal’s Developer account and click on Accounts under Sandbox on the left panel.

2. On the right you can see the sandbox test accounts. Select the buyer email and click on Profile.

3. In the popup window, click on Change Password to change the password in case if you are not sure about the password.

4. In the popup window, goto Funding tab to see the test balance.

You have to use these credentials to test the app in sandbox environment.

paypal sanbox developer account
paypal sandbox account change password
paypal sandbox developer account money


4. Downloading PayPal PHP Rest API SDK

It’s always a good practise to use the SDKs provided by the vendors rather than building ourselves. Paypal provides REST API SDK for multiple platforms. As we choose to write the server side code in PHP, download the latest release of PayPal-PHP-SDK.

Here is the direct link for PayPal-PHP-SDK-1.2.0.zip


5. Downloading Slim Framework

PHP Slim library allows you to write simple and efficient REST APIs. This framework we use here to generate json responses. To know more about Slim, read my previous article Building REST API for Android app using PHP, Slim and MySQL about building a perfect REST API when your app needs to talk to a php, mysql server.

Download the latest version of Slim Framework.


6. Downloading & Installing WAMP

Download and Install WAMP from http://www.wampserver.com/en/ and start the program from Start => All Programs. Once started, you should be able to access via http://localhost/ in the browser.

Watch the below video to know how to download and install WAMP.


7. Creating MySQL Database

Overall we are going to create four tables. users (to store the customer information), products (to store the product information like name, price, description), payments (to store the paypal transactions) and sales (to keep the product sales done by customers). This is very simple database design. In real world scenario, the database will be more complex than this.

android paypal shopping cart database design

Open phpmyadmin by going to http://localhost/phpmyadmin and execute below SQL query to create required database and tables. Also I am inserting a user and few sample products for testing.


CREATE DATABASE IF NOT EXISTS `paypal` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci;
USE `paypal`;


CREATE TABLE IF NOT EXISTS `payments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) NOT NULL,
  `paypalPaymentId` text NOT NULL,
  `create_time` text NOT NULL,
  `update_time` text NOT NULL,
  `state` varchar(15) NOT NULL,
  `amount` decimal(6,2) NOT NULL,
  `currency` varchar(3) NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `userId` (`userId`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;


CREATE TABLE IF NOT EXISTS `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` text NOT NULL,
  `price` decimal(6,2) NOT NULL,
  `description` text NOT NULL,
  `image` text NOT NULL,
  `sku` text NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;


INSERT INTO `products` (`id`, `name`, `price`, `description`, `image`, `sku`, `created_at`) VALUES
(1, 'Google Nexus 6', '690.50', 'Midnight Blue, with 32 GB', 'http://api.androidhive.info/images/nexus5.jpeg', 'sku-2123wers100', '2015-02-04 23:19:42'),
(2, 'Sandisk Cruzer Blade 16 GB Flash Pendrive', '4.50', 'USB 2.0, 16 GB, Black & Red, Read 17.62 MB/sec, Write 4.42 MB/sec', 'http://api.androidhive.info/images/sandisk.jpeg', 'sku-78955545w', '2015-02-10 22:54:28'),
(3, 'Kanvas Katha Backpack', '11.25', '1 Zippered Pocket Outside at Front, Loop Handle, Dual Padded Straps at the Back, 1 Compartment', 'http://api.androidhive.info/images/backpack.jpeg', 'sku-8493948kk4', '2015-02-10 22:55:34'),
(4, 'Prestige Pressure Cooker', '30.00', 'Prestige Induction Starter Pack Deluxe Plus Pressure Cooker 5 L', 'http://api.androidhive.info/images/prestige.jpeg', 'sku-90903034ll', '2015-02-10 22:59:25');


CREATE TABLE IF NOT EXISTS `sales` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `paymentId` int(11) NOT NULL,
  `productId` int(11) NOT NULL,
  `state` varchar(15) NOT NULL,
  `salePrice` decimal(6,2) NOT NULL,
  `quantity` int(4) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `paymentId` (`paymentId`),
  KEY `productId` (`productId`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=17 ;


CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `email` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `id` (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;


INSERT INTO `users` (`id`, `name`, `email`) VALUES
(1, 'Android Hive', 'androidhive@gmail.com');

ALTER TABLE `payments`
  ADD CONSTRAINT `payments_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;

ALTER TABLE `sales`
  ADD CONSTRAINT `sales_ibfk_2` FOREIGN KEY (`productId`) REFERENCES `products` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  ADD CONSTRAINT `sales_ibfk_1` FOREIGN KEY (`paymentId`) REFERENCES `payments` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION;


8. Creating PHP Project

Once you are done creating the database, let’s create the PHP project. Below is the project structure that we are going to create now. Carefully place all the folders/files in appropriate location as shown in the diagram. I am using the Netbeans IDE to develop my PHP project.

paypal server php project structure

In the project, the purpose of each file/folder is

include – All the config & helper classes goes into this directory
libs – All the third party libraries (Slim & PayPal) will be placed here
v1 – It is the version directory of our API

index.php – All the API calls will be handled in this file
.htaccess – Contains Apache web server configuration



1. Goto the directory where wamp is installed (generally wamp will be installed at c:\wamp) and open www directory. This is where all the php projects will be placed.

2. Inside www folder, create a folder named PayPalServer. This is the root directory for our project.

3. Now inside PayPalServer, create three folders named include, libs and v1.

4. Paste the Slim Framework and PayPal SDK in libs folder.

5. Create a file named Config.php in the include directory and add the below code. This file contains configuration values like database credentials, paypal client id & secret and default currency. Change the username and password with your mysql credentials.

<?php

/**
 * Database configuration
 */
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_HOST', 'localhost');
define('DB_NAME', 'paypal');

define('DEFAULT_CURRENCY', 'USD');
define('PAYPAL_CLIENT_ID', 'AdOTNRDUqb6jBLfB1IaVrNHFqLKhWROWCNZiuGrPQBqI0h_Hbf6teycjptu0'); // Paypal client id
define('PAYPAL_SECRET', 'EP5sARCiqusS6XGQG3Y-DpZ5KRL9lagYy8Wg0cvMrnznTUGsen3HMzHqdkXZ'); // Paypal secret

?>



6. Create another file named DBConnect.php in include directory and paste below code. This class takes care of opening database connection.

<?php

/**
 * Handling database connection
 *
 * @author Ravi Tamada
 */
class DbConnect {

    private $conn;

    function __construct() {
        
    }

    /**
     * Establishing database connection
     * @return database connection handler
     */
    function connect() {
        include_once dirname(__FILE__) . '/Config.php';

        // Connecting to mysql database
        $this->conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);

        // Check for database connection error
        if (mysqli_connect_errno()) {
            echo "Failed to connect to MySQL: " . mysqli_connect_error();
        }

        // returing connection resource
        return $this->conn;
    }

}

?>



7. Create another class named DBHandler.php in include directory. This class contains methods to performs CRUD operations on the database.

getAllProducts() – Retrieves all the products from products table
getProductBySku() – Fetches a product by it’s sku code
storePayment() – Stores paypal payment transaction.
storeSale() – Stores sale of a particular product.

<?php

/**
 * Class to handle all db operations
 * This class will have CRUD methods for database tables
 *
 * @author Ravi Tamada
 */
class DbHandler {

    private $conn;

    function __construct() {

        require_once dirname(__FILE__) . '/DbConnect.php';
        // opening db connection
        $db = new DbConnect();
        $this->conn = $db->connect();
    }

    /**
     * Listing products
     * 
     * */
    public function getAllProducts() {
        $stmt = $this->conn->prepare("SELECT * FROM products");
        $stmt->execute();
        $products = $stmt->get_result();
        $stmt->close();
        return $products;
    }

    /*
     * Fetches a product by its sku
     */
    public function getProductBySku($sku) {
        $stmt = $this->conn->prepare("SELECT * FROM products where sku = ?");
        $stmt->bind_param("s", $sku);

        if ($stmt->execute()) {
            $product = $stmt->get_result()->fetch_assoc();
            $stmt->close();
            return $product;
        } else {
            $stmt->close();
            return NULL;
        }
    }

    /**
     * Stores paypal payment in payments table
     */
    public function storePayment($paypalPaymentId, $userId, $create_time, $update_time, $state, $amount, $currency) {
        $stmt = $this->conn->prepare("INSERT INTO payments(paypalPaymentId, userId, create_time, update_time, state, amount, currency) VALUES(?,?,?,?,?,?,?)") or die(mysql_error());
        $stmt->bind_param("sisssds", $paypalPaymentId, $userId, $create_time, $update_time, $state, $amount, $currency);
        $result = $stmt->execute();
        $stmt->close();

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

    /**
     * Stores item sale in sales table
     */
    public function storeSale($payment_id, $product_id, $state, $salePrice, $quantity) {
        $stmt = $this->conn->prepare("INSERT INTO sales(paymentId, productId, state, salePrice, quantity) VALUES(?,?,?,?,?)");
        $stmt->bind_param("iisdi", $payment_id, $product_id, $state, $salePrice, $quantity);
        $result = $stmt->execute();
        $stmt->close();

        if ($result) {
            $sale_id = $this->conn->insert_id;
            return $sale_id;
        } else {
            // task failed to create
            return NULL;
        }
    }

}

?>



8. Now create a file named .htaccess inside v1 folder and add below rules.

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



9. Finally create index.php inside v1 and paste below code. This is most important file where we handle all the REST requests using Slim Framework.

In the below code

echoResponse() – Prints final json response when API call is made

$app->get(‘/products’.. – Fetches all the products from products table and prints in json format

$app->post(‘/verifyPayment’.. – Verifies the paypal payment on the server side that was done on mobile app. This server side verification is very important for every paypal transaction to avoid fraudulent payments.



<?php

ini_set('display_errors', 1);
require_once '../include/DBHandler.php';
require '../libs/Slim/Slim.php';

require __DIR__ . '/../libs/PayPal/autoload.php';

use PayPal\Api\Payment;

\Slim\Slim::registerAutoloader();

$app = new \Slim\Slim();

$userId = 1;

/**
 * Echoing json response to client
 * @param String $status_code Http response code
 * @param Int $response Json response
 */
function echoResponse($status_code, $response) {
    $app = \Slim\Slim::getInstance();
    // Http response code
    $app->status($status_code);

    // setting response content type to json
    $app->contentType('application/json');

    echo json_encode($response);
}

function authenticate(\Slim\Route $route) {
    // Implement your user authentication here
    // Check http://www.androidhive.info/2014/01/how-to-create-rest-api-for-android-app-using-php-slim-and-mysql-day-23/
}

/**
 * Lists all products
 * method - GET
 */
$app->get('/products', 'authenticate', function() {
            $response = array();
            $db = new DbHandler();

            // fetching all products
            $result = $db->getAllProducts();

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

            // looping through result and preparing products array
            while ($task = $result->fetch_assoc()) {
                $tmp = array();
                $tmp["id"] = $task["id"];
                $tmp["name"] = $task["name"];
                $tmp["price"] = $task["price"];
                $tmp["description"] = $task["description"];
                $tmp["image"] = $task["image"];
                $tmp["sku"] = $task["sku"];
                $tmp["created_at"] = $task["created_at"];
                array_push($response["products"], $tmp);
            }

            echoResponse(200, $response);
        });

/**
 * verifying the mobile payment on the server side
 * method - POST
 * @param paymentId paypal payment id
 * @param paymentClientJson paypal json after the payment
 */
$app->post('/verifyPayment', 'authenticate', function() use ($app) {

            $response["error"] = false;
            $response["message"] = "Payment verified successfully";
            global $userId;


            require_once '../include/Config.php';

            try {
                $paymentId = $app->request()->post('paymentId');
                $payment_client = json_decode($app->request()->post('paymentClientJson'), true);

                $apiContext = new \PayPal\Rest\ApiContext(
                        new \PayPal\Auth\OAuthTokenCredential(
                        PAYPAL_CLIENT_ID, // ClientID
                        PAYPAL_SECRET      // ClientSecret
                        )
                );

                // Gettin payment details by making call to paypal rest api
                $payment = Payment::get($paymentId, $apiContext);

                // Verifying the state approved
                if ($payment->getState() != 'approved') {
                    $response["error"] = true;
                    $response["message"] = "Payment has not been verified. Status is " . $payment->getState();
                    echoResponse(200, $response);
                    return;
                }

                // Amount on client side
                $amount_client = $payment_client["amount"];

                // Currency on client side
                $currency_client = $payment_client["currency_code"];

                // Paypal transactions
                $transaction = $payment->getTransactions()[0];
                // Amount on server side
                $amount_server = $transaction->getAmount()->getTotal();
                // Currency on server side
                $currency_server = $transaction->getAmount()->getCurrency();
                $sale_state = $transaction->getRelatedResources()[0]->getSale()->getState();

                // Storing the payment in payments table
                $db = new DbHandler();
                $payment_id_in_db = $db->storePayment($payment->getId(), $userId, $payment->getCreateTime(), $payment->getUpdateTime(), $payment->getState(), $amount_server, $amount_server);

                // Verifying the amount
                if ($amount_server != $amount_client) {
                    $response["error"] = true;
                    $response["message"] = "Payment amount doesn't matched.";
                    echoResponse(200, $response);
                    return;
                }

                // Verifying the currency
                if ($currency_server != $currency_client) {
                    $response["error"] = true;
                    $response["message"] = "Payment currency doesn't matched.";
                    echoResponse(200, $response);
                    return;
                }

                // Verifying the sale state
                if ($sale_state != 'completed') {
                    $response["error"] = true;
                    $response["message"] = "Sale not completed";
                    echoResponse(200, $response);
                    return;
                }

                // storing the saled items
                insertItemSales($payment_id_in_db, $transaction, $sale_state);

                echoResponse(200, $response);
            } catch (\PayPal\Exception\PayPalConnectionException $exc) {
                if ($exc->getCode() == 404) {
                    $response["error"] = true;
                    $response["message"] = "Payment not found!";
                    echoResponse(404, $response);
                } else {
                    $response["error"] = true;
                    $response["message"] = "Unknown error occurred!" . $exc->getMessage();
                    echoResponse(500, $response);
                }
            } catch (Exception $exc) {
                $response["error"] = true;
                $response["message"] = "Unknown error occurred!" . $exc->getMessage();
                echoResponse(500, $response);
            }
        });

/**
 * method to store the saled items in sales table
 */
function insertItemSales($paymentId, $transaction, $state) {

    $item_list = $transaction->getItemList();

    $db = new DbHandler();

    foreach ($item_list->items as $item) {
        $sku = $item->sku;
        $price = $item->price;
        $quanity = $item->quantity;

        $product = $db->getProductBySku($sku);

        // inserting row into sales table
        $db->storeSale($paymentId, $product["id"], $state, $price, $quanity);
    }
}

$app->run();
?>



Now we have completed the server side part. Below are the final endpoints to which our android app should make requests.

URL endpoints

URLMethodParametersDescription
http://localhost/PayPalServer/v1/productsGETFetches all the products
http://localhost/PayPalServer/v1/verifyPaymentPOSTpaymentId, paymentClientJsonVerifies paypal payment

In the next article Android Integrating PayPal using PHP, MySQL – Part 2 we’re going to build the android app and integrate the PayPal gateway. If you have any queries in this part, please do comment in the comment section below.

]]>
0