AndroidHive | Tutorials, Games, Apps, Tips http://www.androidhive.info Sat, 16 May 2015 05:39:44 +0000 en-US hourly 1 http://wordpress.org/?v=3.9.1 Android Swipe Down to Refresh ListView Tutorialhttp://www.androidhive.info/2015/05/android-swipe-down-to-refresh-listview-tutorial/ http://www.androidhive.info/2015/05/android-swipe-down-to-refresh-listview-tutorial/#comments Thu, 14 May 2015 11:30:52 +0000 http://www.androidhive.info/?p=38270 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);
?>
]]>
http://www.androidhive.info/2015/05/android-swipe-down-to-refresh-listview-tutorial/feed/ 0
Android Getting Started with Material Designhttp://www.androidhive.info/2015/04/android-getting-started-with-material-design/ http://www.androidhive.info/2015/04/android-getting-started-with-material-design/#comments Sat, 11 Apr 2015 12:57:18 +0000 http://www.androidhive.info/?p=37987 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="android: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="androidhive.info.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 ActionBarActivity

> 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.ActionBarActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends ActionBarActivity {

    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.0.0'
    compile 'com.android.support:recyclerview-v7:21.0.+'
}



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.


import android.content.Context;
import android.content.res.TypedArray;
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 android.widget.LinearLayout;
import android.widget.TextView;

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

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) {
        }
    }

    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="androidhive.info.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.ActionBarActivity;
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 ActionBarActivity 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="androidhive.info.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
]]>
http://www.androidhive.info/2015/04/android-getting-started-with-material-design/feed/ 0
MoboMarket Shrinks Android Smartphone Management into a Few Clickshttp://www.androidhive.info/2015/03/mobomarket-shrinks-android-smartphone-management-into-a-few-clicks/ http://www.androidhive.info/2015/03/mobomarket-shrinks-android-smartphone-management-into-a-few-clicks/#comments Fri, 27 Mar 2015 07:59:36 +0000 http://www.androidhive.info/?p=36638 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

]]>
http://www.androidhive.info/2015/03/mobomarket-shrinks-android-smartphone-management-into-a-few-clicks/feed/ 0
Android Hosting PHP, MySQL RESTful services to DigitalOceanhttp://www.androidhive.info/2015/03/android-hosting-php-mysql-restful-services-to-digitalocean/ http://www.androidhive.info/2015/03/android-hosting-php-mysql-restful-services-to-digitalocean/#comments Thu, 05 Mar 2015 19:18:37 +0000 http://www.androidhive.info/?p=33894 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.


]]> http://www.androidhive.info/2015/03/android-hosting-php-mysql-restful-services-to-digitalocean/feed/ 0 Android Integrating PayPal using PHP, MySQL – Part 2http://www.androidhive.info/2015/02/android-integrating-paypal-using-php-mysql-part-2/ http://www.androidhive.info/2015/02/android-integrating-paypal-using-php-mysql-part-2/#comments Tue, 24 Feb 2015 09:09:05 +0000 http://www.androidhive.info/?p=31622 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.

]]>
http://www.androidhive.info/2015/02/android-integrating-paypal-using-php-mysql-part-2/feed/ 0
Android Integrating PayPal using PHP, MySQL – Part 1http://www.androidhive.info/2015/02/android-integrating-paypal-using-php-mysql-part-1/ http://www.androidhive.info/2015/02/android-integrating-paypal-using-php-mysql-part-1/#comments Fri, 20 Feb 2015 07:19:45 +0000 http://www.androidhive.info/?p=30932 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.

]]>
http://www.androidhive.info/2015/02/android-integrating-paypal-using-php-mysql-part-1/feed/ 0
Android Location API using Google Play Serviceshttp://www.androidhive.info/2015/02/android-location-api-using-google-play-services/ http://www.androidhive.info/2015/02/android-location-api-using-google-play-services/#comments Tue, 03 Feb 2015 17:59:07 +0000 http://www.androidhive.info/?p=30659 In my previous post about Android GPS, Location Manager, I explained how to get device location (latitude & longitude) using the older android APIs. Now google introduced new way of getting device location using the Google Play Services.

A newer api called FusedLocationApi was introduced which connects with GoogleApiClient and gives us the best location available.

So let’s start this by creating a simple app.

android location api using google play services


1. Downloading & Importing Google Play Services

As this app needs Google Play Services, we need to setup the play services first. If you have the play services installed already, update them to latest version using Android SDK Manager.

1. Open Android SDK Manager and install or update the play services under Extras section.

android sdk manager installing play services

2. In Eclipse goto File ⇒ Import ⇒ Android ⇒ Existing Android Code Into Workspace

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

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

google api console creating new client id


2. Creating Android Project

Once the play services are downloaded and imported into eclipse workspace, we can start building a simple app with the location services integrated.

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

I gave my project name as Location API and package name as info.androidhive.locationapi

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

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

3. Download this marker.png and paste it in your project’s src ⇒ res ⇒ drawable-ldpi folder. (Please note that this is a white color png image, it might not be visible in your browser window)

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

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

    <string name="app_name">Location API</string>
    <string name="lbl_you_are_at">YOU ARE AT</string>
    <string name="btn_get_location">GET MY LOCATION</string>
    <string name="btn_start_location_updates">START LOCATION UPDATES</string>
    <string name="btn_stop_location_updates">STOP LOCATION UPDATES</string>

</resources>



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

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

    <color name="view_bg">#b20e0f</color>
    <color name="white">#ffffff</color>
    <color name="btn_bg">#3e4a56</color>

</resources>



6. Open AndroidManifest.xml and add ACCESS_FINE_LOCATION permission. You also need to add below meta-data for google play services version.

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

After doing required changes, 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.locationapi"
    android:versionCode="1"
    android:versionName="1.0" >

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

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

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

        <activity
            android:name="info.androidhive.locationapi.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>



7. Now we’ll quickly create a simple layout for our app. Open the layout file of your main activity (activity_main.xml) and add below code. This layout contains a TextView to display the location and two buttons (one is to get location and other is to toggle periodic location updates).

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/view_bg"
    android:gravity="center_horizontal"
    android:orientation="vertical" >

    <ImageView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_marginTop="60dp"
        android:src="@drawable/marker" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="25dp"
        android:text="@string/lbl_you_are_at"
        android:textColor="@color/white"
        android:textSize="25dp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/lblLocation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:padding="15dp"
        android:textColor="@color/white"
        android:textSize="16dp" />

    <Button
        android:id="@+id/btnShowLocation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:background="@color/btn_bg"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:text="@string/btn_get_location"
        android:textColor="@color/white" />

    <Button
        android:id="@+id/btnLocationUpdates"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="60dp"
        android:background="@color/btn_bg"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:text="@string/btn_start_location_updates"
        android:textColor="@color/white" />

</LinearLayout>


8. Now we’ll start adding the code related to location api. Open your main activity MainActivity.java and implement the class from ConnectionCallbacks, OnConnectionFailedListener.

