I'm making an application in Android (with Kotlin) that requires Google Maps services. When a button is pressed, it should locate me. However it is not accurate at all.
I use this version of Google Maps:
implementation 'com.google.android.gms:play-services-maps:18.0.2'
implementation 'com.google.android.gms:play-services-location:20.0.0'
Another versions:
Android Studio Chipmunk 2021.2.1 Patch 1
Tested with: Xiaomi Redmi Note 7 and Xiaomi Litte 9
And this is the code when I press the button:
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.*
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private lateinit var mMap: GoogleMap
private var coordinatesUbication: LatLng? = null
private fun getMyUbication() {
if (isGPSPermissions() && isGPSActive()) {
mMap.isMyLocationEnabled = true
mMap.uiSettings.isMyLocationButtonEnabled = false
// 1) Get latitude and longitude of the current position.
fusedLocationProviderClient.lastLocation.addOnCompleteListener { task ->
val location = task.result
if (location != null) {
// 2) Get coordinates.
val coordinates: LatLng? = LatLng(location.latitude, location.longitude)
this#MapsFragment.coordinatesUbication = coordinates
// 3) Set camera position.
CoroutineScope(Dispatchers.IO).launch {
cameraPosition(coordinatesUbication, Constants.CAMERA_ZOOM)
}
}
}
} else if (!isGPSPermissions()) {
myUbication = true
showDialogPermissions()
} else {
myUbication = true
}
}
Related
I am lost on how to make certain properties from a data class (a Place to rent) in Kotlin with InfoWindow of each Marker on Google Maps. Can you please show a step-by-step procedure and possibly code with layout.xml to show how to show Place attributes on the Marker InfoWindow?
Places.kt
package com.example.myapplication.model
import com.google.android.gms.maps.model.LatLng
data class Place(
val name: String,
val latLng: LatLng,
//val address: LatLng,
val price: Int
)
MapsActivity.kt
package com.example.myapplication
//import android.util.Log
//import android.widget.Button
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.example.myapplication.databinding.ActivityMapsBinding
import com.example.myapplication.model.Place
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.*
import java.util.*
class MapsActivity : AppCompatActivity(), GoogleMap.OnMarkerClickListener, OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var binding: ActivityMapsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen()
var name ="Kaleab Tekle"
var latLng= LatLng(9.005401, 38.763611)
var price = 1500
val place = Place( name, latLng, price)
binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
//binds activity_main.xml textview_id and apartment_id
binding.textViewId
//binding.apartmentId
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment =supportFragmentManager.findFragmentById(R.id.map) as? SupportMapFragment
mapFragment?.getMapAsync{ googleMap ->
addMarkers(googleMap)
}
}
//* This callback is triggered when the map is ready to be useia.
/**
* If Google Play services is not installed on the device, the user will be prompted to install
* it inside the SupportMapFragment. This method will only be triggered once the user has
* installed Google Play services and returned to the app.d.
* This is where we can add markers or lines, add listeners or move the camera. In this case,
* we just add a marker near Sydney, Austral
*/
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
val zoomlevel = 15f
val markerArrayList= ArrayList<Marker>()
// with empty "HashMap of <marker, Data>"
var hashMap : HashMap<Marker?, data> = HashMap<Marker?, data> ()
//add markers with related information
for(data in markerArrayList){
val marker = mMap . addMarker (MarkerOptions().position(data.position.latitude,data.position.longitude)
)
hashMap.put(marker, data)
}
mMap.setOnInfoWindowClickListener { marker ->
val data: Data? = hashMap[marker]
if (data != null) {
val intent = Intent(mContext, MapsActivity::class.java)
intent.putExtra(YourActivity.EXTRA_MESSAGE, data)
mContext.startActivity(intent)
}
}
mMap.let {
it.setOnMapClickListener {
//Add LatLng to Array
if (markerArrayList.size > 0) {
val markerToRemove = markerArrayList.get(0)
//remove the marker from the list
markerArrayList.remove(markerToRemove)
//remove the marker from the map
markerToRemove.remove()
}
//Marker Options
val markerOptions = MarkerOptions().position(it).draggable(true)
val currentMarker = mMap.addMarker(markerOptions)
//Add current marker to arrayList
if (currentMarker != null) {
markerArrayList.add(currentMarker)
//set default icon
currentMarker.setIcon(
BitmapDescriptorFactory.defaultMarker(
BitmapDescriptorFactory.HUE_GREEN
)
)
}
//val clickedPointLoc =(it)
//geocode LatLng to Address
//val address =Geocoder(this)
//val geocoder = Geocoder(this, Locale.getDefault())
//val addresses: List<Address> = geocoder.getFromLocation(it.latitude, it.longitude, 1)
//Log.i(addresses?.toString())
}
}
// Add a marker in Sydney and move the camera
val addisababa = LatLng(9.005401, 38.763611)
mMap.addMarker(MarkerOptions().position(addisababa).title("Marker in Addis Ababa"))
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(addisababa, zoomlevel))
}
}
/**
* Adds marker representations of the places list on the provided GoogleMap object
*/
private fun addMarkers(googleMap: GoogleMap) {
places.forEach { place ->
val marker = googleMap.addMarker(
MarkerOptions()
.title(place.name)
.position(place.latLng)
)
}
}
}
I have finished the product I was developing, but currently, we track the users ( passenger ) location as well as the drivers too slowly.
This is the code I use to track and update the map with the passangers/ drivers icon as it moves :
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.os.Looper
import android.util.Log
import androidx.core.content.ContextCompat
import com.google.android.gms.location.*
import mobi.audax.tupi.motorista.bin.task.GeoDecodeTask
import mobi.audax.tupi.passageiro.util.Prefs
class IntermitentLocationThread(val context: Context, val onLocationUpdate: (location: Location?) -> Unit) : LocationListener {
private var UPDATE_INTERVAL = (1000 * 10).toLong() // 10 segundos de intervalo
private val MAX_WAIT_TIME = UPDATE_INTERVAL * 2 // 20 segundos
private var bestLocation: Location? = null
fun requestLocation() {
this.locationService()
}
private fun locationService() {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
val locationRequest = LocationRequest.create()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.fastestInterval = 1000
locationRequest.interval = UPDATE_INTERVAL
locationRequest.maxWaitTime = MAX_WAIT_TIME
locationRequest.smallestDisplacement = 15f
val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
fusedLocationProviderClient.lastLocation.addOnSuccessListener { location -> onLocationChanged(location) }
fusedLocationProviderClient.requestLocationUpdates(locationRequest, object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
onLocationChanged(locationResult.lastLocation)
}
}, Looper.myLooper()!!)
}
}
override fun onLocationChanged(location: Location) {
try {
Log.v("IntermitentLocationThread", "onLocationChanged")
if (location != null) {
val commons = LocationCommons()
// if (!commons.isMock(context, location) && commons.isBetterLocation(location, bestLocation)) {
Log.v("IntermitentLocationThread", "isBetter true")
val prefs = Prefs(context)
prefs.latitude = location.latitude.toFloat()
prefs.longitude = location.longitude.toFloat()
prefs.precisao = location.accuracy
prefs.velocidade = location.speed * 3.6f
prefs.bearing = location.bearing
if (location.extras.containsKey("satellites")) {
prefs.satellites = location.extras.getInt("satellites")
}
GeoDecodeTask(context, location).decoder { }
bestLocation = location
onLocationUpdate(bestLocation)
} else {
Log.v("IntermitentLocationThread", "isBetter false")
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
and heres how i implement it in my Activity:
private void handleLocationUpdates() {
if (isLocationEnabled()) {
loadMapScene();
IntermitentLocationThread thread = new IntermitentLocationThread(this, location -> {
Log.e(TAG, "handleLocationUpdates: "+"pegado localização" );
clearPassageiroMapMarker();
addPassageiroMarker(new GeoCoordinates(location.getLatitude(),
location.getLongitude()), R.drawable.ic_passageiro);
passageiro.setLat(location.getLatitude());
passageiro.setLongitude(location.getLongitude());
SharedPreferences.Editor editor = this.getSharedPreferences(Constantss.PREFERENCES, MODE_PRIVATE).edit();
mapView.getCamera().lookAt(new GeoCoordinates(passageiro.getLat(), passageiro.getLongitude()));
return null;
});
thread.requestLocation();
} else {
Toast.makeText(this, "Por favor" + "ative sua localização...", Toast.LENGTH_LONG).show();
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
}
at the moment, I get passengers/drivers' locations in a very weird and odd consistency, it's not as " fluid " as uber does it. right now, my marker jumps from one point to another (because I clear the marker list and set another one in place, still working on having only one marker in a heremaps map) in a range of 5 to 15 seconds and everybody else on the internet seems to use this google engine to track one's location.
How is it possible to track users' location in a faster / smoother way?
i was able to find a better/fast tracking by using Google's Location Manager...
yes.. LocationManager.
The code i was using before that wasnt really accurate at all, with Googles Location Manager i was able to set a specific timer and keep it accurate.
More on : https://developer.android.com/reference/android/location/LocationManager
After I updated my phone to Android 12, my app (TargetSDKVersion 29) stopped to get location updates. So I have to update the app to API 31.
For this I want to write some instrumented tests for the location updates. I started with the simple test for requestLocationUpdates(). But in this simple test I don't get any location update.
Just in case I also adjusted the timestamps to 5s intervals to simulate some time between the updates - but no luck.
package de.leo.android.buddytracker_lib
import android.content.Context
import android.location.*
import android.os.Looper
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
private const val MOCK_PROVIDER = "MockLocationProvider"
private const val LOG_TAG ="**** Test ****"
class LocationHandlerAndroidTests : LocationListener {
private lateinit var context: Context
private lateinit var locationManager: LocationManager
private var locationUpdateCount = 0
private var currentLocation: Location? = null
override fun onLocationChanged(location: Location) {
locationUpdateCount++
currentLocation = location
}
#Before
fun init() {
context = InstrumentationRegistry.getInstrumentation().context
locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager.addTestProvider(
MOCK_PROVIDER,
false,
false,
false,
false,
false,
false,
false,
Criteria.POWER_LOW,
Criteria.ACCURACY_FINE
)
locationManager.setTestProviderEnabled(MOCK_PROVIDER, true)
assertTrue(locationManager.isLocationEnabled)
locationUpdateCount = 0
currentLocation = null
Thread.sleep(5000L)
}
#After
fun tearDown() {
locationManager.removeTestProvider(MOCK_PROVIDER)
}
#Test
fun requestLocationUpdatesTest() {
Log.d(LOG_TAG, "Requesting Location Updates")
locationManager.requestLocationUpdates(MOCK_PROVIDER, 4000, 10.0f, this, Looper.getMainLooper())
val location1 = Location(MOCK_PROVIDER)
location1.latitude = 10.0
location1.longitude = 20.0
location1.accuracy = 2.0f
location1.time = 1000
location1.elapsedRealtimeNanos = 10000000000
Log.d(LOG_TAG, "set location 1")
locationManager.setTestProviderLocation(MOCK_PROVIDER, location1)
Thread.sleep(5000L)
val location2 = Location(MOCK_PROVIDER)
location2.latitude = 11.0
location2.longitude = 21.0
location2.accuracy = 2.0f
location2.time = 5000
location2.elapsedRealtimeNanos = 15000000000
Log.d(LOG_TAG, "set location 2")
locationManager.setTestProviderLocation(MOCK_PROVIDER, location2)
Thread.sleep(5000L)
// Check if your listener reacted the right way
assertEquals("Got location updates", 2, locationUpdateCount)
assertEquals(11.0, currentLocation?.latitude)
assertEquals(21.0, currentLocation?.longitude)
}
}
Any tips what I made wrong here?
I am trying to add multiple GeoJsonLayer run time my requirement is on responce of api i have to highlight multiple countries in map which is working fine but i am not getting click event multiple layer i know only single layer click is only suppored by android but is ther a any way to add multiple layer and get separate click event of all event
this is my code
import android.location.Address
import android.location.Geocoder
import android.os.AsyncTask
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
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 com.google.gson.Gson
import com.google.maps.android.data.geojson.GeoJsonLayer
import com.google.maps.android.data.geojson.GeoJsonPolygonStyle
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.net.URL
import java.util.*
class MapsNewActivity : AppCompatActivity(), OnMapReadyCallback {
private val TAG = javaClass.simpleName
private lateinit var mMap: GoogleMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the camera. In this case,
* we just add a marker near Sydney, Australia.
* If Google Play services is not installed on the device, the user will be prompted to install
* it inside the SupportMapFragment. This method will only be triggered once the user has
* installed Google Play services and returned to the app.
*/
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
mMap.setOnMapClickListener {
val geocoder = Geocoder(this, Locale.getDefault())
val addresses: List<Address> = geocoder.getFromLocation(it.latitude, it.longitude, 1)
if (addresses.isNotEmpty()) {
val country: String? = addresses[0].getCountryName()
Log.e(TAG, "country is ${country.toString()}")
Toast.makeText(this, country.toString(), Toast.LENGTH_SHORT).show()
}
}
mMap.uiSettings.isZoomControlsEnabled = true
val madrid = LatLng(40.416775, -3.70379)
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(madrid, 3f))
retrieveFileFromUrl()
}
private fun retrieveFileFromUrl() {
DownloadGeoJsonFile().execute("https://raw.githubusercontent.com/xomena-so/so41431384/master/app/src/main/res/raw/es_geojson.json")
DownloadGeoJsonFile().execute("https://raw.githubusercontent.com/xomena-so/so41431384/master/app/src/main/res/raw/us_geojson.json")
}
inner class DownloadGeoJsonFile : AsyncTask<String?, Void?, GeoJsonLayer?>() {
override fun doInBackground(vararg params: String?): GeoJsonLayer? {
try {
// Open a stream from the URL
val stream = URL(params[0]).openStream()
var line: String?
val result = StringBuilder()
val reader = BufferedReader(InputStreamReader(stream))
while (reader.readLine().also { line = it } != null) {
// Read and save each line of the stream
result.append(line)
}
// Close the stream
reader.close()
stream.close()
return GeoJsonLayer(mMap, JSONObject(result.toString()))
// return JSONObject(result.toString())
} catch (e: IOException) {
Log.e(TAG, "GeoJSON file could not be read")
} catch (e: JSONException) {
Log.e(TAG, "GeoJSON file could not be converted to a JSONObject")
}
return null
}
override fun onPostExecute(layer: GeoJsonLayer?) {
layer?.let { addGeoJsonLayerToMap(it) }
}
}
private fun addGeoJsonLayerToMap(layer: GeoJsonLayer) {
val style: GeoJsonPolygonStyle = layer.defaultPolygonStyle
style.fillColor = R.color.map_highlight
style.strokeColor = R.color.map_highlight_border
style.strokeWidth = 1f
layer.addLayerToMap()
layer.setOnFeatureClickListener {
Log.e(TAG, "layer click ${it.getProperty("title")}")
Log.e(TAG, "layer click Data = = = ${Gson().toJson(it)}")
Toast.makeText(
this,
"Feature clicked: " + it.getProperty("title"),
Toast.LENGTH_SHORT
).show()
}
}
}
this is what I am getting
I am also getting a country name in toast on click of any other country but not getting a country name or click event in setOnFeatureClickListener or setOnMapClickListener on click of the highlighted part
so how do I get data on click of highlighted part in map?
Please help me how do I achieve this
Any help would be highly appreciated.
this is not the best solution but if you disable the click of the layer then you will get an event in setOnMapClickListener
so I added this to get click event style.isClickable = false this will pervert the layer click event
val style: GeoJsonPolygonStyle = layer.defaultPolygonStyle
style.fillColor = R.color.map_highlight
style.strokeColor = R.color.map_highlight_border
style.strokeWidth = 1f
style.isClickable = false // this will pervert setOnFeatureClickListener so mMap.setOnMapClickListener work
layer.addLayerToMap()
you will receieve click event in this part
mMap.setOnMapClickListener {
val geocoder = Geocoder(this, Locale.getDefault())
val addresses: List<Address> = geocoder.getFromLocation(it.latitude, it.longitude, 1)
if (addresses.isNotEmpty()) {
val country: String? = addresses[0].getCountryName()
Log.e(TAG, "country is ${country.toString()}")
Toast.makeText(this, country.toString(), Toast.LENGTH_SHORT).show()
}
}
this is working for me please do correct me if I am doing some wrong
I've written a unit test that use a mock location provider. The test passes when a location update is received, or fails when it times out.
The test passes fine running on emulated Pixel 3's, Android M through Android O. It times out on Android P and on Q. I also tested on a physical Pixel 3 with Q, still fails.
I've been beating my head against this for a while and can't figure out what's going on.
Test:
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.location.Location
import android.location.LocationManager
import androidx.test.rule.GrantPermissionRule
import design.inhale.androidtestutils.InstrumentedTest
import design.inhale.datasource.observer.NullDataSourceListener
import design.inhale.testutils.Latch
import design.inhale.utils.locationManager
import design.inhale.utils.mock
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.core.Is
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
class DeviceLocationSourceTest: InstrumentedTest() {
private val mockProviderName = "MockProvider"
private val locationManager: LocationManager
get() = appContext.locationManager
#get:Rule
val permissionRule: GrantPermissionRule = grant(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION)
#Before
fun init() {
addMockLocationProvider()
}
#After
fun deinit() {
instrumentation.waitForIdleSync()
removeMockLocationProvider()
}
#Test(timeout = 10_000)
fun receiveLocationUpdate() {
val latch = Latch()
val listener = object: LocationSourceListener {
override fun onDataUpdated(data: Location) {
with(data) {
assertThat(latitude, Is(equalTo(0.0)))
assertThat(longitude, Is(equalTo(0.0)))
}
latch.release()
}
}
mockLocationSource(listener).start()
instrumentation.waitForIdleSync() // in case we're hitting race conditions?
updateMockLocation(0.0, 0.0)
latch.await()
}
#Suppress("SameParameterValue")
private fun updateMockLocation(latitude: Double, longitude: Double) {
val location = Location(mockProviderName).mock(latitude, longitude)
locationManager.setTestProviderLocation(mockProviderName, location)
}
private fun mockLocationSource(listener: LocationSourceListener = NullDataSourceListener()) =
DeviceLocationSource(appContext, mockProviderName, listener)
private fun addMockLocationProvider() {
with(locationManager) {
try {
addTestProvider(
mockProviderName,
false,
false,
false,
false,
true,
true,
true,
0,
5)
} catch (e: IllegalArgumentException) {
// If this is caught, the mock provider already exists
}
setTestProviderEnabled(mockProviderName, true)
}
}
private fun removeMockLocationProvider() = locationManager.removeTestProvider(mockProviderName)
}
Manifest:
<manifest package="design.inhale.locationapi"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
androidTest/Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="design.inhale.locationapi"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
</manifest>
Location.mock():
#SuppressLint("ObsoleteSdkInt")
fun Location.mock(latitude: Double = 0.0, longitude: Double = 0.0, accuracy: Float = 0f): Location {
this.latitude = latitude
this.longitude = longitude
this.accuracy = accuracy
this.time = currentTimeMillis()
if (SDK_INT > JELLY_BEAN) this.elapsedRealtimeNanos = elapsedRealtimeNanos()
return this
}
Turns out I needed to add permission for background location access (I guess the test counts as running in the background?).
Added permission to the test manifest.
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>`
and changed the test to grant it:
val permissionRule: GrantPermissionRule = grant(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, ACCESS_BACKGROUND_LOCATION)
Note that you'll want to make sure you don't add ACCESS_BACKGROUND_LOCATION to your production manifest. Google will pull your app if they find you're using this permission without a good reason.