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

Load KML in Google Maps

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

Receiver as separate class

Details
Written by: Stanko Milosev
Category: Android
Published: 01 November 2025
Last Updated: 01 November 2025
Hits: 97
  • kotlin
Here I gave one example of foreground service, but where reciever is in MainActivity.kt.

In this example I want to have receiveer in separate class. So in manifest I will add:

<receiver
	android:name=".SeparateClassReceiverExample"
	android:enabled="true"
	android:exported="false">
</receiver>
Where my class SeparateClassReceiverExample looks like:
class SeparateClassReceiverExample : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent?) {
        val msg = intent?.getStringExtra("message")
    }
}
To send messages from separate foreground service I am using this code:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
	val context = this
	when (intent?.action) {
		IntentGlobalActions.START_FOREGROUND_SERVICE -> {
		
		...

			val serviceIntent = Intent(context, SeparateClassReceiverExample::class.java)
			serviceIntent.putExtra("message", "Message number: $messageNumber")
			sendBroadcast(serviceIntent)
			
		}
notice line:
val context = this
Next problem is to update ui, this is why now my receiver will look like:
class SeparateClassReceiverExample : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent?) {
        val msg = intent?.getStringExtra("message")

        val tickIntent = Intent(IntentGlobalActions.UI_UPDATE)
        tickIntent.setPackage(context.packageName)
        tickIntent.putExtra("message", msg)
        context.sendBroadcast(tickIntent)
    }
}
In MainActivity.kt I wrote:
private val uiReceiver = object : BroadcastReceiver() {
	override fun onReceive(context: Context?, intent: Intent?) {
		val msg = intent?.getStringExtra("message")
		if (msg != null) appendLog(msg)
	}
}
and
registerReceiver(uiReceiver, IntentFilter(IntentGlobalActions.UI_UPDATE), RECEIVER_NOT_EXPORTED)
unregisterReceiver(uiReceiver)
Example download from here.

Foreground service as a separate process example

Details
Written by: Stanko Milosev
Category: Android
Published: 26 October 2025
Last Updated: 02 November 2025
Hits: 225
  • kotlin
Foreground services let you asynchronously perform operations that are noticeable to the user

Here are the main files as they should look.

  1. AndroidManifest.xml
  2. activity_main.xml
  3. MainActivity.kt
  4. SeparateProcessServiceExample.kt
  5. IntentGlobalActions.kt
  6. Below, I tried to explain everything line by line.
  7. Here, is example of receiver as separate class

\SeparateProcessServiceExample\app\src\main\AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SeparateProcessServiceExample">
        <service
            android:name=".SeparateProcessServiceExample"
            android:enabled="true"
            android:exported="false"
            android:permission="android.permission.FOREGROUND_SERVICE"
            android:foregroundServiceType="dataSync"
            android:process=":SeparateProcessServiceExample" />

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
\SeparateProcessServiceExample\app\src\main\res\layout\activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <ScrollView
        android:id="@+id/scrollLog"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:fillViewport="true"
        android:scrollbars="vertical"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/btnStart"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <TextView
            android:id="@+id/tvLog"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Log:"
            android:textSize="14sp"
            android:textStyle="bold"
            android:textColor="@android:color/black"
            android:typeface="monospace"
            android:gravity="top|start"
            android:background="#EEEEEE"
            android:padding="8dp" />
    </ScrollView>

    <Button
        android:id="@+id/btnStart"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start"
        android:layout_marginBottom="64dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