public class MainActivity1 extends Activity implements ConnectionCallbacks,
		OnConnectionFailedListener {

}

In brief, you need to do below changes in your activity to get the user’s current location.

> First check for availability of Google Play Services by calling checkPlayServices() in onResume()

> Once play services are available on the device, build the GoogleApiClient by calling buildGoogleApiClient() method.

> Connect to google api client by calling mGoogleApiClient.connect() in onStart() method. By calling this, onConnectionFailed(), onConnected() and onConnectionSuspended() will be triggered depending upon the connection status.

> Once google api is successfully connected, displayLocation() should be called in onConnected() method to get the current location.

Add the below code to your main activity and run the project. Make sure that the wifi and location is enabled on your device before you test.

package info.androidhive.locationapi;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;

import android.app.Activity;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity1 extends Activity implements ConnectionCallbacks,
		OnConnectionFailedListener {
	// LogCat tag
	private static final String TAG = MainActivity.class.getSimpleName();

	private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 1000;

	private Location mLastLocation;

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

	// boolean flag to toggle periodic location updates
	private boolean mRequestingLocationUpdates = false;

	private LocationRequest mLocationRequest;

	// Location updates intervals in sec
	private static int UPDATE_INTERVAL = 10000; // 10 sec
	private static int FATEST_INTERVAL = 5000; // 5 sec
	private static int DISPLACEMENT = 10; // 10 meters

	// UI elements
	private TextView lblLocation;
	private Button btnShowLocation, btnStartLocationUpdates;

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

		lblLocation = (TextView) findViewById(R.id.lblLocation);
		btnShowLocation = (Button) findViewById(R.id.btnShowLocation);
		btnStartLocationUpdates = (Button) findViewById(R.id.btnLocationUpdates);

		// First we need to check availability of play services
		if (checkPlayServices()) {

			// Building the GoogleApi client
			buildGoogleApiClient();
		}

		// Show location button click listener
		btnShowLocation.setOnClickListener(new View.OnClickListener() {

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

	/**
	 * Method to display the location on UI
	 * */
	private void displayLocation() {

		mLastLocation = LocationServices.FusedLocationApi
				.getLastLocation(mGoogleApiClient);

		if (mLastLocation != null) {
			double latitude = mLastLocation.getLatitude();
			double longitude = mLastLocation.getLongitude();

			lblLocation.setText(latitude + ", " + longitude);

		} else {

			lblLocation
					.setText("(Couldn't get the location. Make sure location is enabled on the device)");
		}
	}

	/**
	 * Creating google api client object
	 * */
	protected synchronized void buildGoogleApiClient() {
		mGoogleApiClient = new GoogleApiClient.Builder(this)
				.addConnectionCallbacks(this)
				.addOnConnectionFailedListener(this)
				.addApi(LocationServices.API).build();
	}

	/**
	 * Method to verify google play services on the device
	 * */
	private boolean checkPlayServices() {
		int resultCode = GooglePlayServicesUtil
				.isGooglePlayServicesAvailable(this);
		if (resultCode != ConnectionResult.SUCCESS) {
			if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
				GooglePlayServicesUtil.getErrorDialog(resultCode, this,
						PLAY_SERVICES_RESOLUTION_REQUEST).show();
			} else {
				Toast.makeText(getApplicationContext(),
						"This device is not supported.", Toast.LENGTH_LONG)
						.show();
				finish();
			}
			return false;
		}
		return true;
	}

	@Override
	protected void onStart() {
		super.onStart();
		if (mGoogleApiClient != null) {
			mGoogleApiClient.connect();
		}
	}

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

		checkPlayServices();
	}

	/**
	 * Google api callback methods
	 */
	@Override
	public void onConnectionFailed(ConnectionResult result) {
		Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = "
				+ result.getErrorCode());
	}

	@Override
	public void onConnected(Bundle arg0) {

		// Once connected with google api, get the location
		displayLocation();
	}

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


android location api using google play services


Receiving Location Updates

9. In certain scenarios, your app might needs location updates periodically. Let’s say you are building a direction app where user needs to be get updated whenever location is changed. In that case you need to request for location updates. Doing the below changes, you will get the new location wherever location is changed.

> Implement the activity from LocationListener which adds onLocationChanged() method.

> Create LocationRequest object by calling createLocationRequest() method in onCreate() method upon checking the play services availability.

> Add togglePeriodicLocationUpdates() method which toggles listening to location updates.

> Start the location updates by calling startLocationUpdates() in onConnected() and onResume() methods.

> Stop the location updates by calling stopLocationUpdates() in onStop().

> startLocationUpdates() and stopLocationUpdates() methods are used to start/stop the location updates.

> onLocationChanged() method will be triggered whenever the location is changed. Calling displayLocation() inside onLocationChanged will display new location data on the UI.

public class MainActivity extends Activity implements ConnectionCallbacks,
		OnConnectionFailedListener, LocationListener {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		// First we need to check availability of play services
		if (checkPlayServices()) {

			createLocationRequest();
		}

		// Toggling the periodic location updates
		btnStartLocationUpdates.setOnClickListener(new View.OnClickListener() {

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

	}

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

		// Resuming the periodic location updates
		if (mGoogleApiClient.isConnected() && mRequestingLocationUpdates) {
			startLocationUpdates();
		}
	}

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

	/**
	 * Method to toggle periodic location updates
	 * */
	private void togglePeriodicLocationUpdates() {
		if (!mRequestingLocationUpdates) {
			// Changing the button text
			btnStartLocationUpdates
					.setText(getString(R.string.btn_stop_location_updates));

			mRequestingLocationUpdates = true;

			// Starting the location updates
			startLocationUpdates();

			Log.d(TAG, "Periodic location updates started!");

		} else {
			// Changing the button text
			btnStartLocationUpdates
					.setText(getString(R.string.btn_start_location_updates));

			mRequestingLocationUpdates = false;

			// Stopping the location updates
			stopLocationUpdates();

			Log.d(TAG, "Periodic location updates stopped!");
		}
	}

	/**
	 * Creating location request object
	 * */
	protected void createLocationRequest() {
		mLocationRequest = new LocationRequest();
		mLocationRequest.setInterval(UPDATE_INTERVAL);
		mLocationRequest.setFastestInterval(FATEST_INTERVAL);
		mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
		mLocationRequest.setSmallestDisplacement(DISPLACEMENT); // 10 meters
	}

	/**
	 * Starting the location updates
	 * */
	protected void startLocationUpdates() {

		LocationServices.FusedLocationApi.requestLocationUpdates(
				mGoogleApiClient, mLocationRequest, this);

	}

	/**
	 * Stopping location updates
	 */
	protected void stopLocationUpdates() {
		LocationServices.FusedLocationApi.removeLocationUpdates(
				mGoogleApiClient, this);
	}

	@Override
	public void onConnected(Bundle arg0) {

		// Once connected with google api, get the location
		displayLocation();

		if (mRequestingLocationUpdates) {
			startLocationUpdates();
		}
	}

	@Override
	public void onLocationChanged(Location location) {
		// Assign the new location
		mLastLocation = location;

		Toast.makeText(getApplicationContext(), "Location changed!",
				Toast.LENGTH_SHORT).show();

		// Displaying the new location on UI
		displayLocation();
	}

}

