Introduction
This android tutorial is about adding Location module in the app. You have already seen that almost all the android app out there detects your current location. These apps provides services according to your current location. The services can range from sending plumber to getting food delivered in user’s location. In this Android tutorial we will learn how to get the last know location of a device (as latitude, longitude). We’ll add location module in Android app using FusedLocationProviderClient provided by latest google play service API’s .
Through Google Play Services SDK, Android has provided a new way to access user location. You can read more about the location services here. This is connection-less API. This new API does not mandate the developer to manually manage a connection to Google Play services through a GoogleApiClient. If you have already implemented location aware app using GoogleApiClient, you must already know that to access the location related services, the app first needs to connect to Google Play services asynchronously. The logic that a developer builds around it is many a times erroneous or error-prone. However, in the new LocationServices API, the connection logic is handled implicitly and automatically by Google. Thus, this makes a developer’s life earlier, code complexity is lesser and lesser error prone.
This blog is first part of location related tutorials. In this tutorial, we will keep it very simple. We’ll just get the last known location of the device or user. It’ll retrieve the most recently available location and at times, it may be null as well in those rarest of rare cases when there is no location available or location is disabled in device. This tutorial (as a by product) will also show how we can check for user permission in android app.
So, let’s get cracking folks!!
App Screenshot
I have erased the location intentionally, so that you don’t know where I am coding from 🙂
Tech Stack
1 2 3 |
Android Studio 3.0.1 JAVA for Android programming Google Play Services 11.8.0 |
Basic Steps for implementing location aware app
Well, that’s it. Let’s get started with the development.
Create an empty project
- Launch Android Studio and click: File –> New –> New Project…
- A “Create New Project” dialog box will open; we’ll provide the app name and company domain. I have entered following details, you can provide the name/domain as per your choice and preference.
- Application Name:- ItcGoogleFusedLocationSample
- Company Domain:- iteritory.com
- Click on Next button. In the next window, select the minimum SDK version; you can choose as per your preference. In this case, I have chosen API 16: Android 4.1 (Jelly Bean). Click Next button.
- In the next window, select “Empty Activity“. Click Next button.
- In the next window, let’s keep the Activity Name and Layout Name to default. Click Finish button.
- I’ll change the app name a bit here, traverse and open res–> values –> strings.xml. Find the line with app_name attribute. Change the value to “Last Location ”.
Prerequisite
- Ensure that your android studio is updated to the latest and also you have latest google play service installed. Please check the below screenshot for reference. You can open the SDK manager from the studio to check the relevant details –
- In the app level build.gradle file, add a dependency for play service –> compile ‘com.google.android.gms:play-services-location:11.0.0’. Post modification, the build.gradle file looks like –
123456789101112131415161718192021222324252627282930apply plugin: 'com.android.application'android {compileSdkVersion 26defaultConfig {applicationId "com.iteritory.itcgooglefusedlocationsample"minSdkVersion 16targetSdkVersion 26versionCode 1versionName "1.0"testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])implementation 'com.android.support:appcompat-v7:26.1.0'implementation 'com.android.support.constraint:constraint-layout:1.0.2'testImplementation 'junit:junit:4.12'androidTestImplementation 'com.android.support.test:runner:1.0.1'androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'compile 'com.android.support:design:26.1.0'compile 'com.google.android.gms:play-services-location:11.0.0'} - Next, open the AndroidManifest.xml and followings –
- Add a meta-data section for the google play service
- Add user-permission section for “ACCESS_FINE_LOCATION”. With the ACCESS_FINE_LOCATION setting is specified, the Fused Location Provider API returns location updates that are accurate to within a few feet.
- Post modification AndroidManifest.xml looks like –
123456789101112131415161718192021222324252627<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.iteritory.itcgooglefusedlocationsample"><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/><applicationandroid: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"><meta-dataandroid:name="com.google.android.gms.version"android:value="@integer/google_play_services_version" /><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>
Build the UI
Next, we will build a very simple UI for displaying the latitude, longitude of the user’s current or last known location. This UI will be loaded with the last known device location on app startup. After putting in necessary objects, the activity_main.xml is as follows –
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_activity_container" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="16dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:paddingTop="16dp"> <TextView android:id="@+id/txtLatitude" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginStart="10dp" android:textIsSelectable="true" android:textSize="20sp" /> <TextView android:id="@+id/txtLongitude" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginStart="10dp" android:layout_marginTop="24dp" android:textIsSelectable="true" android:textSize="20sp" /> </LinearLayout> |
Develop the code
- Now, we will start with developing the code. For the sake of simplicity, we will write all the codes in the MainActivity class itself
- Declare required variables for holding location information and initialize them in the onCreate function –
12345678910111213141516171819202122private static final String TAG = "@@@@@#######::";private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 111;private FusedLocationProviderClient mFusedLocationClient;private Location mLastLocation;private String mLatitudeLabel;private String mLongitudeLabel;private TextView mLatitudeText;private TextView mLongitudeText;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mLatitudeLabel = getResources().getString(R.string.latitude_label);mLongitudeLabel = getResources().getString(R.string.longitude_label);mLatitudeText =findViewById(R.id.txtLatitude);mLongitudeText = findViewById(R.id.txtLongitude);mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);}
- Write functions to check if the the app has necessary permission to access location. If the permission is unavailable, request permission from user for the app. Write a function that will actually start the permission requesting dialog.
1234567891011121314151617181920212223242526272829303132//Return whether permissions is needed as boolean value.private boolean checkPermissions() {int permissionState = ActivityCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION);return permissionState == PackageManager.PERMISSION_GRANTED;}//Request permission from userprivate void requestPermissions() {Log.i(TAG, "Inside requestPermissions function");boolean shouldProvideRationale =ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.ACCESS_FINE_LOCATION);//Log an additional rationale to the user. This would happen if the user denied the//request previously, but didn't check the "Don't ask again" checkbox.// In case you want, you can also show snackbar. Here, we used Log just to clear the concept.if (shouldProvideRationale) {Log.i(TAG, "****Inside requestPermissions function when shouldProvideRationale = true");startLocationPermissionRequest();} else {Log.i(TAG, "****Inside requestPermissions function when shouldProvideRationale = false");startLocationPermissionRequest();}}//Start the permission request dialogprivate void startLocationPermissionRequest() {ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.ACCESS_FINE_LOCATION},REQUEST_PERMISSIONS_REQUEST_CODE);}
- Write a function to get the current or last known location. In some situations, it’s possible that it returns null when location is not available or when the location is not enabled.
1234567891011121314151617181920212223/*** This method should be called after location permission is granted. It gets the recently available location,* In some situations, when location, is not available, it may produce null result.* WE used SuppressWarnings annotation to avoid the missing permission warnng. You can comment the annotation* and check the behaviour yourself.*/@SuppressWarnings("MissingPermission")private void getLastLocation() {mFusedLocationClient.getLastLocation().addOnCompleteListener(this, new OnCompleteListener<Location>() {@Overridepublic void onComplete(@NonNull Task<Location> task) {if (task.isSuccessful() && task.getResult() != null) {mLastLocation = task.getResult();mLatitudeText.setText(String.format(Locale.ENGLISH, "%s: %f", mLatitudeLabel, mLastLocation.getLatitude()));mLongitudeText.setText(String.format(Locale.ENGLISH, "%s: %f",mLongitudeLabel, mLastLocation.getLongitude()));} else {Log.i(TAG, "Inside getLocation function. Error while getting location");System.out.println(TAG+task.getException());}}});}
- Stitching all the components together, the MainActivity class looks like –
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134package com.iteritory.itcgooglefusedlocationsample;import android.Manifest;import android.content.pm.PackageManager;import android.location.Location;import android.os.Bundle;import android.support.annotation.NonNull;import android.support.v4.app.ActivityCompat;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.widget.TextView;import com.google.android.gms.location.FusedLocationProviderClient;import com.google.android.gms.location.LocationServices;import com.google.android.gms.tasks.OnCompleteListener;import com.google.android.gms.tasks.Task;import java.util.Locale;public class MainActivity extends AppCompatActivity {private static final String TAG = "@@@@@#######::";private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 111;private FusedLocationProviderClient mFusedLocationClient;private Location mLastLocation;private String mLatitudeLabel;private String mLongitudeLabel;private TextView mLatitudeText;private TextView mLongitudeText;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mLatitudeLabel = getResources().getString(R.string.latitude_label);mLongitudeLabel = getResources().getString(R.string.longitude_label);mLatitudeText =findViewById(R.id.txtLatitude);mLongitudeText = findViewById(R.id.txtLongitude);mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);}@Overridepublic void onStart() {super.onStart();if (!checkPermissions()) {Log.i(TAG, "Inside onStart function; requesting permission when permission is not available");requestPermissions();} else {Log.i(TAG, "Inside onStart function; getting location when permission is already available");getLastLocation();}}//Return whether permissions is needed as boolean value.private boolean checkPermissions() {int permissionState = ActivityCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION);return permissionState == PackageManager.PERMISSION_GRANTED;}//Request permission from userprivate void requestPermissions() {Log.i(TAG, "Inside requestPermissions function");boolean shouldProvideRationale =ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.ACCESS_FINE_LOCATION);//Log an additional rationale to the user. This would happen if the user denied the//request previously, but didn't check the "Don't ask again" checkbox.// In case you want, you can also show snackbar. Here, we used Log just to clear the concept.if (shouldProvideRationale) {Log.i(TAG, "****Inside requestPermissions function when shouldProvideRationale = true");startLocationPermissionRequest();} else {Log.i(TAG, "****Inside requestPermissions function when shouldProvideRationale = false");startLocationPermissionRequest();}}//Start the permission request dialogprivate void startLocationPermissionRequest() {ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.ACCESS_FINE_LOCATION},REQUEST_PERMISSIONS_REQUEST_CODE);}/*** Callback to the following function is received when a permissions request has been completed.*/@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {if (grantResults.length <= 0) {// user interaction is cancelled; in such case we will receive empty grantResults[]//In such case, just record/log it.Log.i(TAG, "User interaction has been cancelled.");} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {// Permission is granted by the user.Log.i(TAG, "User permission has been given. Now getting location");getLastLocation();} else {// Permission is denied by the user.Log.i(TAG, "User denied permission.");}}}/*** This method should be called after location permission is granted. It gets the recently available location,* In some situations, when location, is not available, it may produce null result.* WE used SuppressWarnings annotation to avoid the missing permission warnng. You can comment the annotation* and check the behaviour yourself.*/@SuppressWarnings("MissingPermission")private void getLastLocation() {mFusedLocationClient.getLastLocation().addOnCompleteListener(this, new OnCompleteListener<Location>() {@Overridepublic void onComplete(@NonNull Task<Location> task) {if (task.isSuccessful() && task.getResult() != null) {mLastLocation = task.getResult();mLatitudeText.setText(String.format(Locale.ENGLISH, "%s: %f", mLatitudeLabel, mLastLocation.getLatitude()));mLongitudeText.setText(String.format(Locale.ENGLISH, "%s: %f",mLongitudeLabel, mLastLocation.getLongitude()));} else {Log.i(TAG, "Inside getLocation function. Error while getting location");System.out.println(TAG+task.getException());}}});}}
- I have provided Log statement in every step; this is to make sure we understand the workflow. When you run the app in your device from android studio, open the Logcat pen and notice how the statements are printed. That’s exactly the workflow of the app in that specific situation. For example, when I ran this app with location enabled, I got following workflow –
123402-06 01:03:43.919 17426-17426/? I/@@@@@#######::: Inside onStart function; requesting permission when permission is not available02-06 01:03:43.919 17426-17426/? I/@@@@@#######::: Inside requestPermissions function02-06 01:03:43.921 17426-17426/? I/@@@@@#######::: ****Inside requestPermissions function when shouldProvideRationale = false02-06 01:03:47.193 17426-17426/com.iteritory.itcgooglefusedlocationsample I/@@@@@#######::: User permission has been given. Now getting location
Github Link
The project can be downloaded from this github page.
Conclusion
In this tutorial, we learn a very simple way to get the last known location of a user. In the subsequent tutorial, we will see how to fetch the current location and how we keep getting updated location. See you soon with new learning folks. 🙂