Here I gave the simplest possible example of loading a KML in Google Maps, using
HttpURLConnection.
In this example I will use
retrofit.
Here I gave simplest possible GET and POST example in Kotlin, but problem with that solution is that I am using
enqueue and
Call return type which means that my every request will be send to sort of a queue, until is finished, and if I want to execute it in a loop, I can end in OutOfMemory very easy, which is why I decided rather to use
coroutines and
Response return type. Read more about Response
here
The problem I was trying to solve was how to load a KML file in real time—in a loop—without running into an OutOfMemory exception. One solution I already wrote
here, now here is a complete one.
My API endpoint looks like this:
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.