After doing all the above changes, run and test the app. If your app is not getting location, follow below steps to debug the app.

android location api using google play services


3. Testing the App

Below are the few key points should be kept in mind while testing the app.

> Your device should have internet connection (Wifi or mobile 3G).

> Location service should be enabled. Go to Settings => Location => Turn On.

> When you run the app, if you are not able to get the location even though you have done above two steps, open any of google’s location apps (maps) and come back to our app or just tap on START LOCATION UPDATES.

> If you are testing the periodic location updates, go out and take a short walk (few steps). You should see the locationChanged method calling by giving latest location coordinates.


Complete Code:

Below is the complete code of MainActivity.java

package info.androidhive.locationapi;

import android.app.Activity;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;

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

	// LogCat tag
	private static final String TAG = MainActivity.class.getSimpleName();

	private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 1000;

	private Location mLastLocation;

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

	// boolean flag to toggle periodic location updates
	private boolean mRequestingLocationUpdates = false;

	private LocationRequest mLocationRequest;

	// Location updates intervals in sec
	private static int UPDATE_INTERVAL = 10000; // 10 sec
	private static int FATEST_INTERVAL = 5000; // 5 sec
	private static int DISPLACEMENT = 10; // 10 meters

	// UI elements
	private TextView lblLocation;
	private Button btnShowLocation, btnStartLocationUpdates;

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

		lblLocation = (TextView) findViewById(R.id.lblLocation);
		btnShowLocation = (Button) findViewById(R.id.btnShowLocation);
		btnStartLocationUpdates = (Button) findViewById(R.id.btnLocationUpdates);

		// First we need to check availability of play services
		if (checkPlayServices()) {

			// Building the GoogleApi client
			buildGoogleApiClient();

			createLocationRequest();
		}

		// Show location button click listener
		btnShowLocation.setOnClickListener(new View.OnClickListener() {

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

		// Toggling the periodic location updates
		btnStartLocationUpdates.setOnClickListener(new View.OnClickListener() {

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

	}

	@Override
	protected void onStart() {
		super.onStart();
		if (mGoogleApiClient != null) {
			mGoogleApiClient.connect();
		}
	}

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

		checkPlayServices();

		// Resuming the periodic location updates
		if (mGoogleApiClient.isConnected() && mRequestingLocationUpdates) {
			startLocationUpdates();
		}
	}

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

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

	/**
	 * Method to display the location on UI
	 * */
	private void displayLocation() {

		mLastLocation = LocationServices.FusedLocationApi
				.getLastLocation(mGoogleApiClient);

		if (mLastLocation != null) {
			double latitude = mLastLocation.getLatitude();
			double longitude = mLastLocation.getLongitude();

			lblLocation.setText(latitude + ", " + longitude);

		} else {

			lblLocation
					.setText("(Couldn't get the location. Make sure location is enabled on the device)");
		}
	}

	/**
	 * Method to toggle periodic location updates
	 * */
	private void togglePeriodicLocationUpdates() {
		if (!mRequestingLocationUpdates) {
			// Changing the button text
			btnStartLocationUpdates
					.setText(getString(R.string.btn_stop_location_updates));

			mRequestingLocationUpdates = true;

			// Starting the location updates
			startLocationUpdates();

			Log.d(TAG, "Periodic location updates started!");

		} else {
			// Changing the button text
			btnStartLocationUpdates
					.setText(getString(R.string.btn_start_location_updates));

			mRequestingLocationUpdates = false;

			// Stopping the location updates
			stopLocationUpdates();

			Log.d(TAG, "Periodic location updates stopped!");
		}
	}

	/**
	 * Creating google api client object
	 * */
	protected synchronized void buildGoogleApiClient() {
		mGoogleApiClient = new GoogleApiClient.Builder(this)
				.addConnectionCallbacks(this)
				.addOnConnectionFailedListener(this)
				.addApi(LocationServices.API).build();
	}

	/**
	 * Creating location request object
	 * */
	protected void createLocationRequest() {
		mLocationRequest = new LocationRequest();
		mLocationRequest.setInterval(UPDATE_INTERVAL);
		mLocationRequest.setFastestInterval(FATEST_INTERVAL);
		mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
		mLocationRequest.setSmallestDisplacement(DISPLACEMENT);
	}

	/**
	 * Method to verify google play services on the device
	 * */
	private boolean checkPlayServices() {
		int resultCode = GooglePlayServicesUtil
				.isGooglePlayServicesAvailable(this);
		if (resultCode != ConnectionResult.SUCCESS) {
			if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
				GooglePlayServicesUtil.getErrorDialog(resultCode, this,
						PLAY_SERVICES_RESOLUTION_REQUEST).show();
			} else {
				Toast.makeText(getApplicationContext(),
						"This device is not supported.", Toast.LENGTH_LONG)
						.show();
				finish();
			}
			return false;
		}
		return true;
	}

	/**
	 * Starting the location updates
	 * */
	protected void startLocationUpdates() {

		LocationServices.FusedLocationApi.requestLocationUpdates(
				mGoogleApiClient, mLocationRequest, this);

	}

	/**
	 * Stopping location updates
	 */
	protected void stopLocationUpdates() {
		LocationServices.FusedLocationApi.removeLocationUpdates(
				mGoogleApiClient, this);
	}

	/**
	 * Google api callback methods
	 */
	@Override
	public void onConnectionFailed(ConnectionResult result) {
		Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = "
				+ result.getErrorCode());
	}

	@Override
	public void onConnected(Bundle arg0) {

		// Once connected with google api, get the location
		displayLocation();

		if (mRequestingLocationUpdates) {
			startLocationUpdates();
		}
	}

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

	@Override
	public void onLocationChanged(Location location) {
		// Assign the new location
		mLastLocation = location;

		Toast.makeText(getApplicationContext(), "Location changed!",
				Toast.LENGTH_SHORT).show();

		// Displaying the new location on UI
		displayLocation();
	}

}


References:
Making Your App Location-Aware

]]>
http://www.androidhive.info/2015/02/android-location-api-using-google-play-services/feed/ 0
How to Play YouTube Video in Android Apphttp://www.androidhive.info/2014/12/how-to-play-youtube-video-in-android-app/ http://www.androidhive.info/2014/12/how-to-play-youtube-video-in-android-app/#comments Fri, 26 Dec 2014 09:39:56 +0000 http://www.androidhive.info/?p=3504 We can see lot of android apps playing videos inside the app demonstrating app overview or an intro. Storing the video inside the project will increase the app size. So instead, we can upload the video to YouTube and stream it in the app to decreases the app size.

