Sunday, February 17, 2019

Android - GPS Location and Address with new google map API in kotlin.

Today, mobile phones are more powerful that offers GPS Location to users in mobile applications. To use GPS location and google map, the Android SDK offers the location API that is updated by the Android SDK development team day by day for optimizing mobile battery life and other aspects. Android location APIs make a location-aware application, without needing to focus on the details of the location technology.


GPS location tracker works with the help of Google Play services, which facilitates adding location awareness to an application with automated location tracking, geofencing, and activity recognition that returns a location object. The location object represents a geographic location which consists of latitude, longitude, timestamp, and other information.

In this post, we’ll show you how can use the GPS location to get the address of a place. The sample app we will build first get latitude and longitude of your location and display address. When you move the camera of google map. It will display the address of another location that is the center of google map. The final output of example will look like below:







So, let's start the development of this sample with the help of following steps.


Creating a new Project
1. Create a new project in Android Studio from File ⇒ New Project and select Basic Activity from templates. I have given my package name as com.devlibs.googlemapaddress.
2. Open app/build.gradle and add the following dependencies.
app/build.gradle
implementation 'pub.devrel:easypermissions:0.2.0' implementation 'com.google.android.gms:play-services-maps:16.0.0' implementation 'com.google.android.gms:play-services-location:16.0.0'
here, we using easypermissions to handle run time google map permission.

