Android Real Time Location Tutorial

These days, our smartphones are used in numerous ways. For instance, your Android device can easily be used as a location tracker as well by installing the right kind of apps.You can see, the location module widely used in lot of apps those provides services like food ordering, transportation, health tracking, social networking and lot more. If you also wish to perform Android real time location tracking, then you have come to the right place. In this post, we will teach you how to track the real-time location on an Android device.

Android Realtime Location Tutorial

1. Introduction

Earlier, getting location is very easy with couple of API calls. But to provide more accurate locations and optimizing the battery usage, Android introduced set APIs that should be combined to get the best results from the location API. We will be using Fused Location API that combines signals from GPSWi-Fi, and cell networks, as well as accelerometergyroscopemagnetometer and other sensors to provide more accurate results.

1.1 Location Permissions

There are two permissions available to request location. The accuracy of the location is determined by the kind of permission requested and priority level.

  • ACCESS_COARSE_LOCATION: Gives location approximately equivalent to a city block.
  • ACCESS_FINE_LOCATION: Gives precise location, sometimes in few meters or feet when combined with High Priority accuracy.

1.2 Receiving Location Updates

  • getLastLocation(): Returns the recent available location. When location is not available, it returns null.
  • Location Settings: In order to get the location, proper settings has to enabled in the device such as GPS or Wifi. Instead of requesting the user to enable them separately, you can use Settings Client to check whether proper settings are enabled or not. If enabled, you can proceed with location updates or user will be shown a dialog to turn on the required hardware as shown below.

android-location-settings-dialog

  • Update Interval: This interval defines the rate in milliseconds at which your app prefers the location updates. Your app can receive updates lesser or higher than this rate if other apps requested location updates higher than your value. Let’s say your app requests updates every 10secs, if other app is requesting updates at 5secs, your app might receives the same updates ignoring the 10sec value.
  • Fastest Update Interval: This is the rate at which your app can handle the location updates. Without this value, you can see inconsistent user experience if your app can’t handle frequent location updates.
  • Priority: The accuracy of the location depends on the source of the hardware used. To define this, Priority has to be mentioned while requesting the location. The priority can be BALANCEDHIGHLOWOR NO_POWER.

1.3 Example App

Here is the demo app we are going to implement in this article. The app receives the location updates when it is opened and updates will be stopped when app goes to background.

 

android realtime location

2. Creating New Project

1. Create a new project in Android Studio from File ⇒ New Project and select Basic Activity from templates.

2. Open res/colors.xml and add the below string resources.

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

3. Open res/styles.xml and change DarkActionBar to NoActionBar.

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">

4. Open res/drawable folder and create new drawable file name bg_gradient.xml and add bellow this code.

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

    <gradient
        android:angle="45"
        android:endColor="@color/bg_gradient_end"
        android:startColor="@color/bg_gradient_start"
        android:type="linear" />
</shape>

5. Add ACCESS_FINE_LOCATION permission to your AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.andrious.androidrealtimelocation">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        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>

6. Open app/build.gradle and add location play service dependency. We also need Dexter (Runtime Permissions) and ButterKnife (View Binding) libraries.

implementation 'com.android.support:design:27.1.1'
// location play services
implementation 'com.google.android.gms:play-services-location:15.0.1'

// dexter runtime permissions
implementation 'com.karumi:dexter:4.2.0'

// ButterKnife view binding
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

7. Open the layout file of main activity activity_main.xml and add the below code.

<?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="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_gradient"
    android:padding="20dp"
    tools:context=".MainActivity">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="@color/white"
        android:textSize="25dp"
        android:textStyle="bold"
        android:text="Android Realtime Location"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:gravity="center"
        android:orientation="vertical"
        android:layout_centerInParent="true">
        <TextView
            android:id="@+id/lat"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/white"
            android:textSize="18dp" />

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

        <TextView
            android:id="@+id/updated_on"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:textColor="@color/white"
            android:textSize="11dp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:orientation="vertical"
        android:gravity="center"
        android:layout_alignParentBottom="true">
        <Button
            android:id="@+id/btn_start_location_updates"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="START" />

        <Button
            android:id="@+id/btn_stop_location_updates"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:enabled="false"
            android:text="STOP" />

        <Button
            android:id="@+id/btn_get_last_location"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="GET LAST LOCATION" />
    </LinearLayout>
</RelativeLayout>

8. Open MainActivity.java and add the below code. Initially the code might look heavy but with couple of observations you can understand it easily.

  • First we initialize all the location related clients such as FusedLocationProviderClientLocationRequestLocationSettingsRequestLocationCallback and SettingsClient in onCreate() method.
  • While initializing, we define the interval setInterval(), fastest interval setFastestInterval() and priority setPriority() on location request.
  • Dexter is used to request the location permission before performing any location related operations.
  • startLocationUpdates() requests for location updates. First, it checks whether the location settings are eanbled and once satisfied, the updates will be requested. Here SettingsClient is used to check for settings configuration.
  • The location updates will be received in LocationCallback and proper UI action is taken place. If you really want to the location only once, you can call stopLocationUpdates() method immediately after receiving the first location update.
  • The location updates are paused and resume in onPause() and onResume() method to save the batter power.