In this tutorial we are going to learn how to play YouTube video in the app. This app will have a single screen with a video playing in it. This article covers very basics of YouTube Android API. If you want to dig deep and build a fully fledged youtube app, please go through YouTube Android Player API docs provided by Google.

Android playing youtube video



As we are interacting with Google APIs, we need to get the Google Developer API Key first. Follow below steps to obtain your Google Developer Android API Key.

1. Obtaining the Android API Key

1. First we need to get the SHA-1 fingerprint on your machine using java keytool. Execute the below command in cmd/terminal to get the SHA-1 fingerprint.

On Windows

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

On Linux or Mac

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

2. Go to Google Developer Console and select or create a new project.

3. On the left sidebar, select APIs under APIs & auth and turn the status ON for YouTube Data API v3.

4. On the left sidebar, select Credentials and Create new key under Public API acess.

5. When popup comes asking you to choose platform, select Android Key.

6. Paste the SHA-1 key and your project’s package name separated by semicolon(;).

7. Click on create. Now you should see the API KEY on the dashboard.

android google developer console api key



Now we have the API Key required for this project. Let’s create a new android project and start building the app.

2. Creating the Android Project

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

2. Download the latest of version of YouTube Android Player API and extract it. Once extracted, you can find YouTubeAndroidPlayerApi.jar file inside libs folder.

3. Paste the YouTubeAndroidPlayerApi.jar file in your project’s libs folder.

4. Add the below string values to strings.xml located under res ⇒ values.

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

    <string name="app_name">Youtube Player</string>
    <string name="title_logo">NATIONAL GEOGRAPHIC</string>
    <string name="btn_skip_intro">Skip Intro</string>
    
    
    <string name="error_player">There was an error initializing the YouTubePlayer (%1$s)</string>

</resources>



5. Also add these color values to colors.xml located under res ⇒ values. If you don’t see colors.xml, create a new file with the same name.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="shadow">#555555</color>
    <color name="title">#777777</color>
</resources>



6. Create a class named Config.java to keep our app configuration variables like Google Developer Key and YouTube video id.

In the below class, you need to replace the DEVELOPER_KEY with your own API KEY that we generated in the Google Developer Console.
package info.androidhive.youtubeplayer;

public class Config {
	// Google Console APIs developer key
	// Replace this key with your's
	public static final String DEVELOPER_KEY = "AIzaSyABYoczeHg4XABx_jMRfv-CqmA2YMsIY4A";
	
	// YouTube video id
	public static final String YOUTUBE_VIDEO_CODE = "_oEA18Y8gM0";
}



7. Download this drawable folder and paste it in your project’s res folder. This folder contains few images required for this project.

8. Create an xml file named rouned_corner_shadow.xml inside drawable folder. This drawable layout gives rounded corner background with a shadow effect to the view.

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item>
        <shape android:shape="rectangle" >
            <solid android:color="@color/shadow" />

            <corners android:radius="4dp" />
        </shape>
    </item>
    <item
        android:bottom="2dp"
        android:left="0dp"
        android:right="0dp"
        android:top="0dp">
        <shape android:shape="rectangle" >
            <solid android:color="@android:color/white" />

            <corners android:radius="4dp" />
        </shape>
    </item>

</layer-list>



9. Now open the layout file of your main activity (activity_main.xml) and add below code. This creates a simple layout with YouTubePlayerView.

<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" >

    <ImageView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_centerInParent="true"
        android:scaleType="centerCrop"
        android:src="@drawable/snake_bg" />

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

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

            <com.google.android.youtube.player.YouTubePlayerView
                android:id="@+id/youtube_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="30dp" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="70dp"
                android:layout_marginBottom="20dp"
                android:scaleType="fitCenter"
                android:src="@drawable/nat_geo_logo" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="10dp"
                android:text="@string/title_logo"
                android:textColor="@color/title"
                android:textSize="20dp"
                android:textStyle="bold" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="40dp"
                android:layout_marginBottom="30dp"
                android:scaleType="fitCenter"
                android:src="@drawable/wild" />
        </LinearLayout>

        <Button
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="40dp"
            android:background="@drawable/rouned_corner_shadow"
            android:text="@string/btn_skip_intro" />
    </LinearLayout>

</RelativeLayout>



10.Open your main activity class (MainActivity.java) and do the below simple changes. Here the activity is extended from YouTubeBaseActivity which will be present in YouTubeAndroidPlayerApi.jar. This activity also contains few initialization listener methods to know the status of the youtube player.

package info.androidhive.youtubeplayer;

import android.content.Intent;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;

import com.google.android.youtube.player.YouTubeBaseActivity;
import com.google.android.youtube.player.YouTubeInitializationResult;
import com.google.android.youtube.player.YouTubePlayer;
import com.google.android.youtube.player.YouTubePlayer.PlayerStyle;
import com.google.android.youtube.player.YouTubePlayerView;

public class MainActivity extends YouTubeBaseActivity implements
		YouTubePlayer.OnInitializedListener {

	private static final int RECOVERY_DIALOG_REQUEST = 1;

	// YouTube player view
	private YouTubePlayerView youTubeView;

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

		setContentView(R.layout.activity_main);

		youTubeView = (YouTubePlayerView) findViewById(R.id.youtube_view);

		// Initializing video player with developer key
		youTubeView.initialize(Config.DEVELOPER_KEY, this);

	}

	@Override
	public void onInitializationFailure(YouTubePlayer.Provider provider,
			YouTubeInitializationResult errorReason) {
		if (errorReason.isUserRecoverableError()) {
			errorReason.getErrorDialog(this, RECOVERY_DIALOG_REQUEST).show();
		} else {
			String errorMessage = String.format(
					getString(R.string.error_player), errorReason.toString());
			Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
		}
	}

	@Override
	public void onInitializationSuccess(YouTubePlayer.Provider provider,
			YouTubePlayer player, boolean wasRestored) {
		if (!wasRestored) {

			// loadVideo() will auto play video
			// Use cueVideo() method, if you don't want to play it automatically
			player.loadVideo(Config.YOUTUBE_VIDEO_CODE);

			// Hiding player controls
			player.setPlayerStyle(PlayerStyle.CHROMELESS);
		}
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (requestCode == RECOVERY_DIALOG_REQUEST) {
			// Retry initialization if user performed a recovery action
			getYouTubePlayerProvider().initialize(Config.DEVELOPER_KEY, this);
		}
	}

	private YouTubePlayer.Provider getYouTubePlayerProvider() {
		return (YouTubePlayerView) findViewById(R.id.youtube_view);
	}

}



11. Finally open your AndroidManifest.xml and add INTERNET permission.

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

    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="21" />
    
    <uses-permission android:name="android.permission.INTERNET"/>

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

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

</manifest>

Now if you run the project, you should see the youtube video playing on launching of the app. Below is the final output of this tutorial.