3. The next step is to add the permissions in the Android Manifest to request user’s locations. We will use the location permission later when we request for your location. For that, you need to add ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permission. Here, you can see complete required changes in mainfest.xml.
mainfest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.devlibs.googlemapaddress"> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@drawable/develibs" android:label="@string/app_name" android:roundIcon="@drawable/develibs" 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"/> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </activity> <service android:name=".FetchAddressIntentService" android:exported="false"/> <!--...............................Google key......................................--> <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_key"/>//replace your google key here </application> </manifest>
4. Now create an intent service FetchAddressIntentService.kt to get the address by passing latitude and longitude of a location. As you can see above, we have added it in the application manifest.xml file. The definition of this file has explained in the file with the help of yellow color.
FetchAddressIntentService.kt
class FetchAddressIntentService : IntentService(TAG) { /** * The receiver where results are forwarded from this service. */ private var mReceiver: ResultReceiver? = null override fun onHandleIntent(intent: Intent?) { var errorMessage = "" mReceiver = intent!!.getParcelableExtra("RECEIVER") // Check if receiver was properly registered. if (mReceiver == null) { Log.wtf(TAG, "No receiver received. There is nowhere to resend the results.") return } // Get the location passed to this service through an extra. val location = intent.getParcelableExtra<Location>("LOCATION_DATA_EXTRA") // Make sure that the location data was really sent over through an extra. If it wasn't, // resend an error message and return. if (location == null) { errorMessage = getString(R.string.no_location_data_provided) Log.wtf(TAG, errorMessage) deliverResultToReceiver(1, errorMessage) return } val geocoder = Geocoder(this, Locale.getDefault()) // Address found using the Geocoder. var addresses: List<Address>? = null try { // Using getFromLocation() returns an array of Addresses for the area immediately // surrounding the given latitude and longitude. The results are a best guess and are // not guaranteed to be accurate. addresses = geocoder.getFromLocation( location.latitude, location.longitude, // In this sample, we get just a single address. You can change it according to your requirment. 1 ) } catch (ioException: IOException) { errorMessage = "service not available" Log.e(TAG, errorMessage, ioException) } catch (illegalArgumentException: IllegalArgumentException) { errorMessage = "invalid lat long used" } // Handle case where no address was found. if (addresses == null || addresses.isEmpty()) { if (errorMessage.isEmpty()) { errorMessage = "no address found" Log.e(TAG, errorMessage) } deliverResultToReceiver(1, errorMessage) } else { val address = addresses[0] val addressFragments = ArrayList<String>() // Fetch the address lines using {@code getAddressLine}, // join them, and resend them to the thread. The {@link android.location.address} // class provides other options for fetching address details that you may prefer // to use. Here are some examples: // getLocality() ("Mountain View", for example) // getAdminArea() ("CA", for example) // getPostalCode() ("94043", for example) // getCountryCode() ("US", for example) // getCountryName() ("United States", for example) for (i in 0..address.maxAddressLineIndex) { addressFragments.add(address.getAddressLine(i)) } deliverResultToReceiver(0, TextUtils.join(System.getProperty("line.separator"), addressFragments) ) } } /** * Sends a resultCode and message to the receiver. */ private fun deliverResultToReceiver(resultCode: Int, message: String) { val bundle = Bundle() bundle.putString("RESULT_DATA_KEY", message) mReceiver!!.send(resultCode, bundle) } companion object { private val TAG = "FetchAddressIS" } }
as you see above, the deliverResultToReceiver method returning an address of latitude and longitude.

5. Here, we have the main code snippet of this project. We have created GPSTrackerPresenter.kt  file. It is a util class that tracks your location on given param. You can set minimum time and distance. The minimum duration and distance is a type of filter to avoid your location callback functions from being called too frequent.
  • Minimum time of 1000 milliseconds means, after getting a location, you are going to get next location from the LocationManager roughly after 1000 milliseconds.
  • The minimum distance of 1-meter means, after getting a location, you are getting next location after you have moved roughly 1-meter.
These two filters are combined inside the LocationManager in the way you are going to get a location after “one of the” two conditions are filled. 

For example:- If you are standing up and not moving, the new location is obtained after 1 second even though you’ve not moved 1 meter yet. On the other hand, if you are running fast, you are getting a new location after you’ve moved 1 meter even though you’ve moved 1 meter within 0.5 seconds.

Here, we have a complete GPS location tracker class that we have defined inside the class with the help of yellow color.
GPSTrackerPresenter.kt
import android.Manifest import android.app.Activity import android.content.pm.PackageManager import android.location.Location import android.support.v4.app.ActivityCompat import android.util.Log import com.google.android.gms.common.api.ApiException import com.google.android.gms.common.api.ResolvableApiException import com.google.android.gms.location.* import pub.devrel.easypermissions.EasyPermissions class GPSTrackerPresenter(private val mActivity: Activity, private val mListener: LocationListener) { private var mLocationCallback: LocationCallback? = null private var mFusedLocationClient: FusedLocationProviderClient? = null private var mLocationRequest: LocationRequest? = null // initiliaze th class for create instances ans set callback listeners init { try { mFusedLocationClient = LocationServices.getFusedLocationProviderClient(mActivity) mFusedLocationClient!!.lastLocation .addOnSuccessListener(mActivity) { location -> // Got last known location. In some rare situations this can be null. if (location != null) { mListener.onLocationFound(location) } } createLocationRequest() mLocationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult?) { for (location in locationResult!!.locations) { mListener.onLocationFound(location) } } } } catch (ex: SecurityException) { ex.printStackTrace() } catch (e: Exception) { e.printStackTrace() } checkGpsSetting() } //Here, we assigned required minimum and maximum duration that explained above private fun createLocationRequest() { mLocationRequest = LocationRequest.create() mLocationRequest!!.interval = UPDATE_INTERVAL.toLong() mLocationRequest!!.fastestInterval = FASTEST_INTERVAL.toLong() mLocationRequest!!.priority = LocationRequest.PRIORITY_HIGH_ACCURACY mLocationRequest!!.smallestDisplacement = DISPLACEMENT.toFloat() } //In the start method, we chacking permissions. It'will invoke a permision required pop if required permission not given fun onStart() { if (!checkRequiredLocationPermission()) { getLastLocation() } else { startLocationUpdates() } } fun stopLocationUpdates() { mFusedLocationClient!!.removeLocationUpdates(mLocationCallback!!) } fun onPause() { stopLocationUpdates() } private fun checkRequiredLocationPermission(): Boolean { val perms = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION) if (!EasyPermissions.hasPermissions(mActivity, *perms)) { mListener.checkRequiredLocationPermission() return false } else { return true } } /** * Check Gps setting IN/OFF state. If GPS OFF it'll show on popup. */ private fun checkGpsSetting() { val builder = LocationSettingsRequest.Builder() builder.addLocationRequest(mLocationRequest!!) val task = LocationServices.getSettingsClient(mActivity).checkLocationSettings(builder.build()) task.addOnCompleteListener { result -> try { result.getResult(ApiException::class.java) // All location settings are satisfied. The client can initialize location // requests here. } catch (exception: ApiException) { when (exception.statusCode) { LocationSettingsStatusCodes.RESOLUTION_REQUIRED ->{ val resolvable = exception as ResolvableApiException resolvable.startResolutionForResult(mActivity, MainActivity.GPS_REQUEST_CODE) } // Location settings are not satisfied. But could be fixed by showing the // user a dialog. // Cast to a resolvable exception. LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> { //empty } } // Show the dialog by calling startResolutionForResult(), // Location settings are not satisfied. However, we have no way to fix the // settings so we won't show the dialog. } } } private fun getLastLocation() { if (ActivityCompat.checkSelfPermission(mActivity,
 Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(mActivityManifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return } mFusedLocationClient!!.lastLocation .addOnCompleteListener(mActivity) { task -> if (task.isSuccessful && task.result != null) { mListener.onLocationFound(task.result) } else { Log.w("", "no_location_detected", task.exception) } } } private fun startLocationUpdates() { if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( mActivity,Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED) { // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding return } mFusedLocationClient!!.requestLocationUpdates(mLocationRequest, mLocationCallback!!, null) } interface LocationListener { fun onLocationFound(location: Location?) fun locationError(msg: String) fun checkRequiredLocationPermission(): Boolean } companion object { private const val UPDATE_INTERVAL = 600000 // 10 min private const val FASTEST_INTERVAL = 10000 // 5 min private const val DISPLACEMENT = 10 // 1 km const val RUN_TIME_PERMISSION_CODE = 999 } }
6. Now, we are going to create a user interface to display the location that will be returned by the API. Once you get location, we'll start our service to get the address as shown above. Open the layout file of your main activity (activity_main.xml) and add the below code to achieve the layout.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:clickable="true" android:focusable="true" android:layout_weight="1" android:background="@color/colorBlack"> <fragment android:id="@+id/fragment_address_map" android:name="com.google.android.gms.maps.SupportMapFragment" android:layout_width="match_parent" android:layout_height="match_parent"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:contentDescription="@null" app:srcCompat="@drawable/ic_current_location"/> <LinearLayout android:id="@+id/fragment_address_et_address_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:background="@drawable/round_shadow" android:gravity="center" android:orientation="horizontal" android:padding="10dp"> <EditText android:id="@+id/fragment_address_et_address" style="@style/AppEditTextMediumLargeStyle" android:layout_width="0dp" android:layout_weight="1" android:background="@color/colorTransparent" android:cursorVisible="false" android:ellipsize="end" android:focusable="false" android:padding="5dp" android:focusableInTouchMode="false" android:hint="@string/address" android:longClickable="false" android:textColor="@color/colorBlack" android:textColorHint="@color/colorAccent" /> </LinearLayout> <ImageView android:id="@+id/fragment_address_iv_my_location" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_marginBottom="80dp" android:layout_marginEnd="5dp" android:padding="10dp" app:srcCompat="@drawable/ic_current_location_blue" android:contentDescription="@null"/> </FrameLayout>
in the above layout file, we have used google map fragment to display google map and a blue icon. By using this icon click, you can move your current location.
 




7.
Now open MainAcitivty.kt file and write google map initialization code and handle GPS location object to get an address. As shown above.

MainActivity.kt
import android.Manifest import android.app.Activity import android.content.Intent import android.location.Location import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.os.Handler import android.os.ResultReceiver import android.view.View import android.widget.EditText import android.widget.ImageView 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.LatLng import pub.devrel.easypermissions.EasyPermissions class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks, OnMapReadyCallback, GPSTrackerPresenter.LocationListener, GoogleMap.OnCameraIdleListener, View.OnClickListener { private lateinit var ivCurrentPosition: ImageView private lateinit var etAddress: EditText private lateinit var gpsTrackerPresenter: GPSTrackerPresenter private lateinit var mGoogleMap: GoogleMap private var mResultReceiver: AddressResultReceiver? = null private var mPosition: Location? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ivCurrentPosition = findViewById(R.id.fragment_address_iv_my_location) ivCurrentPosition.setOnClickListener(this) mResultReceiver = AddressResultReceiver(Handler()) etAddress = findViewById(R.id.fragment_address_et_address) val smf = supportFragmentManager.findFragmentById(R.id.fragment_address_map) as SupportMapFragment? smf!!.getMapAsync(this) gpsTrackerPresenter = GPSTrackerPresenter(this, this) } override fun onMapReady(googleMap: GoogleMap) { mGoogleMap = googleMap mGoogleMap.animateCamera(CameraUpdateFactory.zoomTo(15f)) mGoogleMap.setOnCameraIdleListener(this) mGoogleMap.uiSettings.isMapToolbarEnabled = false mGoogleMap.uiSettings.isZoomControlsEnabled = false } override fun onLocationFound(location: Location?) { gpsTrackerPresenter.stopLocationUpdates() mPosition = location mGoogleMap.clear() animateToCurrentLocation() } private fun animateToCurrentLocation() { if (mPosition != null) { mGoogleMap.animateCamera( CameraUpdateFactory.newLatLngZoom( LatLng( mPosition!!.latitude, mPosition!!.longitude ), 15.0f ) ) } } override fun checkRequiredLocationPermission(): Boolean { val perms = arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION) if (!EasyPermissions.hasPermissions(this, *perms)) { EasyPermissions.requestPermissions( this, getString(R.string.google_map_permission), GPSTrackerPresenter.RUN_TIME_PERMISSION_CODE, *perms ) return false } else { return true } }
/** * Creates an intent, adds location data to it as an extra, and starts the intent service for * fetching an address. * * @param mLastLocation mLastLocation */ private fun startIntentService(mLastLocation: Location) { // Create an intent for passing to the intent service responsible for fetching the address. val intent = Intent(this, FetchAddressIntentService::class.java) // Pass the result receiver as an extra to the service. intent.putExtra("RECEIVER", mResultReceiver) // Pass the location data as an extra to the service. intent.putExtra("LOCATION_DATA_EXTRA", mLastLocation) // Start the service. If the service isn't already running, it is instantiated and started // (creating a process for it if needed); if it is running then it remains running. The // service kills itself automatically once all intents are processed. startService(intent) } override fun onClick(v: View) { when (v.id) { R.id.fragment_address_iv_my_location -> onMyLocationClick() else -> { //nothing to do } } } override fun onCameraIdle() { if (mPosition == null) { mPosition = Location("") } mPosition!!.latitude = mGoogleMap.cameraPosition.target.latitude mPosition!!.longitude = mGoogleMap.cameraPosition.target.longitude val markerLocation = Location("") markerLocation.latitude = mGoogleMap.cameraPosition.target.latitude markerLocation.longitude = mGoogleMap.cameraPosition.target.longitude startIntentService(markerLocation) } private fun clearCameraMoveListener() { mGoogleMap.setOnCameraMoveListener(null) } override fun locationError(msg: String) { //empty } override fun onStart() { super.onStart() gpsTrackerPresenter.onStart() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == GPS_REQUEST_CODE && resultCode == Activity.RESULT_OK) { gpsTrackerPresenter.onStart() } } override fun onDestroy() { gpsTrackerPresenter.onPause() super.onDestroy() } private fun onMyLocationClick() { ivCurrentPosition.setImageResource(R.drawable.ic_current_location_blue) clearCameraMoveListener() gpsTrackerPresenter.onStart() } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) } override fun onPermissionsGranted(requestCode: Int, perms: List<String>) { gpsTrackerPresenter.onStart() } override fun onPermissionsDenied(requestCode: Int, perms: List<String>) { //permission denied } /** * Receiver for data sent from FetchAddressIntentService. */ inner class AddressResultReceiver internal constructor(handler: Handler) : ResultReceiver(handler) { /** * Receives data sent from FetchAddressIntentService and updates the UI in MainActivity. */ override fun onReceiveResult(resultCode: Int, resultData: Bundle) { etAddress.setText(resultData.getString("RESULT_DATA_KEY")) } } companion object { const val GPS_REQUEST_CODE = 999 } }
 Finally, merge all code and run the application. If our user does not grant permission to access fine location or coarse location, our app will request this permission and enabled their location services for our app.

If you have followed the article carefully, you can see the app running very smoothly as shown in the above. But if you face any problem.

Source Code
 
You can get the working project from the above link and please feel free to ask in the comment section below. 
Share:

Get it on Google Play

React Native - Start Development with Typescript

React Native is a popular framework for building mobile apps for both Android and iOS. It allows developers to write JavaScript code that ca...