milosev.com
  • Home
    • List all categories
    • Sitemap
  • Downloads
    • WebSphere
    • Hitachi902
    • Hospital
    • Kryptonite
    • OCR
    • APK
  • About me
    • Gallery
      • Italy2022
      • Côte d'Azur 2024
    • Curriculum vitae
      • Resume
      • Lebenslauf
    • Social networks
      • Facebook
      • Twitter
      • LinkedIn
      • Xing
      • GitHub
      • Google Maps
      • Sports tracker
    • Adventures planning
  1. You are here:  
  2. Home
  3. Android

Reloading a KML Layer in Google Maps in a Loop

Details
Written by: Stanko Milosev
Category: Android
Published: 16 November 2025
Last Updated: 19 November 2025
Hits: 17
  • kotlin
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.

Remove KmlLayer from the Map to avoid OutOfMemory

Details
Written by: Stanko Milosev
Category: Android
Published: 12 November 2025
Last Updated: 12 November 2025
Hits: 18
  • kotlin
Example:
private var kmlLayer: KmlLayer? = null
...
kmlLayer?.removeLayerFromMap()
kmlLayer = KmlLayer(googleMap, input, activity)
kmlLayer?.addLayerToMap()

Load KML in Google Maps

Details
Written by: Stanko Milosev
Category: Android
Published: 02 November 2025
Last Updated: 16 November 2025
Hits: 51
  • kotlin
Here I gave simple example of loading google maps in android app. Now I want to load KML file which is on my web site, using simple HttpURLConnection.

First I will add a button for load KML, in order to have clear example, so my layout looks like:

<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.

Simple Google Maps example

Details
Written by: Stanko Milosev
Category: Android
Published: 02 November 2025
Last Updated: 13 November 2025
Hits: 58
  • kotlin
Here is one simplest possible example of google maps.

First start new "Empty Views Activity" like I already explained here

In "\GoogleMapsTestSandBox\app\build.gradle.kts" I have added "com.google.android.gms:play-services-maps:19.2.0", so my dependencies looks like

dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.material)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)

    implementation("com.google.android.gms:play-services-maps:19.2.0")
}
Since I don't want to make google maps key public, I will add "secrets" file. To do it, first I wil add id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") version "2.0.1" plugin in gradle "\GoogleMapsTestSandBox\app\build.gradle.kts":
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
    id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") version "2.0.1"
}
Before I sync my gradle, I need to add two files local.defaults.properties and secrets.properties.

local.defaults.properties:

MAPS_API_KEY=DEFAULT_API_KEY
secrets.properties:
sdk.dir=C\:\\Users\\smilosev\\AppData\\Local\\Android\\Sdk
MAPS_API_KEY=myMapsApiKey;
Now in "\GoogleMapsTestSandBox\app\src\main\AndroidManifest.xml" add:
<meta-data
	android:name="com.google.android.geo.API_KEY"
	android:value="${MAPS_API_KEY}" />
Since in this example I just want to display google maps my "\GoogleMapsTestSandBox\app\src\main\res\layout\activity_main.xml" will look like:
<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="match_parent"
    android:layout_height="match_parent" />
For this, simplest example my "\GoogleMapsTestSandBox\app\src\main\java\com\milosev\googlemapstestsandbox\MainActivity.kt" will look like:
package com.milosev.googlemapstestsandbox

import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
    }
}
Example download from here.
  1. Receiver as separate class
  2. Foreground service as a separate process example
  3. Upload images from Android Kotlin to Web Api .NET Core - third Example
  4. Upload images from Android Kotlin to Web Api .NET Core - second Example

Page 1 of 13

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10