android playing youtube video
]]>
http://www.androidhive.info/2014/12/how-to-play-youtube-video-in-android-app/feed/ 0
Android Uploading Camera Image, Video to Server with Progress Barhttp://www.androidhive.info/2014/12/android-uploading-camera-image-video-to-server-with-progress-bar/ http://www.androidhive.info/2014/12/android-uploading-camera-image-video-to-server-with-progress-bar/#comments Thu, 18 Dec 2014 11:25:08 +0000 http://www.androidhive.info/?p=26429 My previous tutorial explains how to download a file by showing a progress bar. In this article I am going to explain how to upload a file to server by showing the progress bar. Using this tutorial you can build an app like Instagram where you can capture image or record a video using camera and then upload to a server. On the server side, I used PHP language to read the file and moved it to a particular location.

The best thing about this article is, it works well with larger file uploads too without any out of memory errors. I have tested the app by uploading 50MB file flawlessly.

android file upload with progress bar


Prerequisite

As this article uploads the image/video taken from camera, you need to have knowledge over android camera module. So I recommend you go through my previous tutorial Android Working with Camera which gives you an overview of integrating camera in your android apps.


1. Creating Android Project

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

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

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

    <string name="app_name">Camera File Upload</string>
    <string name="btnTakePicture">Capture Image</string>
    <string name="btnRecordVideo">Record Video</string>
    <string name="or">(or)</string>
    <string name="btnUploadToServer">Upload to Server</string>

</resources>



3. Add below color values in colors.xml located under res ⇒ values folder.

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

    <color name="view_background">#e8ecfa</color>
    <color name="btn_bg">#277bec</color>
    <color name="white">#ffffff</color>
    <color name="txt_font">#4e5572</color>
    <color name="action_bar">#1f2649</color>

</resources>



4. Now under src folder create a new class named Config.java. This class file contains file upload URL and image directory name to save the image/video on mobile memory. You will have to replace the file upload url with yours while testing.

package info.androidhive.camerafileupload;

public class Config {
	// File upload url (replace the ip with your server address)
	public static final String FILE_UPLOAD_URL = "http://192.168.0.104/AndroidFileUpload/fileUpload.php";
	
	// Directory name to store captured images and videos
    public static final String IMAGE_DIRECTORY_NAME = "Android File Upload";
}



5. Create a class named AndroidMultiPartEntity.java and paste below code. This class is a custom MultipartEntity class which provides very important functionality required for this project such as progress bar incrementation.

package info.androidhive.camerafileupload;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;

import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;

@SuppressWarnings("deprecation")
public class AndroidMultiPartEntity extends MultipartEntity

{

	private final ProgressListener listener;

	public AndroidMultiPartEntity(final ProgressListener listener) {
		super();
		this.listener = listener;
	}

	public AndroidMultiPartEntity(final HttpMultipartMode mode,
			final ProgressListener listener) {
		super(mode);
		this.listener = listener;
	}

	public AndroidMultiPartEntity(HttpMultipartMode mode, final String boundary,
			final Charset charset, final ProgressListener listener) {
		super(mode, boundary, charset);
		this.listener = listener;
	}

	@Override
	public void writeTo(final OutputStream outstream) throws IOException {
		super.writeTo(new CountingOutputStream(outstream, this.listener));
	}

	public static interface ProgressListener {
		void transferred(long num);
	}

	public static class CountingOutputStream extends FilterOutputStream {

		private final ProgressListener listener;
		private long transferred;

		public CountingOutputStream(final OutputStream out,
				final ProgressListener listener) {
			super(out);
			this.listener = listener;
			this.transferred = 0;
		}

		public void write(byte[] b, int off, int len) throws IOException {
			out.write(b, off, len);
			this.transferred += len;
			this.listener.transferred(this.transferred);
		}

		public void write(int b) throws IOException {
			out.write(b);
			this.transferred++;
			this.listener.transferred(this.transferred);
		}
	}
}



Now we’ll add camera support in our app by creating a simple screen with two buttons to invoke camera app to capture image or record video.

6. Open your AndroidManifest.xml file and add required permissions. You can notice that UploadActivity also added in below manifest file. We’ll create it in few minutes.

INTERNET – Required to make network calls
WRITE_EXTERNAL_STORAGE – Required to store image/video on to storage
RECORD_AUDIO – Required to record audio along with video

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

    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="21" />

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

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="info.androidhive.camerafileupload.UploadActivity"
            android:screenOrientation="portrait" >
        </activity>
    </application>

</manifest>



7. Open the layout file of your main activity (activity_main.xml) and add below code. This creates a layout with two buttons.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/view_background"
    android:baselineAligned="false"
    android:orientation="vertical" >

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

        <!-- Capture picture button -->

        <Button
            android:id="@+id/btnCapturePicture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp"
            android:background="@color/btn_bg"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:text="@string/btnTakePicture"
            android:textColor="@color/white" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="20dp"
            android:gravity="center_horizontal"
            android:text="@string/or"
            android:textColor="@color/txt_font" />

        <!-- Record video button -->

        <Button
            android:id="@+id/btnRecordVideo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/btn_bg"
            android:paddingLeft="20dp"
            android:paddingRight="20dp"
            android:text="@string/btnRecordVideo"
            android:textColor="@color/white" />
    </LinearLayout>

</RelativeLayout>



8. Add below camera related code in your MainActivity.java class. This code is directly taken from this tutorial.

In brief what this activity will do is,

> Camera app will be launched on tapping take picture or record video button.
> Once the image / video is captured, it will be stored on to mobile SDCard.
> Finally UploadActivity will be launched by passing the SDCard path of the media that is captured. The process of uploading will be done in UploadActivity.

