Jun 5, 2013

Fused location provider example

src :http://developer.android.com/google/play-services/location.html
Fused location provider beckoned the first radical improvement that Location Management has witnessed in Android. It used to be a very challenging and complex task to develop location aware applications in Android until Fused location provider was introduced. I recommend to read the article "A Deep Drive into location" by +Reto Meier and study these two open source libraries "Andorid protips location" and "little fluffy location library" to understand challenge and complexity

Fused Location provider makes things very easy for the developers.The Fused Location Provider intelligently manages the underlying location technology and gives you the best location according to your needs. It's simple to use, provide immediate location, power efficient and part of Google Play Service.

Fused location provider provide three ways to fetch location

1.  Last Location: Use when you want to know current location once.
2. Request Location using Listener: Use when application is on screen / frontend and require continues location.
3. Request Location using Pending Intent: Use when application in background and require continues location.

Let's develop a simple application which demonstrate all the three methods

 

 

Code Snippet for all three methods

1. Last Location
private LocationClient locationclient;
..
..
locationclient = new LocationClient(this,this,this);
locationclient.connect();
..
..
Location loc =locationclient.getLastLocation();
Log.i(TAG, "Last Known Location :" + loc.getLatitude() + "," + loc.getLongitude());
..
..
locationclient.disconnect();

2. Request location using listener
private LocationClient locationclient;
private LocationRequest locationrequest;
..
..
locationclient = new LocationClient(this,this,this);
locationclient.connect();
..
..
locationrequest = LocationRequest.create();
locationrequest.setInterval(100);
locationclient.requestLocationUpdates(locationrequest, this);
..
..
@Override
public void onLocationChanged(Location location) {
 if(location!=null){
  Log.i(TAG, "Location Request :" + location.getLatitude() + "," + location.getLongitude());
 }
  
}
..
..
locationclient.disconnect();

3. Request location using pendingintent
private LocationClient locationclient;
private LocationRequest locationrequest;
private Intent mIntentService;
private PendingIntent mPendingIntent;
..
..
locationclient = new LocationClient(this,this,this);
locationclient.connect();
// you will receive location update in LocationService
mIntentService = new Intent(this,LocationService.class);
mPendingIntent = PendingIntent.getService(this, 1, mIntentService, 0);
locationrequest = LocationRequest.create();
locationrequest.setInterval(100);
locationclient.requestLocationUpdates(locationrequest, mPendingIntent);
..
..
locationclient.disconnect();

Full Source Code Example

Step 1: Set up Google Play Service in current eclipse workspace, please refer this link for more information : http://developer.android.com/google/play-services/setup.html
Step 2: Create new project in eclipse named "Fused Location"
Step 3: Open activity_main.xml and add following code, activity content main three section for 1. last location 2. location request using listener 3. location request using pendingintent, for 3rd method we need to create one intentservice.
<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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/txtConnectionStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:gravity="center"
        android:text="Connection Status"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/txtLastKnownLoc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/btnLastLoc"
        android:layout_alignRight="@+id/btnLastLoc"
        android:layout_below="@+id/btnLastLoc"
        android:gravity="center"
        android:text="0.0,0.0"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <Button
        android:id="@+id/btnLastLoc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/txtConnectionStatus"
        android:layout_alignRight="@+id/txtConnectionStatus"
        android:layout_below="@+id/txtConnectionStatus"
        android:layout_marginTop="18dp"
        android:onClick="buttonClicked"
        android:text="Fetch Last Location" />

    <Button
        android:id="@+id/btnStartRequest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView1"
        android:layout_alignRight="@+id/txtLastKnownLoc"
        android:layout_below="@+id/etLocationInterval"
        android:onClick="buttonClicked"
        android:text="Start" />

    <TextView
        android:id="@+id/txtLocationRequest"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/btnStartRequest"
        android:layout_alignRight="@+id/btnStartRequest"
        android:layout_below="@+id/btnStartRequest"
        android:gravity="center"
        android:text="0.0,0.0"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <EditText
        android:id="@+id/etLocationInterval"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignRight="@+id/txtLastKnownLoc"
        android:layout_alignTop="@+id/textView1"
        android:ems="3"
        android:inputType="number"
        android:text="100" >
    </EditText>

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/txtLastKnownLoc"
        android:layout_below="@+id/txtLastKnownLoc"
        android:layout_marginTop="51dp"
        android:text="Location Request Listener"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/txtLocationRequest"
        android:layout_alignRight="@+id/txtLocationRequest"
        android:layout_below="@+id/txtLocationRequest"
        android:layout_marginTop="50dp"
        android:text="Request Location PendingIntent"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <Button
        android:id="@+id/btnRequestLocationIntent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textView2"
        android:layout_alignRight="@+id/textView2"
        android:layout_below="@+id/textView2"
        android:onClick="buttonClicked"
        android:text="Start" />

</RelativeLayout>

