How to Easily Integrate GPS Location Updates in Jetpack Compose: A 5-Step Guide

Integrating localization updates in Jetpack Compose is very easy. Follow these 5 steps to do it.

1. Setup your project

Add permissions in manifest file

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    
    <!-- Location permissions -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
    <uses-permission
        android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION"
        android:maxSdkVersion="28" />
        
</manifest>

Add google dependency

In your build.gradle.kts add

dependencies {
    implementation(libs.play.services.location)
    
    //Ommited
}

In your libs.version.toml add

[versions]
playServicesLocation = "21.3.0"


[libraries]
play-services-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "playServicesLocation" }

2. Check for permission when your app start

In the Main Activity add some functions to check and request permissions

class MainActivity : ComponentActivity() {

    private val LOCATION_PERMISSION_REQUEST_CODE = 1

    private fun checkPermissions() {

        if (!hasLocationPermissions()) {
            requestLocationPermissions()
            return
        }
    }

    private fun requestLocationPermissions() {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
            LOCATION_PERMISSION_REQUEST_CODE
        )
    }

    private fun hasLocationPermissions(): Boolean {
        return hasPermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
                hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
    }

    private fun hasPermission(permission: String): Boolean {
        val result = ActivityCompat.checkSelfPermission(this,permission);

        return result == PackageManager.PERMISSION_GRANTED;
    }
    
    //Ommited

}

And call checkPermissions on create

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        checkPermissions()
}

3. Create View Model And State

State

data class LocationState(
    val latitude: Double = 0.0,
    val longitude: Double = 0.0
)

View model

class LocationViewModel: ViewModel() {
    private val uiState = MutableStateFlow(LocationState())
    val state: StateFlow<LocationState> = uiState;

    fun update(latitude: Double, longitude: Double) {
        uiState.update { it.copy(latitude, longitude) }
    }
}

4. Create Screen and add it to your activity

Screen

@Composable
fun LocationScreen(viewModel: LocationViewModel) {
    val state by viewModel.state.collectAsState()

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,

        ) {
        Text(text = "Latitude: ${state.latitude}" )
        Text(text = "Longitude: ${state.longitude}")
    }
}

Add the screen as the content of your main activity

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()


        checkPermissions()

        val viewModel = LocationViewModel()
        
        // Content added
        setContent {
           LocationScreen(viewModel)
        }
    }

5. Initialize the location client and update the view model

class MainActivity : ComponentActivity() {

    private lateinit var locationClient: FusedLocationProviderClient
    
    @SuppressLint("MissingPermission")
    private fun initUpdates(viewModel: LocationViewModel) {
        locationClient = LocationServices.getFusedLocationProviderClient(this);

        locationClient.requestLocationUpdates(
            createLocationRequest(),
            {location -> viewModel.update(location.latitude, location.longitude)},
            Looper.getMainLooper()
        )
    }
    
    private fun createLocationRequest(): LocationRequest {
        return LocationRequest.Builder(1000).build()
    }
  
  //ommited

}

And finally call initUpdates on create method

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()


        checkPermissions()

        val viewModel = LocationViewModel()
        
        // Here 
        initUpdates(viewModel)

        setContent {
           LocationScreen(viewModel)
        }
    }

This is the whole code

package com.example.locationintegration

import android.content.pm.PackageManager
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.app.ActivityCompat
import com.example.locationintegration.ui.theme.LocationIntegrationTheme
import com.google.android.gms.location.LocationRequest
import android.Manifest
import android.annotation.SuppressLint
import android.os.Looper
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.lifecycle.ViewModel
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update

class MainActivity : ComponentActivity() {

    private lateinit var locationClient: FusedLocationProviderClient

    @SuppressLint("MissingPermission")
    private fun initUpdates(viewModel: LocationViewModel) {
        locationClient = LocationServices.getFusedLocationProviderClient(this);

        locationClient.requestLocationUpdates(
            createLocationRequest(),
            {location -> viewModel.update(location.latitude, location.longitude)},
            Looper.getMainLooper()
        ).addOnFailureListener { ex -> {
            System.out.println(ex)
        } }

    }

    private val LOCATION_PERMISSION_REQUEST_CODE = 1



    private fun checkPermissions() {

        if (!hasLocationPermissions()) {
            requestLocationPermissions()
            return
        }
    }

    private fun requestLocationPermissions() {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
            LOCATION_PERMISSION_REQUEST_CODE
        )
    }

    private fun hasLocationPermissions(): Boolean {
        return hasPermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
                hasPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
    }

    private fun hasPermission(permission: String): Boolean {
        val result = ActivityCompat.checkSelfPermission(this,permission);

        return result == PackageManager.PERMISSION_GRANTED;
    }

    private fun createLocationRequest(): LocationRequest {
        return LocationRequest.Builder(1000).build()

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()


        checkPermissions()

        val viewModel = LocationViewModel()

        initUpdates(viewModel)

        setContent {
           LocationScreen(viewModel)
        }
    }
}

@Composable
fun LocationScreen(viewModel: LocationViewModel) {
    val state by viewModel.state.collectAsState()

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,

        ) {
        Text(text = "Latitude: ${state.latitude}" )
        Text(text = "Longitude: ${state.longitude}")
    }
}


class LocationViewModel: ViewModel() {
    private val uiState = MutableStateFlow(LocationState())
    val state: StateFlow<LocationState> = uiState;

    fun update(latitude: Double, longitude: Double) {
        uiState.update { it.copy(latitude, longitude) }
    }
}


data class LocationState(
    val latitude: Double = 0.0,
    val longitude: Double = 0.0
)

You can check the project here

https://github.com/FractalCodeRicardo/programmingheadache-misc/tree/main/location-integration

Links


Github: https://github.com/FractalCodeRicardo

Medium: https://medium.com/@nosilverbullet

Web page: https://programmingheadache.com

Youtube: https://www.youtube.com/@ProgrammingHeadache

Leave a Reply

Your email address will not be published. Required fields are marked *