Android Location API using Google Play Services

In this tutorial, we are going to learn how to implement android location service api using Google Play Services.

The previous android tutorial I wrote on this topic was on Android Framework Location API using Android LocationManager. As I stated in the tutorial that Google has suggested that to best way to work with android Location API is to use the Google Play Services.

This is what we are going to use in this tutorial. Unlike this previous tutorial, here where are going to add the Google Play Services to our application.

Android Google Play Services helps you take advantage of the latest, Google-powered features such as Maps, Google+, and more, with automatic platform updates distributed as an APK through the Google Play store. This makes it faster for your users to receive updates and easier for you to integrate the newest that Google has to offer.

If you want to read more about Google Play Service, you can refer to the link in android developers guide.

Note: If the number of method references in your app exceeds the 65K limit, your app may fail to compile. You may be able to mitigate this problem when compiling your app by specifying only the specific Google Play services APIs your app uses, instead of all of them. For information on how to do this, see Selectively compiling APIs into your executable.

Before we go deeper into this tutorial, it is important for us to understand what we are planning to achieve. Below is the screen-shot of the application we will be creating.

android location Api

Lets start to soil our hands in code. we will create our project in our IDE. For this tutorial, I am using the following tools and environment, feel free to use what works for you.

Windows 7

Android Studio

Sony Xperia ZL

Min SDK 14

Target SDK 23

To create a new android application project, follow the steps as stipulated below.

Go to File menu

Click on New menu

Click on Android Application

Enter Project name: AndroidLocationServices

Package: com.inducesmile.androidlocationservices

Select Blank Activity

Keep other default selections

Continue to click on next button until Finish button is active, then click on Finish Button.

Now that we have created our android project, we are going to add Google Play Services as one of our application dependency module.

Open your android build.gradle   file located in gradle script/build.gradle , copy and paste this line of code to the dependencies section.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.google.android.gms:play-services:8.4.0'
}

Go to the res folder, then double click on the values folder and double click on the colors.xml  file and add the following lines of codes. This will holder all the colors we will use in this tutorial.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
    <color name="colorBlack">#000000</color>
</resources>

Now, head over to the strings.xml  file and modify the content with the code below.

<resources>
    <string name="app_name">Android Google Play Services Location API</string>
    <string name="latitude">Latitude</string>
    <string name="longitude">Longitude</string>
    <string name="city">Present Location</string>
    <string name="no_text"> </string>
</resources>

Since we are going to use Location API, we will use permission to access the location. The user must grant the request before the app can access the user location.

Open you Mainfest.xml , copy and paste the following code inside the file. The complete code for the Mainfest.xml  file will look like this.

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

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

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

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

</manifest>

The main layout file for the MainActivity class is located in the res/layout folder. Double click on activity_main.xml  file to open it in your IDE.

We are going to add six TextView widgets. Three of them will holder the title information while the remaining three will be set when we access the longitude and latitude values of users location. We will further use the Geocoder class  to obtain the address of the user.

Copy and paste the below code in activity_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal">
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="vertical">
            <TextView
                android:id="@+id/latitude_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/latitude"
                android:textSize="18dp"
                android:textStyle="bold"
                android:textColor="@color/colorBlack"
                android:layout_marginTop="32dp"
                android:layout_gravity="center"/>
            <TextView
                android:id="@+id/latitude"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/no_text"
                android:textColor="@color/colorBlack"
                android:layout_marginTop="16dp"
                android:layout_gravity="center"/>
        </LinearLayout>
        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="vertical">
            <TextView
                android:id="@+id/longitude_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/longitude"
                android:textSize="18dp"
                android:textStyle="bold"
                android:textColor="@color/colorBlack"
                android:layout_marginTop="32dp"
                android:layout_gravity="center"/>
            <TextView
                android:id="@+id/longitude"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/no_text"
                android:textColor="@color/colorBlack"
                android:layout_marginTop="16dp"
                android:layout_gravity="center"/>
        </LinearLayout>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical">
        <TextView
            android:id="@+id/city_location"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/city"
            android:textSize="18dp"
            android:textStyle="bold"
            android:textColor="@color/colorBlack"
            android:layout_marginTop="32dp"
            android:layout_gravity="center_horizontal"/>
        <TextView
            android:id="@+id/city"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/no_text"
            android:textSize="18dp"
            android:textColor="@color/colorBlack"
            android:layout_marginTop="16dp"
            android:layout_gravity="center_horizontal"/>
    </LinearLayout>

</LinearLayout>

As you can see, the layout file is very simple and straight forward. We will now move over to the MainActivity class.

We will going to obtain the instances of the TextView controls that we will populate with latitude and longitude.

In the Mainactivity class, we are going to implement the following interfaces. Add the following line in the class declaration

public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener

We add the GoogleAPiClient as a class variable