Step 4: Open MainActivity.java file and add following code.
package com.kpbird.fusedlocation;

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

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;

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

 private String TAG = this.getClass().getSimpleName();
 
 private TextView txtConnectionStatus;
 private TextView txtLastKnownLoc;
 private EditText etLocationInterval;
 private TextView txtLocationRequest;
 
 private LocationClient locationclient;
 private LocationRequest locationrequest;
 private Intent mIntentService;
 private PendingIntent mPendingIntent;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  txtConnectionStatus = (TextView) findViewById(R.id.txtConnectionStatus);
  txtLastKnownLoc = (TextView) findViewById(R.id.txtLastKnownLoc);
  etLocationInterval = (EditText) findViewById(R.id.etLocationInterval);
  txtLocationRequest = (TextView) findViewById(R.id.txtLocationRequest);
  
  mIntentService = new Intent(this,LocationService.class);
  mPendingIntent = PendingIntent.getService(this, 1, mIntentService, 0);
  
  int resp =GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
  if(resp == ConnectionResult.SUCCESS){
   locationclient = new LocationClient(this,this,this);
   locationclient.connect();
  }
  else{
   Toast.makeText(this, "Google Play Service Error " + resp, Toast.LENGTH_LONG).show();
   
  }
  
 }
 public void buttonClicked(View v){
  if(v.getId() == R.id.btnLastLoc){
   if(locationclient!=null && locationclient.isConnected()){
    Location loc =locationclient.getLastLocation();
    Log.i(TAG, "Last Known Location :" + loc.getLatitude() + "," + loc.getLongitude());
    txtLastKnownLoc.setText(loc.getLatitude() + "," + loc.getLongitude()); 
   }
  }
  if(v.getId() == R.id.btnStartRequest){
   if(locationclient!=null && locationclient.isConnected()){
    
    if(((Button)v).getText().equals("Start")){
     locationrequest = LocationRequest.create();
     locationrequest.setInterval(Long.parseLong(etLocationInterval.getText().toString()));
     locationclient.requestLocationUpdates(locationrequest, this);
     ((Button) v).setText("Stop"); 
    }
    else{
     locationclient.removeLocationUpdates(this);
     ((Button) v).setText("Start");
    }
    
   }
  }
  if(v.getId() == R.id.btnRequestLocationIntent){
   if(((Button)v).getText().equals("Start")){
    
    locationrequest = LocationRequest.create();
    locationrequest.setInterval(100);
    locationclient.requestLocationUpdates(locationrequest, mPendingIntent);
    
    ((Button) v).setText("Stop");
   }
   else{
    locationclient.removeLocationUpdates(mPendingIntent);
    ((Button) v).setText("Start");
   }
  }
 }
 
 @Override
 protected void onDestroy() {
  super.onDestroy();
  if(locationclient!=null)
   locationclient.disconnect();
 }

 @Override
 public void onConnected(Bundle connectionHint) {
  Log.i(TAG, "onConnected");
  txtConnectionStatus.setText("Connection Status : Connected");
  
 }

 @Override
 public void onDisconnected() {
  Log.i(TAG, "onDisconnected");
  txtConnectionStatus.setText("Connection Status : Disconnected");
  
 }

 @Override
 public void onConnectionFailed(ConnectionResult result) {
  Log.i(TAG, "onConnectionFailed");
  txtConnectionStatus.setText("Connection Status : Fail");

 }

 @Override
 public void onLocationChanged(Location location) {
  if(location!=null){
   Log.i(TAG, "Location Request :" + location.getLatitude() + "," + location.getLongitude());
   txtLocationRequest.setText(location.getLatitude() + "," + location.getLongitude());
  }
  
 }
}

Step 5 : Create new class for IntentService, named "LocationService", LocationService will receive location from 3rd method and display notification in notification bar.
package com.kpbird.fusedlocation;

import android.app.IntentService;
import android.app.NotificationManager;
import android.content.Intent;
import android.location.Location;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.util.Log;

import com.google.android.gms.location.LocationClient;

public class LocationService extends IntentService {

 private String TAG = this.getClass().getSimpleName();
 public LocationService() {
  super("Fused Location");
 }
 public LocationService(String name) {
  super("Fused Location");
 }
 @Override
 protected void onHandleIntent(Intent intent) {
   
   Location location = intent.getParcelableExtra(LocationClient.KEY_LOCATION_CHANGED);
   if(location !=null){
    Log.i(TAG, "onHandleIntent " + location.getLatitude() + "," + location.getLongitude());
    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 
    Builder noti = new NotificationCompat.Builder(this);
    noti.setContentTitle("Fused Location");
    noti.setContentText(location.getLatitude() + "," + location.getLongitude());
    noti.setSmallIcon(R.drawable.ic_launcher);
    notificationManager.notify(1234, noti.build());
   }
 }

}

Step 6 : Open AndroidManifest.xml and add user permission for location and register LocationService.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kpbird.fusedlocation"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="14" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <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" >
        <activity
            android:name="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="LocationService"></service>
    </application>