package com.andrious.androidrealtimelocation;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.Looper;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResponse;
import com.google.android.gms.location.LocationSettingsStatusCodes;
import com.google.android.gms.location.SettingsClient;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.single.PermissionListener;
import java.text.DateFormat;
import java.util.Date;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;


public class MainActivity extends AppCompatActivity {

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

    TextView latitude;
    TextView longitude;
    TextView txtUpdatedOn;
    Button btnStartUpdates;
    Button btnStopUpdates;

    // location last updated time
    private String mLastUpdateTime;

    // location updates interval - 10sec
    private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 10000;

    // fastest updates interval - 5 sec
    // location updates will be received if another app is requesting the locations
    // than your app can handle
    private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = 5000;
    private static final int REQUEST_CHECK_SETTINGS = 100;

    // bunch of location related apis
    private FusedLocationProviderClient mFusedLocationClient;
    private SettingsClient mSettingsClient;
    private LocationRequest mLocationRequest;
    private LocationSettingsRequest mLocationSettingsRequest;
    private LocationCallback mLocationCallback;
    private Location mCurrentLocation;

    // boolean flag to toggle the ui
    private Boolean mRequestingLocationUpdates;

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

        latitude=findViewById(R.id.lat);
        longitude=findViewById(R.id.lon);
        txtUpdatedOn=findViewById(R.id.updated_on);
        btnStartUpdates=findViewById(R.id.btn_start_location_updates);
        btnStopUpdates=findViewById(R.id.btn_stop_location_updates);

        ButterKnife.bind(this);

        // initialize the necessary libraries
        init();