package info.androidhive.camerafileupload;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {
	
	// LogCat tag
	private static final String TAG = MainActivity.class.getSimpleName();
	
 
    // Camera activity request codes
    private static final int CAMERA_CAPTURE_IMAGE_REQUEST_CODE = 100;
    private static final int CAMERA_CAPTURE_VIDEO_REQUEST_CODE = 200;
    
    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;
 
    private Uri fileUri; // file url to store image/video
    
    private Button btnCapturePicture, btnRecordVideo;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // Changing action bar background color
        // These two lines are not needed
        getActionBar().setBackgroundDrawable(new ColorDrawable(Color.parseColor(getResources().getString(R.color.action_bar))));
 
        btnCapturePicture = (Button) findViewById(R.id.btnCapturePicture);
        btnRecordVideo = (Button) findViewById(R.id.btnRecordVideo);
 
        /**
         * Capture image button click event
         */
        btnCapturePicture.setOnClickListener(new View.OnClickListener() {
 
            @Override
            public void onClick(View v) {
                // capture picture
                captureImage();
            }
        });
 
        /**
         * Record video button click event
         */
        btnRecordVideo.setOnClickListener(new View.OnClickListener() {
 
            @Override
            public void onClick(View v) {
                // record video
                recordVideo();
            }
        });
 
        // Checking camera availability
        if (!isDeviceSupportCamera()) {
            Toast.makeText(getApplicationContext(),
                    "Sorry! Your device doesn't support camera",
                    Toast.LENGTH_LONG).show();
            // will close the app if the device does't have camera
            finish();
        }
    }
 
    /**
     * Checking device has camera hardware or not
     * */
    private boolean isDeviceSupportCamera() {
        if (getApplicationContext().getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_CAMERA)) {
            // this device has a camera
            return true;
        } else {
            // no camera on this device
            return false;
        }
    }
 
    /**
     * Launching camera app to capture image
     */
    private void captureImage() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 
        fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);
 
        intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
 
        // start the image capture Intent
        startActivityForResult(intent, CAMERA_CAPTURE_IMAGE_REQUEST_CODE);
    }
    
    /**
     * Launching camera app to record video
     */
    private void recordVideo() {
        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
 
        fileUri = getOutputMediaFileUri(MEDIA_TYPE_VIDEO);
 
        // set video quality
        intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
 
        intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file
                                                            // name
 
        // start the video capture Intent
        startActivityForResult(intent, CAMERA_CAPTURE_VIDEO_REQUEST_CODE);
    }
 
    /**
     * Here we store the file url as it will be null after returning from camera
     * app
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
 
        // save file url in bundle as it will be null on screen orientation
        // changes
        outState.putParcelable("file_uri", fileUri);
    }
 
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
 
        // get the file url
        fileUri = savedInstanceState.getParcelable("file_uri");
    }
 
    
 
    /**
     * Receiving activity result method will be called after closing the camera
     * */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // if the result is capturing Image
        if (requestCode == CAMERA_CAPTURE_IMAGE_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                
            	// successfully captured the image
                // launching upload activity
            	launchUploadActivity(true);
            	
            	
            } else if (resultCode == RESULT_CANCELED) {
                
            	// user cancelled Image capture
                Toast.makeText(getApplicationContext(),
                        "User cancelled image capture", Toast.LENGTH_SHORT)
                        .show();
            
            } else {
                // failed to capture image
                Toast.makeText(getApplicationContext(),
                        "Sorry! Failed to capture image", Toast.LENGTH_SHORT)
                        .show();
            }
        
        } else if (requestCode == CAMERA_CAPTURE_VIDEO_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                
            	// video successfully recorded
                // launching upload activity
            	launchUploadActivity(false);
            
            } else if (resultCode == RESULT_CANCELED) {
                
            	// user cancelled recording
                Toast.makeText(getApplicationContext(),
                        "User cancelled video recording", Toast.LENGTH_SHORT)
                        .show();
            
            } else {
                // failed to record video
                Toast.makeText(getApplicationContext(),
                        "Sorry! Failed to record video", Toast.LENGTH_SHORT)
                        .show();
            }
        }
    }
    
    private void launchUploadActivity(boolean isImage){
    	Intent i = new Intent(MainActivity.this, UploadActivity.class);
        i.putExtra("filePath", fileUri.getPath());
        i.putExtra("isImage", isImage);
        startActivity(i);
    }
     
    /**
     * ------------ Helper Methods ---------------------- 
     * */
 
    /**
     * Creating file uri to store image/video
     */
    public Uri getOutputMediaFileUri(int type) {
        return Uri.fromFile(getOutputMediaFile(type));
    }
 
    /**
     * returning image / video
     */
    private static File getOutputMediaFile(int type) {
 
        // External sdcard location
        File mediaStorageDir = new File(
                Environment
                        .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                Config.IMAGE_DIRECTORY_NAME);
 
        // Create the storage directory if it does not exist
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.d(TAG, "Oops! Failed create "
                        + Config.IMAGE_DIRECTORY_NAME + " directory");
                return null;
            }
        }
 
        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",
                Locale.getDefault()).format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator
                    + "IMG_" + timeStamp + ".jpg");
        } else if (type == MEDIA_TYPE_VIDEO) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator
                    + "VID_" + timeStamp + ".mp4");
        } else {
            return null;
        }
 
        return mediaFile;
    }
}



Now if you run the app, you should see following output.

android-file-upload-camera-screen
android-file-upload-camera-taking-camera-picture



Once you are able to launch camera and capture images, we can move forward and start creating the upload activity.

9. Create an xml file under res ⇒ layout folder named activity_upload.xml. This layout contains ImageView, VideoView to preview the captured media and a ProgressBar to show uploading progress.

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

    

    <!-- To display picture taken -->

    <ImageView
        android:id="@+id/imgPreview"
        android:layout_width="fill_parent"
        android:layout_height="200dp"
        android:visibility="gone" 
        android:layout_marginTop="15dp"/>

    <!-- Videoview to preview recorded video -->

    <VideoView
        android:id="@+id/videoPreview"
        android:layout_width="fill_parent"
        android:layout_height="400dp"
        android:visibility="gone" 
        android:layout_marginTop="15dp"/>

    <TextView
        android:id="@+id/txtPercentage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginBottom="15dp"
        android:layout_marginTop="15dp"
        android:textColor="@color/txt_font"
        android:textSize="30dp" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="20dp"
        android:layout_marginBottom="35dp" 
        android:visibility="gone"/>

    <Button
        android:id="@+id/btnUpload"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:background="@color/btn_bg"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        android:text="@string/btnUploadToServer"
        android:textColor="@color/white" 
        android:layout_marginBottom="20dp"/>

</LinearLayout>



10. Create a class named UploadActivity.java and paste below code. In this activity

> The path of captured camera image/video is received from MainActivity and image/video is displayed on the screen for preview purpose.
> UploadFileToServer async method takes care of uploading file to server and updating the Progress Bar.

package info.androidhive.camerafileupload;

import info.androidhive.camerafileupload.AndroidMultiPartEntity.ProgressListener;

import java.io.File;
import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;

public class UploadActivity extends Activity {
	// LogCat tag
	private static final String TAG = MainActivity.class.getSimpleName();