\SeparateProcessServiceExample\app\src\main\java\com\milosev\separateProcessServiceExample\MainActivity.kt:
package com.milosev.separateProcessServiceExample

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.milosev.separateProcessServiceExample.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private val maxLines = 500

    private val receiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            val msg = intent?.getStringExtra("message")
            appendLog("Receiver: Got: $msg")
        }
    }

    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS)
            != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
                100
            )
        }

        val filter = IntentFilter(IntentGlobalActions.TEST_MESSAGE_ACTION)
        registerReceiver(receiver, filter, RECEIVER_NOT_EXPORTED)

        binding.btnStart.setOnClickListener {
            val intent = Intent(this, SeparateProcessServiceExample::class.java)
            intent.action = IntentGlobalActions.START_FOREGROUND_SERVICE
            startForegroundService(intent)
        }

    }

    fun appendLog(message: String) {
        runOnUiThread {
            binding.tvLog.append("\n$message")

            val lines = binding.tvLog.text.split("\n")
            if (lines.size > maxLines) {
                val trimmed = lines.takeLast(maxLines).joinToString("\n")
                binding.tvLog.text = trimmed
            }

            binding.scrollLog.post {
                binding.scrollLog.fullScroll(View.FOCUS_DOWN)
            }
        }
    }
}
\SeparateProcessServiceExample\app\src\main\java\com\milosev\separateProcessServiceExample\SeparateProcessServiceExample.kt:
package com.milosev.separateProcessServiceExample

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.os.IBinder
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class SeparateProcessServiceExample : Service(), CoroutineScope by MainScope() {

    private var job: Job? = null

    override fun onBind(intent: Intent): IBinder {
        TODO("Return the communication channel to the service.")
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            IntentGlobalActions.START_FOREGROUND_SERVICE -> {

                val channelId = createNotificationChannel("my_service", "My Background Service")
                val notificationBuilder = NotificationCompat.Builder(this, channelId)
                val notification = notificationBuilder.setOngoing(true)
                    .setContentTitle("test")
                    .setContentText("test")
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setPriority(1)
                    .setCategory(Notification.CATEGORY_SERVICE)
                    .build()

                startForeground(101, notification)
                var messageNumber = 0
                job = launch {
                    while(true) {
                        messageNumber++
                        val intent = Intent(IntentGlobalActions.TEST_MESSAGE_ACTION)
                        intent.putExtra("message", "Message number: $messageNumber")
                        intent.setPackage(packageName)
                        sendBroadcast(intent)
                        delay(1_000)
                    }
                }

            }
        }

        return START_STICKY
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private fun createNotificationChannel(channelId: String, channelName: String): String {
        val chan = NotificationChannel(
            channelId,
            channelName, NotificationManager.IMPORTANCE_DEFAULT
        )
        chan.lightColor = Color.RED
        chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
        val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        service.createNotificationChannel(chan)
        return channelId
    }
}
\SeparateProcessServiceExample\app\src\main\java\com\milosev\separateProcessServiceExample\IntentGlobalActions.kt:
package com.milosev.separateProcessServiceExample

object IntentGlobalActions {
    const val TEST_MESSAGE_ACTION = "com.milosev.TEST_MESSAGE_ACTION"
    const val START_FOREGROUND_SERVICE = "startService"
}

---

Since LocalBroadcastManager is deprecated, here are examples for creating a separate process Service and sending messages from the process to the UI.

First I created new project with "Empty Views Activity":

Then I have added new service and name it "SeparateProcessServiceExample", please note that later I will derive it from MainScope:

I have disabled exported, since I don't want to share messages between other applications:

In "\SeparateProcessServiceExample\app\src\main\AndroidManifest.xml" I have added permissions for FOREGROUND_SERVICE, and since later I want to display icon in notification when my service starts, I will also need POST_NOTIFICATIONS, also I need FOREGROUND_SERVICE_DATA_SYNC permission because I will want to start with "startForeground" method:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Also in AndroidManifest.xml I have added service under node application. "foregroundServiceType" I need because of the error:

To call Service.startForeground(), the <service> element of manifest file must have the foregroundServiceType attribute specified
<service
	android:name=".SeparateProcessServiceExample"
	android:enabled="true"
	android:exported="false"
	android:permission="android.permission.FOREGROUND_SERVICE"
	android:foregroundServiceType="dataSync"
	android:process=":SeparateProcessServiceExample" />
So my "\SeparateProcessServiceExample\app\src\main\AndroidManifest.xml" looks like:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SeparateProcessServiceExample">
        <service
            android:name=".SeparateProcessServiceExample"
            android:enabled="true"
            android:exported="false"
            android:permission="android.permission.FOREGROUND_SERVICE"
            android:foregroundServiceType="dataSync"
            android:process=":SeparateProcessServiceExample" />

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
Now I have addded new object "IntentGlobalActions", for better readibility, which I will use to define actions for intent:
object IntentGlobalActions {
    const val TEST_MESSAGE_ACTION = "com.milosev.TEST_MESSAGE_ACTION"
    const val START_FOREGROUND_SERVICE = "startService"
}
My "\SeparateProcessServiceExample\app\src\main\res\layout\activity_main.xml" looks like:
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <ScrollView
        android:id="@+id/scrollLog"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:fillViewport="true"
        android:scrollbars="vertical"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/btnStart"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <TextView
            android:id="@+id/tvLog"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Log:"
            android:textSize="14sp"
            android:textStyle="bold"
            android:textColor="@android:color/black"
            android:typeface="monospace"
            android:gravity="top|start"
            android:background="#EEEEEE"
            android:padding="8dp" />
    </ScrollView>

    <Button
        android:id="@+id/btnStart"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start"
        android:layout_marginBottom="64dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