private GoogleApiClient mGoogleApiClient;

Instanciate the GoogleAPiClient in the onCreate()  method of the Activity class

if (mGoogleApiClient == null) {
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(LocationServices.API)
            .build();
}

In the onStart()  method, connect the GoogleApiClient  instance to Google Play Services as shown below

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

In onStop() method, the GoogleApiClient is disconnected in order to free resources when the activity is finally destroyed.

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

In the onConnected(Bundle bundle)  callback method which signified that the GoogleApiClient  instance is connected, LocationRequest is send to see if there is any settings that is not enable that the Location service Api might need.

The setResultCallback()  method of the result instance checks if all the setting required is enable, if so the last known location is requested from from the FusedLocationApi class .

Please not that starting from Android Api level 23, that permissions are requested at run-time so we have to always request user permission. The down side of it is that if a user refuse to grant a permission that our application will use, we will find a way to handle it so that it will not crash our application or make it behave in a way that was not intended.

The complete code on this method is shown below.

@Override
public void onConnected(Bundle bundle) {
    mLocationRequest = createLocationRequest();
    builder = new LocationSettingsRequest.Builder().addLocationRequest(mLocationRequest);
    result = LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, builder.build());
    result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
        @Override
        public void onResult(LocationSettingsResult result) {
            final Status status = result.getStatus();
            final LocationSettingsStates mState = result.getLocationSettingsStates();
            switch (status.getStatusCode()) {
                case LocationSettingsStatusCodes.SUCCESS:
                    // All location settings are satisfied. The client can
                    // initialize location requests here.
                    if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                        ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_LOCATION);
                    } else {
                        mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                        if (mLastLocation != null) {
                            latitudePosition.setText(String.valueOf(mLastLocation.getLatitude()));
                            longitudePosition.setText(String.valueOf(mLastLocation.getLongitude()));
                            getAddressFromLocation(mLastLocation, getApplicationContext(), new GeoCoderHandler());
                        }
                    }
                    break;
                case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                    // Location settings are not satisfied, but this can be fixed
                    // by showing the user a dialog.
                    try {
                        // Show the dialog by calling startResolutionForResult(),
                        // and check the result in onActivityResult().
                        status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS);
                    } catch (IntentSender.SendIntentException e) {
                        // Ignore the error.
                    }
                    break;
                case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                    // Location settings are not satisfied. However, we have no way
                    // to fix the settings so we won't show the dialog.
                    break;
            }
        }
    });
}

When the location settings is not satisfied, the line below.

status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS);

call the onActivityResult()  to give the user to option of enabling what is missing. The code snippet is shown below.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case REQUEST_CHECK_SETTINGS:
            switch (resultCode) {
                case Activity.RESULT_OK:
                    if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                        ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_LOCATION);
                    } else {
                        mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                        if (mLastLocation != null) {
                            latitudePosition.setText(String.valueOf(mLastLocation.getLatitude()));
                            longitudePosition.setText(String.valueOf(mLastLocation.getLongitude()));
                            getAddressFromLocation(mLastLocation, getApplicationContext(), new GeoCoderHandler());
                        }
                    }
                    break;
                case Activity.RESULT_CANCELED:
                    // user does not want to update setting. Handle it in a way that it will to affect your app functionality
                    Toast.makeText(MainActivity.this, "User does not update location setting", Toast.LENGTH_LONG).show();
                    break;
            }
            break;
    }
}