	private ProgressBar progressBar;
	private String filePath = null;
	private TextView txtPercentage;
	private ImageView imgPreview;
	private VideoView vidPreview;
	private Button btnUpload;
	long totalSize = 0;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_upload);
		txtPercentage = (TextView) findViewById(R.id.txtPercentage);
		btnUpload = (Button) findViewById(R.id.btnUpload);
		progressBar = (ProgressBar) findViewById(R.id.progressBar);
		imgPreview = (ImageView) findViewById(R.id.imgPreview);
		vidPreview = (VideoView) findViewById(R.id.videoPreview);

		// Changing action bar background color
		getActionBar().setBackgroundDrawable(
				new ColorDrawable(Color.parseColor(getResources().getString(
						R.color.action_bar))));

		// Receiving the data from previous activity
		Intent i = getIntent();

		// image or video path that is captured in previous activity
		filePath = i.getStringExtra("filePath");

		// boolean flag to identify the media type, image or video
		boolean isImage = i.getBooleanExtra("isImage", true);

		if (filePath != null) {
			// Displaying the image or video on the screen
			previewMedia(isImage);
		} else {
			Toast.makeText(getApplicationContext(),
					"Sorry, file path is missing!", Toast.LENGTH_LONG).show();
		}

		btnUpload.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// uploading the file to server
				new UploadFileToServer().execute();
			}
		});

	}

	/**
	 * Displaying captured image/video on the screen
	 * */
	private void previewMedia(boolean isImage) {
		// Checking whether captured media is image or video
		if (isImage) {
			imgPreview.setVisibility(View.VISIBLE);
			vidPreview.setVisibility(View.GONE);
			// bimatp factory
			BitmapFactory.Options options = new BitmapFactory.Options();

			// down sizing image as it throws OutOfMemory Exception for larger
			// images
			options.inSampleSize = 8;

			final Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

			imgPreview.setImageBitmap(bitmap);
		} else {
			imgPreview.setVisibility(View.GONE);
			vidPreview.setVisibility(View.VISIBLE);
			vidPreview.setVideoPath(filePath);
			// start playing
			vidPreview.start();
		}
	}

	/**
	 * Uploading the file to server
	 * */
	private class UploadFileToServer extends AsyncTask<Void, Integer, String> {
		@Override
		protected void onPreExecute() {
			// setting progress bar to zero
			progressBar.setProgress(0);
			super.onPreExecute();
		}

		@Override
		protected void onProgressUpdate(Integer... progress) {
			// Making progress bar visible
			progressBar.setVisibility(View.VISIBLE);

			// updating progress bar value
			progressBar.setProgress(progress[0]);

			// updating percentage value
			txtPercentage.setText(String.valueOf(progress[0]) + "%");
		}

		@Override
		protected String doInBackground(Void... params) {
			return uploadFile();
		}

		@SuppressWarnings("deprecation")
		private String uploadFile() {
			String responseString = null;

			HttpClient httpclient = new DefaultHttpClient();
			HttpPost httppost = new HttpPost(Config.FILE_UPLOAD_URL);

			try {
				AndroidMultiPartEntity entity = new AndroidMultiPartEntity(
						new ProgressListener() {

							@Override
							public void transferred(long num) {
								publishProgress((int) ((num / (float) totalSize) * 100));
							}
						});

				File sourceFile = new File(filePath);

				// Adding file data to http body
				entity.addPart("image", new FileBody(sourceFile));

				// Extra parameters if you want to pass to server
				entity.addPart("website",
						new StringBody("www.androidhive.info"));
				entity.addPart("email", new StringBody("abc@gmail.com"));

				totalSize = entity.getContentLength();
				httppost.setEntity(entity);

				// Making server call
				HttpResponse response = httpclient.execute(httppost);
				HttpEntity r_entity = response.getEntity();

				int statusCode = response.getStatusLine().getStatusCode();
				if (statusCode == 200) {
					// Server response
					responseString = EntityUtils.toString(r_entity);
				} else {
					responseString = "Error occurred! Http Status Code: "
							+ statusCode;
				}

			} catch (ClientProtocolException e) {
				responseString = e.toString();
			} catch (IOException e) {
				responseString = e.toString();
			}

			return responseString;

		}

		@Override
		protected void onPostExecute(String result) {
			Log.e(TAG, "Response from server: " + result);

			// showing the server response in an alert dialog
			showAlert(result);

			super.onPostExecute(result);
		}

	}

	/**
	 * Method to show alert dialog
	 * */
	private void showAlert(String message) {
		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setMessage(message).setTitle("Response from Servers")
				.setCancelable(false)
				.setPositiveButton("OK", new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int id) {
						// do nothing
					}
				});
		AlertDialog alert = builder.create();
		alert.show();
	}

}



Until now we are done with android project. Now let’s quickly create the PHP project to receive the file that is being sent from android app. But before that, we need to do small configuration changes to WAMP server.

2. Installing & Configuring WAMP Server

1. Download and install WAMP software. On windows machine, WAMP will be installed at C:\wamp location.

2. Open php.ini and modify below values. By default wamp server allows maximum of 2MB file only to upload. After changing the below values, you can upload the files upto 50MB size.

wamp-server-editing-php.ini-file
upload_max_filesize = 50M
post_max_size = 50M
max_input_time = 300
max_execution_time = 300

3. Now restart the WAMP server.


3. Creating PHP Project

1. Go inside C:\wamp\www and create a folder named AndroidFileUpload. This will be the root directory of our project.

2. Now go into AndroidFileUpload folder and create a folder named uploads to keep all the uploaded files.

3. Create a file named fileUpload.php and paste below content. Below php code takes care of receiving the files from android app and store them in uploads folder. Upon the processing the file, server responds with a JSON message.

<?php

// Path to move uploaded files
$target_path = "uploads/";

// array for final json respone
$response = array();

// getting server ip address
$server_ip = gethostbyname(gethostname());

// final file url that is being uploaded
$file_upload_url = 'http://' . $server_ip . '/' . 'AndroidFileUpload' . '/' . $target_path;


if (isset($_FILES['image']['name'])) {
    $target_path = $target_path . basename($_FILES['image']['name']);

    // reading other post parameters
    $email = isset($_POST['email']) ? $_POST['email'] : '';
    $website = isset($_POST['website']) ? $_POST['website'] : '';

    $response['file_name'] = basename($_FILES['image']['name']);
    $response['email'] = $email;
    $response['website'] = $website;

    try {
        // Throws exception incase file is not being moved
        if (!move_uploaded_file($_FILES['image']['tmp_name'], $target_path)) {
            // make error flag true
            $response['error'] = true;
            $response['message'] = 'Could not move the file!';
        }

        // File successfully uploaded
        $response['message'] = 'File uploaded successfully!';
        $response['error'] = false;
        $response['file_path'] = $file_upload_url . basename($_FILES['image']['name']);
    } catch (Exception $e) {
        // Exception occurred. Make error flag true
        $response['error'] = true;
        $response['message'] = $e->getMessage();
    }
} else {
    // File parameter is missing
    $response['error'] = true;
    $response['message'] = 'Not received any file!F';
}

// Echo final json response to client
echo json_encode($response);
?>



Below is the sample JSON response if the file is uploaded successfully. You can use error value to verify the upload on android side.

{
    "file_name": "DSC_0021.JPG",
    "email": "admin@androidhive.info",
    "website": "www.androidhive.info",
    "message": "File uploaded successfully!",
    "error": false,
    "file_path": "http://192.168.0.104/AndroidFileUpload/uploads/DSC_0021.JPG"
}


4. Testing the File Upload (localhost)

The following steps shows you how to test the both apps together locally.

1. Connect the both the devices (machine running the wamp server & android mobile) to same wifi network.

2. Start the WAMP server.

3. Get the ip address of the machine that is running the PHP project. You can get the ip address by typing ipconfig in command prompt. (On mac os, use ifconfig to get the ip address)

4. Replace the ip address in Config.java (check 4th step in android project) with your ip address.

5. Deploy & run the android app on the mobile.