</manifest>

Grab full source code from Github : Download
How to use ?
1. clone code from github
2. import google play service in eclipse
3. import my example
4. change google play service library path from project properties->android


15 comments:

  1. Hey! I was wondering how easy to implement the new location APIs. Great example and code. Thank you. And BTW it is indeed very easy.

    ReplyDelete
  2. GiuseppeSorce12:11 AM

    hi keta, nice tutorial. i have a question. is the new fused location best of old location, ok it's very very simple but is it most precise?

    ReplyDelete
  3. kpbird10:48 AM

    Hello,

    Fused location provide methods to change default configuration, It might help you, check following link

    http://developer.android.com/reference/com/google/android/gms/location/LocationRequest.html



    KPBird,

    ReplyDelete
  4. shashi10:04 AM

    Hi Keta ,
    I am getting location null when using getlastlocation and then application is crashing beacuse it is null
    can you explain why it is null

    ReplyDelete
  5. Davy De Waele11:58 AM

    Nice sample ... One important remark about battery drain ... If you use the PendingIntent mechanism and set the LocationRequest to use high accuracy (locationrequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) you'll see it attempting to fetch the location using your GPS. (as expected). However, if you uninstall the demo application, it will still keep polling the GPS, causing a huge battery drain. Only way to stop it from using the GPS is to restart the phone. Are you also experiencing that ? Posted a question on SO regarding it but so far no response : http://stackoverflow.com/questions/16659752/pendingintents-and-google-play-services-how-to-do-cleanup

    ReplyDelete
  6. Disqusserit7:06 AM

    Thanks for a great blog!

    But, i get serious problems when i try to run this code on a Android 2.2 HTC Wildfire.
    The pendingIntent just crashes the application.. Any thoughts?

    ReplyDelete
  7. Anthony8:42 PM

    Thanks for this, very helpful. The android training lesson made it seem more complex than it really is - your explanation was much clearer.

    ReplyDelete
  8. Lucas Rosada2:28 AM

    According to the documentation, it's a condition that rarely happens, but it can happen and your app should be prepared to that. That is common specially in the emulator.

    ReplyDelete
  9. Lucas Rosada2:32 AM

    Hey Ketan awesome sample, really clarifying.


    Just a remark about the code. If you try to use locationClient instance before onConnected is called, you'll run into an exception. In the sample, that'll happen if the user clicks the button (fast enough!).

    ReplyDelete
  10. Arsalan Mehmood6:25 PM

    Hi,

    First of all thanks for writing such a great tutorial....!

    I'm getting a problem that
    Location loc =locationclient.getLastLocation();

    is returning same location but i've traveled more than 5KM. But one thing, only GPS is turned ON when i try to fetch current location...!

    Can you tell me, what can be the problem ?

    ReplyDelete
  11. Thanks Lucas!

    I solved doing this:

    // Get the current location
    Location currentLocation = mLocationClient.getLastLocation();

    if(currentLocation == null){
    mLocationManager.requestSingleUpdate(LocationManager.NETWORK_PROVIDER, this, null);
    currentLocation = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
    // Start the background task
    (new LoginActivity.GetAddressTask(this)).execute(currentLocation);
    }else{
    // Start the background task
    (new LoginActivity.GetAddressTask(this)).execute(currentLocation);

    }

    ReplyDelete
  12. Lucas Rosada11:07 PM

    Hi iker!

    Awesome! Network provider will give you a fast (although gross) fix.

    But, correct me if I'm misunderstanding: what if the network provider doesn't give you a fix fast enough before starting your getAddressTask? I mean, the background task should be started asynchronously when the location is received. Did I understand your code wrong?

    Also, LocationManager is class from the old Android's location framework. If you use Fused Location, you could do as follow (without any "low level" detail, like the provider) :

    LocationClient mClient = [...]; // assuming it has been initialized
    LocationRequest mRequest = LocationRequest
    .create()
    .setNumUpdates(1)
    .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
    mClient.requestLocationUpdates(mRequest, this);



    Also, remember that, in the old location framework, providers not always are available. For instance, the user could unmark "Allow location from network" in the phone settings.


    Hope it helps!

    ReplyDelete
  13. Hi Lucas,
    Good points.
    I use you're approach, you mean to use com.google.android.gms.location.LocationListener, then the callback of mClient.requestLocationUpdates(mRequest, this); goes to:
    @Override
    public void onLocationChanged(Location location) {
    // Hide the loading localization wheel and
    // Start the background task
    }

    Is this what you mean? I tried this, and worked, I will try to do more test.
    Thank you for your tips!
    Iker.

    ReplyDelete
  14. Lucas Rosada8:45 PM

    Exactly, @iker! Not a problem ;)

    ReplyDelete
  15. MohamedHussienElKhateeb3:31 PM

    Mostly getLastLocation(); get null in some devices so i solved this problem by using service to detect location manually

    ReplyDelete