Many applications now days use location to offer services to their users. Examples include many taxi ride apps like Uber. The location of the user is useful to display location features or to show directions. If you want to create an app that uses a client’s location then you are in luck. I am going to show you how to get the user’s current location and display it on a map. You can also use this free geocoding api in your Development projects.
The source code for this guide can be found on AndroidCurrentUserLocation from GitHub. First, get Google maps API key from this by following instructions from their page. After getting the key and enabling billing, you have to enable maps API with any additional APIs you might need like Places API.
Create your project from Android Studio. Navigate to gradle.properties
and add this line at the end, ensure you have added your API key.
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
ApiKeyMap="Insert Google Maps Key Here"
Open build.gradle(Module: app) file and these dependencies.
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.neveropen.trucksend"
minSdkVersion 16
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
buildConfigField 'String', "ApiKeyMap", ApiKeyMap
resValue 'string', "api_key_map", ApiKeyMap
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField 'String', "ApiKeyMap", ApiKeyMap
resValue 'string', "api_key_map", ApiKeyMap
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.0.0'
implementation 'com.google.android.gms:play-services-location:17.0.0'
implementation 'com.google.android.gms:play-services-maps:17.0.0'
implementation "com.karumi:dexter:5.0.0"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
Click on Sync Now at the top right to sync the dependencies.
Open your manifest file and add the following:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.neveropen.trucksend">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<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"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/api_key_map" />
</application>
</manifest>
Open your activity and start adding the required functions:
private lateinit var googleMap: GoogleMap
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
mapFragment!!.getMapAsync(this)
fusedLocationProviderClient = FusedLocationProviderClient(this)
}
We initiate the Google map and we use the FusedLocationProviderClient to fetch the user’s current location or last known location. Note that my activity will now extend the onMapReady(). This is necessary so that the activity knows when Google maps is ready to display.
override fun onMapReady(map: GoogleMap?) {
googleMap = map?: return
}
We have to ask the user for permission to access their location. We will use the Dexter permissions library to do this. First, we check if the user has given permission to location.
private fun isPermissionGiven(): Boolean{
return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
}
If permission is not granted we call the method givePermission
to ask for permission.
private fun givePermission() {
Dexter.withActivity(this)
.withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
.withListener(this)
.check()
}
After permissions have been granted, call the getCurrentLocation
function that uses the FusedLocationProviderClient to fetch the current user location or last known location. We use locationRequest
to refresh the current user location at some interval for high accuracy so that we are always updated.
private fun getCurrentLocation() {
val locationRequest = LocationRequest()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = (10 * 1000).toLong()
locationRequest.fastestInterval = 2000
val builder = LocationSettingsRequest.Builder()
builder.addLocationRequest(locationRequest)
val locationSettingsRequest = builder.build()
val result = LocationServices.getSettingsClient(this).checkLocationSettings(locationSettingsRequest)
result.addOnCompleteListener { task ->
try {
val response = task.getResult(ApiException::class.java)
if (response!!.locationSettingsStates.isLocationPresent){
getLastLocation()
}
} catch (exception: ApiException) {
when (exception.statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try {
val resolvable = exception as ResolvableApiException
resolvable.startResolutionForResult(this, REQUEST_CHECK_SETTINGS)
} catch (e: IntentSender.SendIntentException) {
} catch (e: ClassCastException) {
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> { }
}
}
}
}
Geocoder Bonus
As a bonus, I have added a Gecoder method to try getting the address for the location given! I have also added a custom pin marker.
private fun getLastLocation() {
fusedLocationProviderClient.lastLocation
.addOnCompleteListener(this) { task ->
if (task.isSuccessful && task.result != null) {
val mLastLocation = task.result
var address = "No known address"
val gcd = Geocoder(this, Locale.getDefault())
val addresses: List<Address>
try {
addresses = gcd.getFromLocation(mLastLocation!!.latitude, mLastLocation.longitude, 1)
if (addresses.isNotEmpty()) {
address = addresses[0].getAddressLine(0)
}
} catch (e: IOException) {
e.printStackTrace()
}
val icon = BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource(this.resources, R.drawable.ic_pickup))
googleMap.addMarker(
MarkerOptions()
.position(LatLng(mLastLocation!!.latitude, mLastLocation.longitude))
.title("Current Location")
.snippet(address)
.icon(icon)
)
val cameraPosition = CameraPosition.Builder()
.target(LatLng(mLastLocation.latitude, mLastLocation.longitude))
.zoom(17f)
.build()
googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
} else {
Toast.makeText(this, "No current location found", Toast.LENGTH_LONG).show()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CHECK_SETTINGS -> {
if (resultCode == Activity.RESULT_OK) {
getCurrentLocation()
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
The final activity looks like this:
package com.neveropen.trucksend
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.IntentSender
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.location.Address
import android.location.Geocoder
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.core.app.ActivityCompat
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.*
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions
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 kotlinx.android.synthetic.main.activity_main.*
import java.io.IOException
import java.util.*
class MainActivity : AppCompatActivity(), OnMapReadyCallback, PermissionListener {
companion object {
const val REQUEST_CHECK_SETTINGS = 43
}
private lateinit var googleMap: GoogleMap
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
mapFragment!!.getMapAsync(this)
fusedLocationProviderClient = FusedLocationProviderClient(this)
}
override fun onMapReady(map: GoogleMap?) {
googleMap = map?: return
if (isPermissionGiven()){
googleMap.isMyLocationEnabled = true
googleMap.uiSettings.isMyLocationButtonEnabled = true
googleMap.uiSettings.isZoomControlsEnabled = true
getCurrentLocation()
} else {
givePermission()
}
}
private fun isPermissionGiven(): Boolean{
return ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
}
private fun givePermission() {
Dexter.withActivity(this)
.withPermission(Manifest.permission.ACCESS_FINE_LOCATION)
.withListener(this)
.check()
}
override fun onPermissionGranted(response: PermissionGrantedResponse?) {
getCurrentLocation()
}
override fun onPermissionRationaleShouldBeShown(
permission: PermissionRequest?,
token: PermissionToken?
) {
token!!.continuePermissionRequest()
}
override fun onPermissionDenied(response: PermissionDeniedResponse?) {
Toast.makeText(this, "Permission required for showing location", Toast.LENGTH_LONG).show()
finish()
}
private fun getCurrentLocation() {
val locationRequest = LocationRequest()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = (10 * 1000).toLong()
locationRequest.fastestInterval = 2000
val builder = LocationSettingsRequest.Builder()
builder.addLocationRequest(locationRequest)
val locationSettingsRequest = builder.build()
val result = LocationServices.getSettingsClient(this).checkLocationSettings(locationSettingsRequest)
result.addOnCompleteListener { task ->
try {
val response = task.getResult(ApiException::class.java)
if (response!!.locationSettingsStates.isLocationPresent){
getLastLocation()
}
} catch (exception: ApiException) {
when (exception.statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try {
val resolvable = exception as ResolvableApiException
resolvable.startResolutionForResult(this, REQUEST_CHECK_SETTINGS)
} catch (e: IntentSender.SendIntentException) {
} catch (e: ClassCastException) {
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> { }
}
}
}
}
private fun getLastLocation() {
fusedLocationProviderClient.lastLocation
.addOnCompleteListener(this) { task ->
if (task.isSuccessful && task.result != null) {
val mLastLocation = task.result
var address = "No known address"
val gcd = Geocoder(this, Locale.getDefault())
val addresses: List<Address>
try {
addresses = gcd.getFromLocation(mLastLocation!!.latitude, mLastLocation.longitude, 1)
if (addresses.isNotEmpty()) {
address = addresses[0].getAddressLine(0)
}
} catch (e: IOException) {
e.printStackTrace()
}
val icon = BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeResource(this.resources, R.drawable.ic_pickup))
googleMap.addMarker(
MarkerOptions()
.position(LatLng(mLastLocation!!.latitude, mLastLocation.longitude))
.title("Current Location")
.snippet(address)
.icon(icon)
)
val cameraPosition = CameraPosition.Builder()
.target(LatLng(mLastLocation.latitude, mLastLocation.longitude))
.zoom(17f)
.build()
googleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition))
} else {
Toast.makeText(this, "No current location found", Toast.LENGTH_LONG).show()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CHECK_SETTINGS -> {
if (resultCode == Activity.RESULT_OK) {
getCurrentLocation()
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
}
Great! Now run your app to see your current location.
Find the source code for this AndroidCurrentUserLocation from GitHub. Thanks!