android-uploading-camera-picture-to-server
android-uploading-camera-picture-to-server1
android-uploading-camera-picture-to-server2



References
1. Stackoverflow Question about file upload with progress bar.

2. Icon that I used as app icon.

]]>
http://www.androidhive.info/2014/12/android-uploading-camera-image-video-to-server-with-progress-bar/feed/ 0
Google Glass CardScrollView Examplehttp://www.androidhive.info/2014/12/google-glass-cardscrollview-example/ http://www.androidhive.info/2014/12/google-glass-cardscrollview-example/#comments Tue, 09 Dec 2014 04:16:10 +0000 http://www.androidhive.info/?p=26239 Google Glass CardScrollView is similar to ListView that we see in android mobile. On glass you can consider each list item as a Card Item which occupies fullscreen of the glass. CardScrollView allows us to swipe through the cards either from left to right, or vice versa.

This tutorial explains how to implement CardScrollView with a custom adapter class.

google glass card scroll view adapter



google glass card scroll view adapter



Creating New Glass Project

1. In Eclipse go to File ⇒ New ⇒ Android Application Project and give application name, project name and package.

2. Set Minimum Required SDK and Target SDK to API 19: Android 4.4 (KitKat), Compile With to Glass Development Kit Sneak Peek (Google Inc.) (API19) and select the Theme to None

3. Once the project is created, open AndroidManifest.xml file and remove the theme android:theme property to allow glass to apply it’s own theme.

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

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

    <string name="app_name">Card ScrollView</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
    
    <!-- "ok glass" voice command -->
    <string name="start_command">Movie Cards</string>

</resources>



5. Download this drawable.zip and paste the contents in project’s res ⇒ drawable folder. This downloaded folder contains card images required for this project.

6. Create two packages named model and adapter in your project.

7. Under model package, create a new class named MovieCard.java and paste below code. This model class represents single card item in CardScrollView.

package info.androidhive.cardscrollview.model;

import com.google.android.glass.app.Card.ImageLayout;

public class MovieCard {

	private String text;
	private String footerText;
	private ImageLayout imgLayout;
	private int[] images;

	public MovieCard() {
	}

	public MovieCard(String text, String footerText,
			ImageLayout imgLayout, int[] images) {		
		this.text = text;
		this.footerText = footerText;
		this.imgLayout = imgLayout;
		this.images = images;
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}

	public String getFooterText() {
		return footerText;
	}

	public void setFooterText(String footerText) {
		this.footerText = footerText;
	}

	public ImageLayout getImgLayout() {
		return imgLayout;
	}

	public void setImgLayout(ImageLayout imgLayout) {
		this.imgLayout = imgLayout;
	}

	public int[] getImages() {
		return images;
	}

	public void setImages(int[] images) {
		this.images = images;
	}

}


8. Now under adapter package, create a class named MovieCardsAdapter.java. This is the custom adapter class which provides data to cardscrollview.

package info.androidhive.cardscrollview.adapter;

import info.androidhive.cardscrollview.model.MovieCard;

import java.util.List;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.glass.app.Card;
import com.google.android.glass.widget.CardScrollAdapter;

public class MovieCardsAdapter extends CardScrollAdapter {
	private List<moviecard> mCards;
	private Context context;

	public MovieCardsAdapter(Context context, List<moviecard> mCards) {
		this.context = context;
		this.mCards = mCards;
	}

	@Override
	public int getPosition(Object item) {
		return mCards.indexOf(item);
	}

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

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		Card card = new Card(context);

		MovieCard mc = mCards.get(position);
		
		// Card text
		if (mc.getText() != null)
			card.setText(mc.getText());

		// Card footer note
		if (mc.getFooterText() != null)
			card.setFootnote(mc.getFooterText());

		// Set image layout
		if (mc.getImgLayout() != null)
			card.setImageLayout(mc.getImgLayout());
		
		// loop and set card images
		for(int img : mc.getImages()){
			card.addImage(img);
		}

		return card.getView();
	}

	

}



9. Open your main activity class MainActivity.java and do the below changes. In this activity we are adding the card items in prepareMovieCards() method.

package info.androidhive.cardscrollview;

import info.androidhive.cardscrollview.R;
import info.androidhive.cardscrollview.adapter.MovieCardsAdapter;
import info.androidhive.cardscrollview.model.MovieCard;

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

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

import com.google.android.glass.app.Card.ImageLayout;
import com.google.android.glass.widget.CardScrollView;

public class MainActivity extends Activity {

	private List<moviecard> mCards;
	private CardScrollView mCardScrollView;
	private Context context;

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

		context = this;

		prepareMovieCards();

		mCardScrollView = new CardScrollView(this);
		MovieCardsAdapter adapter = new MovieCardsAdapter(context, mCards);
		mCardScrollView.setAdapter(adapter);
		mCardScrollView.activate();
		setContentView(mCardScrollView);
	}

	private void prepareMovieCards() {
		mCards = new ArrayList<moviecard>();

		// Card with no background image
		MovieCard mc = new MovieCard("I don't know. But who cares! Ha ha!",
				"Wait! What does that mean?", ImageLayout.FULL, new int[] {});
		mCards.add(mc);

		// Card with full background image
		mc = new MovieCard("I wanna go home. Does anyone know where my dad is?",
				"Pet store?", ImageLayout.FULL,
				new int[] { R.drawable.card_full });
		mCards.add(mc);

		// Card with full background of 3 images
		mc = new MovieCard("Dude? Dude? Focus dude... Dude?",
				"Oh, he lives. Hey, dude!", ImageLayout.FULL, new int[] {
						R.drawable.card_bottom_left,
						R.drawable.card_bottom_right, R.drawable.card_top });
		mCards.add(mc);

		// Card with left aligned images
		mc = new MovieCard("Just keep swimming.",
				"I'm sorry, Dory. But I... do", ImageLayout.LEFT, new int[] {
						R.drawable.card_bottom_left,
						R.drawable.card_bottom_right, R.drawable.card_top });
		mCards.add(mc);

	}
}



10. Before the running the app, add the app to Ok Glass menu by doing following changes. Create a folder named xml under res folder.

11. Under xml folder, create an xml file named voice_trigger_start.xml and add below content.

<!--?xml version="1.0" encoding="utf-8"?-->
<trigger keyword="@string/start_command">



12. Finally open the AndroidManifest.xml and add the voice related actions to launch the app by voice command.

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

    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="19" />
    
    <uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/app_icon"
        android:label="@string/app_name" >
        <activity
            android:name="info.androidhive.cardscrollview.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
            </intent-filter>

            <meta-data
                android:name="com.google.android.glass.VoiceTrigger"
                android:resource="@xml/voice_trigger_start" />
        </activity>
    </application>

</manifest>

Now run the app and say “Ok Glass Movie Cards” to launch the app. Once the app is launched, you can see the CardScrollView with beautiful images as shown in the above top demo.

]]>
http://www.androidhive.info/2014/12/google-glass-cardscrollview-example/feed/ 0