- Details
- Written by: Stanko Milosev
- Category: Android
- Hits: 754
implementation("com.google.android.gms:play-services-location:21.3.0")
in "\app\src\main\AndroidManifest.xml"
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />Shortly, I am using this code to receive last known location
var locationRequest: LocationRequest
val localInterval: Long = 0
locationRequest = LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY, TimeUnit.SECONDS.toMillis(localInterval)
).apply {
setGranularity(Granularity.GRANULARITY_PERMISSION_LEVEL)
setDurationMillis(TimeUnit.MINUTES.toMillis(Long.MAX_VALUE))
setWaitForAccurateLocation(true)
setMaxUpdates(Int.MAX_VALUE)
setIntervalMillis(TimeUnit.SECONDS.toMillis(localInterval))
setMinUpdateIntervalMillis(TimeUnit.SECONDS.toMillis(localInterval))
setMinUpdateDistanceMeters(0F)
}.build()
val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResultLocal: LocationResult) {
logger.log("\nLat: " + locationResultLocal.lastLocation?.latitude.toString()
+ "\nLon: " + locationResultLocal.lastLocation?.longitude.toString()
+ "\nAltitude" + locationResultLocal.lastLocation?.altitude.toString())
}
}
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(myActivity)
fusedLocationClient.requestLocationUpdates(
locationRequest, locationCallback, Looper.getMainLooper()
)
Notice line:
setMinUpdateIntervalMillis(TimeUnit.SECONDS.toMillis(localInterval))With setMinUpdateIntervalMillis I am reciving location as soon as possible, constantly, without stopping. To stop location updates use this code:
fusedLocationClient.removeLocationUpdates(locationCallback)My class for starting / stoping location updates looks like this:
package com.example.testsandbox
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Looper
import androidx.core.app.ActivityCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.Granularity
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import java.util.concurrent.TimeUnit
class MyFusedLocationClient(var logger: ILogger) {
private lateinit var fusedLocationClient: FusedLocationProviderClient
lateinit var myActivity: Activity
var locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResultLocal: LocationResult) {
logger.log("\nLat: " + locationResultLocal.lastLocation?.latitude.toString()
+ "\nLon: " + locationResultLocal.lastLocation?.longitude.toString()
+ "\nAltitude" + locationResultLocal.lastLocation?.altitude.toString())
}
}
fun startLocationUpdates() {
myRequestLocationUpdates(myActivity)
}
fun stopLocationUpdates() {
fusedLocationClient.removeLocationUpdates(locationCallback)
}
private fun myRequestLocationUpdates(myActivity: Activity) {
if (ActivityCompat.checkSelfPermission(
myActivity,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
myActivity,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
myActivity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_LOCATION_PERMISSION
)
}
var locationRequest: LocationRequest
val localInterval: Long = 0
locationRequest = LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY, TimeUnit.SECONDS.toMillis(localInterval)
).apply {
setGranularity(Granularity.GRANULARITY_PERMISSION_LEVEL)
setDurationMillis(TimeUnit.MINUTES.toMillis(Long.MAX_VALUE))
setWaitForAccurateLocation(true)
setMaxUpdates(Int.MAX_VALUE)
setIntervalMillis(TimeUnit.SECONDS.toMillis(localInterval))
setMinUpdateIntervalMillis(TimeUnit.SECONDS.toMillis(localInterval))
setMinUpdateDistanceMeters(0F)
}.build()
fusedLocationClient = LocationServices.getFusedLocationProviderClient(myActivity)
fusedLocationClient.requestLocationUpdates(
locationRequest, locationCallback, Looper.getMainLooper()
)
}
companion object {
const val REQUEST_LOCATION_PERMISSION = 123
}
}
In activty use it like this:
val myFusedLocationClient = MyFusedLocationClient(logger) myFusedLocationClient.myActivity = this ... myFusedLocationClient.startLocationUpdates() ... myFusedLocationClient.stopLocationUpdates()Full example downlaod from here.
- Details
- Written by: Stanko Milosev
- Category: Android
- Hits: 313
interface IGetKml {
@GET
suspend fun getKml(@Url url: String): Response<ResponseBody>
}
Notice that getKml is suspending function.
The class that actually loads the KML looks like this:
package com.milosev.googlemapstestsandbox
import android.content.Context
import android.util.Log
import com.google.android.gms.maps.GoogleMap
import com.google.maps.android.data.kml.KmlLayer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import java.io.ByteArrayInputStream
import java.net.URL
class LoadKml {
private var kmlLayer: KmlLayer? = null
suspend fun execute(activity: Context, googleMap: GoogleMap, strUrl: String) {
val url = URL(strUrl)
val kmlClient = CreateRetrofitBuilder().createRetrofitBuilder("${url.protocol}://${url.host}/")
.create(IGetKml::class.java)
withContext(Dispatchers.IO) {
while (isActive)
{
try {
val webApiRequest =
kmlClient.getKml(strUrl);
if (webApiRequest.isSuccessful) {
val bytes = webApiRequest.body()?.bytes()
if (bytes != null) {
val input = ByteArrayInputStream(bytes)
withContext(Dispatchers.Main) {
kmlLayer?.removeLayerFromMap()
Log.i("KML", "Removed successfully")
kmlLayer = KmlLayer(googleMap, input, activity)
kmlLayer?.addLayerToMap()
Log.i("KML", "Loaded successfully")
}
}
} else {
Log.e("KML", "Error: ${webApiRequest.code()}")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
Notice that execute is also suspend function and also notice that I am starting loop with Dispatchers.IO, but KML I will add in Dispatchers.Main
Here is MainActivity.kt
package com.milosev.googlemapstestsandbox
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
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.milosev.googlemapstestsandbox.databinding.ActivityMainBinding
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private var kmlUpdateJob: Job? = null
private lateinit var binding: ActivityMainBinding
private lateinit var map: GoogleMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
val mainActivity = this
var isStarted = false
binding.btnLoadKml.setOnClickListener {
isStarted = !isStarted
if (isStarted) {
binding.btnLoadKml.text = "Stop"
val loadKml = LoadKml()
kmlUpdateJob = lifecycleScope.launch {
loadKml.execute(mainActivity, map, binding.etKmlUrl.text.toString())
}
}
else {
kmlUpdateJob?.cancel()
kmlUpdateJob = null
binding.btnLoadKml.text = "Start"
}
}
}
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
val tunis = LatLng(35.7607919, 10.7537573)
map.moveCamera(CameraUpdateFactory.newLatLngZoom(tunis, 8f))
}
}
Notice how I am starting to load KML using job:
private var kmlUpdateJob: Job? = null
...
kmlUpdateJob = lifecycleScope.launch {
loadKml.execute(mainActivity, map, binding.etKmlUrl.text.toString())
}
...
kmlUpdateJob?.cancel()
kmlUpdateJob = null
Example download from here.
- Details
- Written by: Stanko Milosev
- Category: Android
- Hits: 240
private var kmlLayer: KmlLayer? = null ... kmlLayer?.removeLayerFromMap() kmlLayer = KmlLayer(googleMap, input, activity) kmlLayer?.addLayerToMap()
- Details
- Written by: Stanko Milosev
- Category: Android
- Hits: 298
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<fragment
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/btnLoadKml"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Load KML"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginBottom="32dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
In gradle "\GoogleMapsTestSandBox\app\build.gradle.kts" add dependency:
implementation("com.google.maps.android:android-maps-utils:3.19.0")
Now, setup view binding as I already explained here, MainActivity I will inherit from OnMapReadyCallback, so now it looks like:
package com.milosev.googlemapstestsandbox
import android.os.Bundle
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.milosev.googlemapstestsandbox.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var binding: ActivityMainBinding
private lateinit var map: GoogleMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
binding.btnLoadKml.setOnClickListener {
}
}
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
val tunis = LatLng(36.8065, 10.1815)
map.moveCamera(CameraUpdateFactory.newLatLngZoom(tunis, 8f))
}
}
setOnClickListener is empty, here I will add:
val loadKml = LoadKml() loadKml.execute(this, map)Since my KML is too big, I had to extend timeouts, and first I am saving in into temp file, so class LoadKml will look like this:
package com.milosev.googlemapstestsandbox
import android.content.Context
import android.widget.Toast
import com.google.android.gms.maps.GoogleMap
import com.google.maps.android.data.kml.KmlLayer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileInputStream
import java.net.URL
class LoadKml {
fun execute(activity: Context, map: GoogleMap) {
CoroutineScope(Dispatchers.IO).launch {
try {
val url =
URL("https://www.milosev.com/gallery/allWithPics/travelBuddies/tunis/kml/kml.kml")
val connection = url.openConnection()
connection.connectTimeout = 15000
connection.readTimeout = 60000
val tempFile = File(activity.cacheDir, "temp.kml")
connection.getInputStream().use { input ->
tempFile.outputStream().use { output ->
input.copyTo(output)
}
}
withContext(Dispatchers.Main) {
val layer = KmlLayer(map, FileInputStream(tempFile), activity)
layer.addLayerToMap()
Toast.makeText(activity, "KML Loaded", Toast.LENGTH_LONG).show()
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(activity, "Error: ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
}
Part:
val tempFile = File(activity.cacheDir, "temp.kml")
connection.getInputStream().use { input ->
tempFile.outputStream().use { output ->
input.copyTo(output)
}
}
is maybe not needed, I was testing of loading big KML files.
Example download from here.