Map, Location update and AutoComplete Places - Kotlin
Let's see how to show current location in Maps and also show the location which is chosen from AutoComplete in Maps with the updated Google play services. That is, for location listening we gonna use FusedLocationProviderClient and for autocomplete places, we gonna use, GeoDataClient with AutoCompletePrediction, i.e., no need to call places url from our end, Google Playservices will do that work for you. Okiee, Let's start now.
You can find the project in Github.
You can find the project in Github.
PreRequisite:
Navigate to https://developers.google.com/maps/documentation/android-api/signup and do as per the instructions to start.Manifest:
Include your API key, which you got from previous step in AndroidManifest.xml
<meta-data android:name="com.google.android.geo.API_KEY" android:value="AIzXXXXXXXXXX_XXXXXXXXXXXXXX" />
Gradle:
Include the following in app-level build 's dependencies.implementation 'com.google.android.gms:play-services-maps:11.8.0' implementation 'com.google.android.gms:play-services-places:11.8.0' implementation 'com.google.android.gms:play-services-location:11.8.0'
Drawables:
search.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" > <path android:pathData="M15.5,14l-0.8,0l-0.3,-0.3c1,-1.1 1.6,-2.6 1.6,-4.2C16,5.9 13.1,3 9.5,3C5.9,3 3, 5.9 3,9.5S5.9,16 9.5,16c1.6,0 3.1,-0.6 4.2,-1.6l0.3,0.3l0,0.8l5,5l1.5,-1.5L15.5,14zM9.5,14C7,14 5, 12 5,9.5S7,5 9.5,5C12,5 14,7 14,9.5S12,14 9.5,14z" android:fillColor="#8e8e8e"/> </vector>
cancel.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0" > <path android:pathData="M19,6.41L17.59,5,12,10.59,6.41,5,5,6.41,10.59,12,5,17.59,6.41,19,12, 13.41,17.59,19,19,17.59,13.41,12z" android:fillColor="#8e8e8e"/> </vector>
Design:
search_place.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout 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"> <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/map" android:name="com.google.android.gms.maps.SupportMapFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <RelativeLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_margin="15dp" android:background="@drawable/rounded_edit" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"> <AutoCompleteTextView android:id="@+id/enter_place" android:layout_width="match_parent" android:layout_height="35dp" android:background="@null" android:drawableLeft="@drawable/search" android:drawablePadding="5dp" android:hint="@string/place_autocomplete_search_hint" android:lines="1" android:paddingLeft="7dp" android:paddingRight="7dp" /> <android.support.v7.widget.AppCompatImageView android:id="@+id/cancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="7dp" android:visibility="gone" android:src="@drawable/cancel" /> </RelativeLayout> </android.support.constraint.ConstraintLayout>
Code:
SearchPlacesActivity.kt
First, we have to initialize map. Then we have to check for Location permission to get the last accessed location, if present and updated location to set Marker at the particular co-ordinates. on Map. LocationSettingsRequest , mLocationSettingsRequest - will take care of checking whether the GPS has been enabled or not. If it's not enabled, then a dialog will be shown to enable it. LocationRequest mLocationRequest, is to create location update request with desired priority and update intervals. LocationCallback mLocationCallback, should be created to receive the location updates. Then FusedLocationProviderClient mFusedLocationClient should request location updates for the declared LocationCallback and LocationRequest. We have completed Map and location updates part.
Let's proceed with Places AutoComplete now. For that, we have to create an Adapter first, with Filtering operation (See PlacesAdapter.kt). Then we have to set that adapter for AutoCompleteTextView. While typing in that, related places will be displayed as dropdown and it can be clicked by the User. After clicked by the user, GeoDataClient, mGeoDataClient - is to get detail of that place and then to move the marker of the map to its co-ordinates.
import android.Manifest import android.content.Context import android.content.IntentSender import android.content.pm.PackageManager import android.location.Location import android.os.Bundle import android.os.Looper import android.support.v4.app.ActivityCompat import android.support.v7.app.AppCompatActivity import android.text.Editable import android.text.TextWatcher import android.util.Log import android.view.View import android.view.inputmethod.InputMethodManager 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.* import com.google.android.gms.location.places.GeoDataClient import com.google.android.gms.location.places.PlaceBufferResponse import com.google.android.gms.location.places.Places import com.google.android.gms.maps.* import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.LatLngBounds import com.google.android.gms.maps.model.MarkerOptions 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 kotlinx.android.synthetic.main.search_place.* /** * Created by developer on 2/2/18. */ class SearchPlacesActivity : AppCompatActivity(), OnMapReadyCallback { var googleMap: GoogleMap? = null lateinit var placesAdapter: PlacesAdapter lateinit var latLng: LatLng lateinit var mLocationRequest: LocationRequest lateinit var mFusedLocationClient: FusedLocationProviderClient lateinit var mLocationCallback: LocationCallback lateinit var mGeoDataClient: GeoDataClient lateinit var mSettingsClient: SettingsClient lateinit var mLocationSettingsRequest: LocationSettingsRequest private val REQUEST_CHECK_SETTINGS = 0x1 var isAutoCompleteLocation = false lateinit var location: Location val REQUEST_LOCATION = 1011 var selectedLocation = "" val BOUNDS_INDIA = LatLngBounds(LatLng(23.63936, 68.14712), LatLng(28.20453, 97.34466)) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.search_place) MapsInitializer.initialize(this) val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this) mGeoDataClient = Places.getGeoDataClient(this, null); mLocationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult?) { val loc = locationResult!!.lastLocation if(!isAutoCompleteLocation) { location = loc latLng = LatLng(location.latitude, location.longitude) assignToMap() } } } mLocationRequest = LocationRequest.create() .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) .setInterval((10 * 1000).toLong()) // 10 seconds, in milliseconds .setFastestInterval((6 * 1000).toLong()) // 1 second, in milliseconds mSettingsClient = LocationServices.getSettingsClient(this) val builder = LocationSettingsRequest.Builder(); builder.addLocationRequest(mLocationRequest) mLocationSettingsRequest = builder.build() placesAdapter = PlacesAdapter(this, android.R.layout.simple_list_item_1, mGeoDataClient, null, BOUNDS_INDIA) enter_place.setAdapter(placesAdapter) enter_place.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(s: Editable?) { } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if (count > 0) { cancel.visibility = View.VISIBLE } else { cancel.visibility = View.GONE } } }) enter_place.setOnItemClickListener({ parent, view, position, id -> //getLatLong(placesAdapter.getPlace(position)) hideKeyboard() val item = placesAdapter.getItem(position) val placeId = item?.getPlaceId() val primaryText = item?.getPrimaryText(null) Log.i("Autocomplete", "Autocomplete item selected: " + primaryText) val placeResult = mGeoDataClient.getPlaceById(placeId) placeResult.addOnCompleteListener(object : OnCompleteListener<PlaceBufferResponse> { override fun onComplete(task: Task<PlaceBufferResponse>) { val places = task.getResult(); val place = places.get(0) isAutoCompleteLocation = true latLng = place.latLng assignToMap() places.release() } }) Toast.makeText(applicationContext, "Clicked: " + primaryText, Toast.LENGTH_SHORT).show() }) cancel.setOnClickListener { enter_place.setText("") } } private fun assignToMap() { googleMap?.clear() val options = MarkerOptions() .position(latLng) .title("My Location") googleMap?.apply { addMarker(options) moveCamera(CameraUpdateFactory.newLatLng(latLng)) animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f)) } } private fun getLastLocation() { try { mFusedLocationClient.getLastLocation()?.addOnCompleteListener { task -> if (task.isSuccessful && task.result != null) { location = task.getResult() latLng = LatLng(location.latitude, location.longitude) assignToMap() } else { Log.w("Location", "Failed to get location.") } } } catch (unlikely: SecurityException) { Log.e("Location", "Lost location permission." + unlikely) } } private fun initLocation() { try { mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this@SearchPlacesActivity) getLastLocation() try { mSettingsClient.checkLocationSettings(mLocationSettingsRequest) .addOnSuccessListener(this, object : OnSuccessListener<LocationSettingsResponse> { override fun onSuccess(p0: LocationSettingsResponse?) { mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper()); } }).addOnFailureListener(this, object : OnFailureListener { override fun onFailure(p0: java.lang.Exception) { val statusCode = (p0 as ApiException).getStatusCode(); when (statusCode) { LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> { Log.i("Location", "Location settings are not satisfied. Attempting to upgrade " + "location settings "); try { // Show the dialog by calling startResolutionForResult(), and check the // result in onActivityResult(). val rae = p0 as ResolvableApiException rae.startResolutionForResult(this@SearchPlacesActivity, REQUEST_CHECK_SETTINGS); } catch (sie: IntentSender.SendIntentException) { Log.i("Location", "PendingIntent unable to execute request."); } } LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> Toast.makeText(this@SearchPlacesActivity, "Location settings are inadequate, and cannot be \"+\n" + " \"fixed here. Fix in Settings.", Toast.LENGTH_LONG).show(); } } }) } catch (unlikely: SecurityException) { Log.e("Location", "Lost location permission. Could not request updates. " + unlikely) } } catch (e: SecurityException) { e.printStackTrace() } catch (e: Exception) { e.printStackTrace() } } override fun onMapReady(p0: GoogleMap?) { Log.v("googleMap", "googleMap==" + googleMap) googleMap = p0 googleMap?.setMapType(GoogleMap.MAP_TYPE_NORMAL) googleMap?.getUiSettings()?.apply { isZoomControlsEnabled = false isCompassEnabled = true isMyLocationButtonEnabled = true } } /* To hide Keyboard */ fun hideKeyboard() { try { val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager inputMethodManager.hideSoftInputFromWindow(currentFocus!!.windowToken, 0) } catch (e: Exception) { e.printStackTrace() } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_LOCATION) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { initLocation() } else { Toast.makeText(this@SearchPlacesActivity, R.string.permission_denied, Toast.LENGTH_SHORT).show() } } } private fun checkPermissions(): Boolean { val permissionState = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) return permissionState == PackageManager.PERMISSION_GRANTED } private fun requestPermissions() { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_LOCATION) } override fun onResume() { super.onResume() if (checkPermissions()) { initLocation() } else { requestPermissions(); } } }
PlacesAdapter.kt
import android.content.Context import android.util.Log import android.view.View import android.view.ViewGroup import android.widget.* import com.google.android.gms.common.data.DataBufferUtils import com.google.android.gms.location.places.AutocompleteFilter import com.google.android.gms.location.places.AutocompletePrediction import com.google.android.gms.location.places.GeoDataClient import com.google.android.gms.maps.model.LatLngBounds import com.google.android.gms.tasks.RuntimeExecutionException import com.google.android.gms.tasks.Tasks import java.util.concurrent.ExecutionException import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException /** * Created by Yamini on 2/2/18. */ class PlacesAdapter(context: Context, resourceId: Int, geoData: GeoDataClient, filter: AutocompleteFilter?, boundS_GREATER_SYDNEY: LatLngBounds) : ArrayAdapter<AutocompletePrediction>(context, resourceId), Filterable { var resultList: MutableList<AutocompletePrediction> = ArrayList<AutocompletePrediction>() private val TAG = "PlaceAutoAdapter" val mContext = context val bounds = boundS_GREATER_SYDNEY val geoDataClient = geoData val mPlaceFilter = filter override fun getCount(): Int { return resultList.size } override fun getItem(position: Int): AutocompletePrediction? { return resultList.get(position) } override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { val row = super.getView(position, convertView, parent) val item = getItem(position) val textView1 = row.findViewById<View>(android.R.id.text1) as TextView textView1.text = item?.getFullText(null) return row } override fun getFilter(): Filter { return object : Filter() { override fun performFiltering(constraint: CharSequence?): Filter.FilterResults { val results = Filter.FilterResults() // We need a separate list to store the results, since // this is run asynchronously. var filterData: ArrayList<AutocompletePrediction>? = ArrayList() // Skip the autocomplete query if no constraints are given. if (constraint != null) { // Query the autocomplete API for the (constraint) search string. filterData = getAutocomplete(constraint) } results.values = filterData if (filterData != null) { results.count = filterData.size } else { results.count = 0 } return results } override fun publishResults(constraint: CharSequence?, results: Filter.FilterResults?) { Log.v("results", "results==" + results); if (results != null && results.count > 0) { // The API returned at least one result, update the data. resultList = results.values as ArrayList<AutocompletePrediction> Log.v("resultList", "resultList==" + resultList); notifyDataSetChanged() } else { // The API did not return any results, invalidate the data set. notifyDataSetInvalidated() } } override fun convertResultToString(resultValue: Any): CharSequence { // Override this method to display a readable result in the AutocompleteTextView // when clicked. return if (resultValue is AutocompletePrediction) { resultValue.getFullText(null) } else { super.convertResultToString(resultValue) } } } } private fun getAutocomplete(constraint: CharSequence): ArrayList<AutocompletePrediction>? { Log.i(TAG, "Starting autocomplete query for:" + constraint) // Submit the query to the autocomplete API and retrieve a PendingResult that will // contain the results when the query completes. val results = geoDataClient.getAutocompletePredictions(constraint.toString(), bounds, mPlaceFilter) // This method should have been called off the main UI thread. Block and wait for at most // 60s for a result from the API. try { Tasks.await(results, 60, TimeUnit.SECONDS) } catch (e: ExecutionException) { e.printStackTrace() } catch (e: InterruptedException) { e.printStackTrace() } catch (e: TimeoutException) { e.printStackTrace() } try { val autocompletePredictions = results.getResult() Log.i(TAG, "Query completed. Received " + autocompletePredictions.getCount() + " predictions.") // Freeze the results immutable representation that can be stored safely. return DataBufferUtils.freezeAndClose<AutocompletePrediction, AutocompletePrediction>(autocompletePredictions) } catch (e: RuntimeExecutionException) { // If the query did not complete successfully return null Toast.makeText(mContext, "Error contacting API: " + e.toString(), Toast.LENGTH_SHORT).show() Log.e(TAG, "Error getting autocomplete prediction API call", e) return null } } }
Run Application:
Now the app is ready to launch. Output will be displayed as follows:
Can you post this Whole Project on Github that will be Much Helpfull
ReplyDeleteHi Ronak, You can find the whole project in https://github.com/yamzzzz/ExploreMapLocation
Deletei am very new to android and I have been getting an error . the google map doesn't show as well as the results...can u plz help me with this?.
ReplyDeletenvm I was able to solve it....thank you anyway.
DeleteHi I have this error
ReplyDeletecom.google.android.gms.tasks.RuntimeExecutionException: com.google.android.gms.common.api.ApiException: 13: ERROR