Ask user to allow notification permission:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
	if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS)
		!= PackageManager.PERMISSION_GRANTED
	) {
		ActivityCompat.requestPermissions(
			this,
			arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
			100
		)
	}
}
Setup View Binding:
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
but don't forget to enable viewBinding in your "\SeparateProcessServiceExample\app\build.gradle.kts":
buildFeatures {
	viewBinding = true
}
In "\SeparateProcessServiceExample\app\src\main\java\com\milosev\separateProcessServiceExample\SeparateProcessServiceExample.kt" first override onStartCommand and filter pro intent messages defined in IntentGlobalActions, and return START_STICKY:
@RequiresApi(Build.VERSION_CODES.O)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
	when (intent?.action) {
		IntentGlobalActions.START_FOREGROUND_SERVICE -> {
		}
	}
	return START_STICKY
}
create notification:
val channelId = createNotificationChannel("my_service", "My Background Service")
val notificationBuilder = NotificationCompat.Builder(this, channelId)
val notification = notificationBuilder.setOngoing(true)
	.setContentTitle("test")
	.setContentText("test")
	.setSmallIcon(R.mipmap.ic_launcher)
	.setPriority(1)
	.setCategory(Notification.CATEGORY_SERVICE)
	.build()
Where the method createNotificationChannel looks like:
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String {
	val chan = NotificationChannel(
		channelId,
		channelName, NotificationManager.IMPORTANCE_DEFAULT
	)
	chan.lightColor = Color.RED
	chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
	val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
	service.createNotificationChannel(chan)
	return channelId
}
Start service:
startForeground(101, notification)
Next, I want to have an endles task, from which I want to send messages like:
private var job: Job? = null
...
job = launch {
	while(true) {
		delay(10_000)
	}
}
In order to use launch my SeparateProcessServiceExample to derive from CoroutineScope by MainScope(), so now it looks like:
class SeparateProcessServiceExample : Service(), CoroutineScope by MainScope() {
Message sending from service looks like:
val intent = Intent(IntentGlobalActions.TEST_MESSAGE_ACTION)
intent.putExtra("message", "Message number: $messageNumber")
intent.setPackage(packageName)
sendBroadcast(intent)
Now, back to "\SeparateProcessServiceExample\app\src\main\java\com\milosev\separateProcessServiceExample\MainActivity.kt", create reciever like:
private val receiver = object : BroadcastReceiver() {
	override fun onReceive(context: Context?, intent: Intent?) {
		val msg = intent?.getStringExtra("message")
		appendLog("Receiver: Got: $msg")
	}
}
Register it:
val filter = IntentFilter(IntentGlobalActions.TEST_MESSAGE_ACTION)
registerReceiver(receiver, filter, RECEIVER_NOT_EXPORTED)
Where the method appendLog looks like:
fun appendLog(message: String) {
	runOnUiThread {
		binding.tvLog.append("\n$message")

		val lines = binding.tvLog.text.split("\n")
		if (lines.size > maxLines) {
			val trimmed = lines.takeLast(maxLines).joinToString("\n")
			binding.tvLog.text = trimmed
		}

		binding.scrollLog.post {
			binding.scrollLog.fullScroll(View.FOCUS_DOWN)
		}
	}
}
To Stop a service from \SeparateProcessServiceExample\app\src\main\java\com\milosev\separateProcessServiceExample\MainActivity.kt I have sent STOP_FOREGROUND_SERVICE:
val intent = Intent(this, SeparateProcessServiceExample::class.java)
intent.action = IntentGlobalActions.STOP_FOREGROUND_SERVICE
startForegroundService(intent)
and in \SeparateProcessServiceExample\app\src\main\java\com\milosev\separateProcessServiceExample\SeparateProcessServiceExample.kt:
IntentGlobalActions.STOP_FOREGROUND_SERVICE -> {
	job?.cancel()
	stopForeground(STOP_FOREGROUND_REMOVE)
	stopSelfResult(startId)
}
Example download from here.
  1. Upload images from Android Kotlin to Web Api .NET Core - third Example
  2. Upload images from Android Kotlin to Web Api .NET Core - second Example
  3. Upload images from Android Kotlin to Web Api .NET Core - first Example
  4. Upload images to virtual device

Page 2 of 13

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