The complete complete for the MainActivity class is shown below.

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResult;
import com.google.android.gms.location.LocationSettingsStates;
import com.google.android.gms.location.LocationSettingsStatusCodes;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
    private static final String TAG = "MainActivity";
    private GoogleApiClient mGoogleApiClient;
    private Location mLastLocation;
    private TextView latitudePosition;
    private TextView longitudePosition;
    private TextView currentCity;
    private final int REQUEST_LOCATION = 200;
    private final int REQUEST_CHECK_SETTINGS = 300;
    private LocationRequest mLocationRequest;
    private PendingResult<LocationSettingsResult> result;
    private LocationSettingsRequest.Builder builder;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        latitudePosition = (TextView) findViewById(R.id.latitude);
        longitudePosition = (TextView) findViewById(R.id.longitude);
        currentCity = (TextView) findViewById(R.id.city);
        if (mGoogleApiClient == null) {
            mGoogleApiClient = new GoogleApiClient.Builder(this)
                    .addConnectionCallbacks(this)
                    .addOnConnectionFailedListener(this)
                    .addApi(LocationServices.API)
                    .build();
        }
    }
    @Override
    protected void onStart() {
        mGoogleApiClient.connect();
        super.onStart();
    }
    @Override
    protected void onStop() {
        mGoogleApiClient.disconnect();
        super.onStop();
    }
    @Override
    public void onConnected(Bundle bundle) {
        mLocationRequest = createLocationRequest();
        builder = new LocationSettingsRequest.Builder().addLocationRequest(mLocationRequest);
        result = LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, builder.build());
        result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
            @Override
            public void onResult(LocationSettingsResult result) {
                final Status status = result.getStatus();
                final LocationSettingsStates mState = result.getLocationSettingsStates();
                switch (status.getStatusCode()) {
                    case LocationSettingsStatusCodes.SUCCESS:
                        // All location settings are satisfied. The client can
                        // initialize location requests here.
                        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_LOCATION);
                        } else {
                            mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                            if (mLastLocation != null) {
                                latitudePosition.setText(String.valueOf(mLastLocation.getLatitude()));
                                longitudePosition.setText(String.valueOf(mLastLocation.getLongitude()));
                                getAddressFromLocation(mLastLocation, getApplicationContext(), new GeoCoderHandler());
                            }
                        }
                        break;
                    case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                        // Location settings are not satisfied, but this can be fixed
                        // by showing the user a dialog.
                        try {
                            // Show the dialog by calling startResolutionForResult(),
                            // and check the result in onActivityResult().
                            status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS);
                        } catch (IntentSender.SendIntentException e) {
                            // Ignore the error.
                        }
                        break;
                    case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                        // Location settings are not satisfied. However, we have no way
                        // to fix the settings so we won't show the dialog.
                        break;
                }
            }
        });
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQUEST_CHECK_SETTINGS:
                switch (resultCode) {
                    case Activity.RESULT_OK:
                        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_LOCATION);
                        } else {
                            mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
                            if (mLastLocation != null) {
                                latitudePosition.setText(String.valueOf(mLastLocation.getLatitude()));
                                longitudePosition.setText(String.valueOf(mLastLocation.getLongitude()));
                                getAddressFromLocation(mLastLocation, getApplicationContext(), new GeoCoderHandler());
                            }
                        }
                        break;
                    case Activity.RESULT_CANCELED:
                        // user does not want to update setting. Handle it in a way that it will to affect your app functionality
                        Toast.makeText(MainActivity.this, "User does not update location setting", Toast.LENGTH_LONG).show();
                        break;
                }
                break;
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case REQUEST_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // permission was granted, yay! Do the
                    // contacts-related task you need to do.
                } else {
                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.
                }
                return;
            }
            // other 'case' lines to check for other
            // permissions this app might request
        }
    }
    @Override
    public void onConnectionSuspended(int i) {
    }
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
    }
    @Override
    public void onLocationChanged(Location location) {
        if (mLastLocation != null) {
            latitudePosition.setText(String.valueOf(mLastLocation.getLatitude()));
            longitudePosition.setText(String.valueOf(mLastLocation.getLongitude()));
            getAddressFromLocation(mLastLocation, getApplicationContext(), new GeoCoderHandler());
        }
    }
    public static void getAddressFromLocation(final Location location, final Context context, final Handler handler) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                Geocoder geocoder = new Geocoder(context, Locale.getDefault());
                String result = null;
                try {
                    List<Address> list = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);
                    if (list != null && list.size() > 0) {
                        Address address = list.get(0);
                        // sending back first address line and locality
                        result = address.getAddressLine(0) + ", " + address.getLocality() + ", " + address.getCountryName();
                    }
                } catch (IOException e) {
                    Log.e(TAG, "Impossible to connect to Geocoder", e);
                } finally {
                    Message msg = Message.obtain();
                    msg.setTarget(handler);
                    if (result != null) {
                        msg.what = 1;
                        Bundle bundle = new Bundle();
                        bundle.putString("address", result);
                        msg.setData(bundle);
                    } else
                        msg.what = 0;
                    msg.sendToTarget();
                }
            }
        };
        thread.start();
    }
    private class GeoCoderHandler extends Handler {
        @Override
        public void handleMessage(Message message) {
            String result;
            switch (message.what) {
                case 1:
                    Bundle bundle = message.getData();
                    result = bundle.getString("address");
                    break;
                default:
                    result = null;
            }
            currentCity.setText(result);
        }
    }
    protected LocationRequest createLocationRequest() {
        LocationRequest mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(10000);
        mLocationRequest.setFastestInterval(5000);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        return mLocationRequest;
    }
}

Please note that I did not test this tutorial on device running android 6 but it has been tested from android 5 and below.

Run your application and see for yourself what we have just created.

This brings us to the end of this tutorial, If you find anything confusing kindly contact me with your questions or use the comment box below.

You can download the code for this tutorial below. If you are having hard time downloading the tutorials, kindly contact me.

Remember to subscribe with your email address so that you will be among the first to receive my new post once it is published.

Please if you love this tutorial, kindly download my new android app – Daily Calculator Tool – in Google Play Store and let me know what you think about it.

12 Comments

    • Inducesmile
        • Inducesmile
    • Inducesmile

Add a Comment