        // restore the values from saved instance state
        restoreValuesFromBundle(savedInstanceState);
    }

    private void init() {
        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
        mSettingsClient = LocationServices.getSettingsClient(this);

        mLocationCallback = new LocationCallback() {
            @Override
            public void onLocationResult(LocationResult locationResult) {
                super.onLocationResult(locationResult);
                // location is received
                mCurrentLocation = locationResult.getLastLocation();
                mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());

                updateLocationUI();
            }
        };

        mRequestingLocationUpdates = false;

        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
        builder.addLocationRequest(mLocationRequest);
        mLocationSettingsRequest = builder.build();
    }

    /**
     * Restoring values from saved instance state
     */
    private void restoreValuesFromBundle(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            if (savedInstanceState.containsKey("is_requesting_updates")) {
                mRequestingLocationUpdates = savedInstanceState.getBoolean("is_requesting_updates");
            }

            if (savedInstanceState.containsKey("last_known_location")) {
                mCurrentLocation = savedInstanceState.getParcelable("last_known_location");
            }

            if (savedInstanceState.containsKey("last_updated_on")) {
                mLastUpdateTime = savedInstanceState.getString("last_updated_on");
            }
        }

        updateLocationUI();
    }


    /**
     * Update the UI displaying the location data
     * and toggling the buttons
     */
    private void updateLocationUI() {
        if (mCurrentLocation != null) {

            latitude.setText("Latitude: " + mCurrentLocation.getLatitude());
            longitude.setText("Longitude: " + mCurrentLocation.getLongitude());

            // giving a blink animation on TextView
            latitude.setAlpha(0);
            longitude.setAlpha(0);
            latitude.animate().alpha(1).setDuration(300);
            longitude.animate().alpha(1).setDuration(300);

            // location last updated time
            txtUpdatedOn.setText("Last updated on: " + mLastUpdateTime);
        }

        toggleButtons();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean("is_requesting_updates", mRequestingLocationUpdates);
        outState.putParcelable("last_known_location", mCurrentLocation);
        outState.putString("last_updated_on", mLastUpdateTime);

    }

    private void toggleButtons() {
        if (mRequestingLocationUpdates) {
            btnStartUpdates.setEnabled(false);
            btnStopUpdates.setEnabled(true);
        } else {
            btnStartUpdates.setEnabled(true);
            btnStopUpdates.setEnabled(false);
        }
    }

    /**
     * Starting location updates
     * Check whether location settings are satisfied and then
     * location updates will be requested
     */
    private void startLocationUpdates() {
        mSettingsClient
                .checkLocationSettings(mLocationSettingsRequest)
                .addOnSuccessListener(this, new OnSuccessListener<LocationSettingsResponse>() {
                    @SuppressLint("MissingPermission")
                    @Override
                    public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
                        Log.i(TAG, "All location settings are satisfied.");

                        Toast.makeText(getApplicationContext(), "Started location updates!", Toast.LENGTH_SHORT).show();

                        //noinspection MissingPermission
                        mFusedLocationClient.requestLocationUpdates(mLocationRequest,
                                mLocationCallback, Looper.myLooper());

                        updateLocationUI();
                    }
                })
                .addOnFailureListener(this, new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        int statusCode = ((ApiException) e).getStatusCode();
                        switch (statusCode) {
                            case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
                                Log.i(TAG, "Location settings are not satisfied. Attempting to upgrade " +
                                        "location settings ");
                                try {
                                    // Show the dialog by calling startResolutionForResult(), and check the
                                    // result in onActivityResult().
                                    ResolvableApiException rae = (ResolvableApiException) e;
                                    rae.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS);
                                } catch (IntentSender.SendIntentException sie) {
                                    Log.i(TAG, "PendingIntent unable to execute request.");
                                }
                                break;
                            case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
                                String errorMessage = "Location settings are inadequate, and cannot be " +
                                        "fixed here. Fix in Settings.";
                                Log.e(TAG, errorMessage);

                                Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show();
                        }

                        updateLocationUI();
                    }
                });
    }

    @OnClick(R.id.btn_start_location_updates)
    public void startLocationButtonClick() {
        // Requesting ACCESS_FINE_LOCATION using Dexter library
        Dexter.withActivity(this)
                .withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
                .withListener(new PermissionListener() {
                    @Override
                    public void onPermissionGranted(PermissionGrantedResponse response) {
                        mRequestingLocationUpdates = true;
                        startLocationUpdates();
                    }

                    @Override
                    public void onPermissionDenied(PermissionDeniedResponse response) {
                        if (response.isPermanentlyDenied()) {
                            // open device settings when the permission is
                            // denied permanently
                            openSettings();
                        }
                    }

                    @Override
                    public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
                        token.continuePermissionRequest();
                    }
                }).check();
    }

    @OnClick(R.id.btn_stop_location_updates)
    public void stopLocationButtonClick() {
        mRequestingLocationUpdates = false;
        stopLocationUpdates();
    }

    public void stopLocationUpdates() {
        // Removing location updates
        mFusedLocationClient
                .removeLocationUpdates(mLocationCallback)
                .addOnCompleteListener(this, new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {
                        Toast.makeText(getApplicationContext(), "Location updates stopped!", Toast.LENGTH_SHORT).show();
                        toggleButtons();
                    }
                });
    }

    @OnClick(R.id.btn_get_last_location)
    public void showLastKnownLocation() {
        if (mCurrentLocation != null) {
            Toast.makeText(getApplicationContext(), "Latitude: " + mCurrentLocation.getLatitude()
                    + "\nLongitude: " + mCurrentLocation.getLongitude(), Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(getApplicationContext(), "Last known location is not available!", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            // Check for the integer request code originally supplied to startResolutionForResult().
            case REQUEST_CHECK_SETTINGS:
                switch (resultCode) {
                    case Activity.RESULT_OK:
                        Log.e(TAG, "User agreed to make required location settings changes.");
                        // Nothing to do. startLocationupdates() gets called in onResume again.
                        break;
                    case Activity.RESULT_CANCELED:
                        Log.e(TAG, "User chose not to make required location settings changes.");
                        mRequestingLocationUpdates = false;
                        break;
                }
                break;
        }
    }

    private void openSettings() {
        Intent intent = new Intent();
        intent.setAction(
                Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        Uri uri = Uri.fromParts("package",
                BuildConfig.APPLICATION_ID, null);
        intent.setData(uri);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }

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

        // Resuming location updates depending on button state and
        // allowed permissions
        if (mRequestingLocationUpdates && checkPermissions()) {
            startLocationUpdates();
        }

        updateLocationUI();
    }

    private boolean checkPermissions() {
        int permissionState = ActivityCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION);
        return permissionState == PackageManager.PERMISSION_GRANTED;
    }


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

        if (mRequestingLocationUpdates) {
            // pausing location updates
            stopLocationUpdates();
        }
    }
}

Run the app in emulator or on a real device and enjoy….

 

 

 

 

4 Comments

  1. Shannon Geidner

    August 30, 2018 at 2:10 pm

    With thanks! Valuable information!

  2. furtdsolinopv

    October 4, 2018 at 3:12 pm

    I would like to thnkx for the efforts you’ve put in writing this web site. I’m hoping the same high-grade web site post from you in the upcoming as well. Actually your creative writing skills has encouraged me to get my own web site now. Really the blogging is spreading its wings rapidly. Your write up is a good example of it.

  3. Dwight Walthour

    February 15, 2019 at 3:02 am

    Do you mind if I quote a couple of your articles as long as I provide credit and sources back to your weblog? My website is in the exact same niche as yours and my users would definitely benefit from a lot of the information you present here. Please let me know if this alright with you. Cheers!

  4. buy best private proxies

    March 20, 2019 at 6:41 am

    I have realized some new points from your web-site about desktops. Another thing I’ve always believed is that computer systems have become something that each family must have for many reasons. They provide convenient ways in which to organize the home, pay bills, shop, study, tune in to music and in many cases watch tv series. An innovative strategy to complete many of these tasks is by using a notebook. These computer systems are mobile ones, small, highly effective and mobile.

Leave a Reply