Google Map
Current Location
Note
You only need to add the Google Play Services support here if your application needs to display the user's current location.
Next, let's add the user's current location. A little later we'll use this for the initial map position and to keep track of where the user parked their car.
The user's Location is determined by the "Fused Location Provider". This service uses technologies such as GPS, cell towers, and wi-fi to determine the current location. Some of these provide precise location (such as GPS), while others might only be able to approximate user location.
Because an application could send location information somewhere else (a server on the internet, for example), location is considered a "dangerous" permission, and we must ask the user if it's ok to use while the application is running.
The user has a choice: they can allow precise or approximate location information, for all runs of the application or just the current run, or deny the request. Ideally, your application should gracefully handle denied function. For our car-finder application, if the user denies current location tracking, we could, for example, allow the user to tap the location of their car on the map rather than automatically using the current location. (For this example application, we won't do that; we'll just tell the user the application cannot function without location.)
Note
Requesting permissions at runtime can be a bit tricky, and support for permissions when using
Jetpack Compose is still experimental (and a bit difficult to use properly). For now, we'll
be requesting permissions and listening for location using the Activity
and updating a view
model with the reported locations.
Any needed permissions must be declared first in the AndroidManifest.xml
. Here we declare
show in full file app/src/main/AndroidManifest.xml
// ...
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
// ...
</manifest>
- Coarse Location (for "approximate" location)
- Fine Location (for "precise" location)
Both of these are "dangerous" permissions and must be requested at runtime.
The Google Play Location Services allows us to set up a listener to receive location updates. We'll want to store the current location somewhere, and we'll need it when setting the car's location. The car's location will be persisted and we want to ensure it stays across configuration changes. This sounds like a job for a View Model.
We access Play Location Services by adding a new dependency. While we're here we'll also add lifecycle support for compose.
show in full file gradle/libs.versions.toml
[versions]
// ...
//
maps-compose = "6.2.1"
location-services = "21.3.0"
lifecycle-runtime-compose = "2.8.7"
[libraries]
location-services = { group = "com.google.android.gms", name = "play-services-location", version.ref = "location-services" }
lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle-runtime-compose" }
maps-compose = { group = "com.google.maps.android", name = "maps-compose", version.ref = "maps-compose" }
maps-compose-utils = { group = "com.google.maps.android", name = "maps-compose-utils", version.ref = "maps-compose" }
// ...
[plugins]
// ...
show in full file app/build.gradle.kts
// ...
dependencies {
implementation(libs.location.services)
implementation(libs.lifecycle.runtime.compose)
implementation(libs.maps.compose)
implementation(libs.maps.compose.utils)
// ...
}
// ...
We'll need to track the location. We create a CarViewModel
and set up a MutableStateFlow
as a private
property in the view model. By convention, we prefix it with an underscore,
indicating it's the actual flow that we'll emit to. We want our view model to keep control of
emitted values, so we only expose a read-only Flow
publicly.
But our MainActivity
will be the thing that actually talks with the Fused Location Provider;
it'll need to update the current location (by calling updateLocation
).
show in full file app/src/main/java/com/androidbyexample/compose/google/google/maps/CarViewModel.kt
// ...
import kotlinx.coroutines.flow.MutableStateFlow
class CarViewModel(application: Application) : AndroidViewModel(application) {
private val _currentLocation = MutableStateFlow<Location?>(null)
val currentLocation: Flow<Location?>
get() = _currentLocation
fun updateLocation(location: Location?) {
_currentLocation.value = location
}
}
In the MainActivity
, we define properties for the fused location provider client and the callback
that we'll register to receive location updates.
show in full file app/src/main/java/com/androidbyexample/compose/google/google/maps/MainActivity.kt
// ...
class MainActivity : ComponentActivity() {
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
private val viewModel: CarViewModel by viewModels()
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
viewModel.updateLocation(locationResult.lastLocation)
}
}
private val getLocationPermission =
// ...
}
We have to set up a new sequence of events to start our activity. This sequence looks like
graph TD
A{Check Google Play Services}
X[Cannot run - exit]
X2[Cannot run - exit]
A -->|Not Available|X
A -->|Available|B
B{Do we have location permission?}
B -->|Yes|C
B -->|No|E
C[Start location request]
C --> D
D[Setup Map]
E[Request Location Permissions]
E --> F
F{Were permissions granted?}
F -->|No|G
F -->|Yes|C
G[Show rationale]
G --> H
H{Does user agree?}
H -->|Yes|I
H -->|No|X2
I[Application Info Screen to change permissions - exits application]
We move the UI setup out of onCreate
(we'll see it again soon), and check Google Play Services
Availability. If Play Services aren't available, we can't run and must exit.
show in full file app/src/main/java/com/androidbyexample/compose/google/google/maps/MainActivity.kt
// ...
class MainActivity : ComponentActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GoogleApiAvailability.getInstance()
.makeGooglePlayServicesAvailable(this)
.addOnSuccessListener {
if (ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
getLocationPermission.launch(
arrayOf(
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
)
)
} else {
startLocationAndMap()
}
}.addOnFailureListener(this) {
Toast.makeText(
this,
"Google Play services required (or upgrade required)",
Toast.LENGTH_SHORT
).show()
finish()
}
}
}
If Play Services are available, we check if we have location permissions. If not, we request permission. If so, we just go ahead and start our location request and set up the UI.
show in full file app/src/main/java/com/androidbyexample/compose/google/google/maps/MainActivity.kt
// ...
class MainActivity : ComponentActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// ...
GoogleApiAvailability.getInstance()
.makeGooglePlayServicesAvailable(this)
.addOnSuccessListener {
if (ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
getLocationPermission.launch(
arrayOf(
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
)
)
} else {
startLocationAndMap()
}
}.addOnFailureListener(this) {
Toast.makeText(
// ...
}
// ...
}
}
Requesting permissions has gotten a bit easier over the years... We register a launcher that will invoke the system permission requester and return whether the user granted or denied permissions. If granted, we go ahead and start the location request and set up the UI. If not granted, we go into rationale mode.
show in full file app/src/main/java/com/androidbyexample/compose/google/google/maps/MainActivity.kt
// ...
class MainActivity : ComponentActivity() {
// ...
}
private val getLocationPermission =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { isGranted ->
if (isGranted.values.any { it }) {
startLocationAndMap()
} else {
// if the user denied permissions, tell them they
// cannot use the app without them. In general,
// you should try to just reduce function and let the
// user continue, but location is a key part of this
// application.
// (Note that a real version of this application
// might allow the user to manually click on the map
// to set their current location, and we wouldn't
// show this dialog, or perhaps only show it once)
// NOTE: This is a normal Android-View-based dialog, not a compose one!
AlertDialog.Builder(this)
.setTitle("Permissions Needed")
.setMessage(
"We need coarse-location or fine-location permission " +
"to locate a car (fine location is highly " +
"recommended for accurate car locating). " +
"Please allow these permissions via App Info " +
"settings")
.setCancelable(false)
.setNegativeButton("Quit") { _, _ -> finish() }
.setPositiveButton("App Info") { _, _ ->
startActivity(
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS
).apply {
data = Uri.parse("package:$packageName")
}
)
finish()
}
.show()
}
}
@SuppressLint("MissingPermission")
// ...
}
We've decided that our car finder simply cannot run without knowing the current location, so we
display this rationale to the user in a dialog. (Note that this is an old-style "views" dialog,
not a Jetpack Compose dialog.) The user can agree to go change the permissions (and we'll send
them to the system Application Info screen to do so), or they can say "nope!" and we'll quit the
application by calling finish()
on the Activity
(which pops it off the back stack - because
we only have one Activity
on the back stack, the application exits).
After all of the availability and permission checking, we can finally start the application in
startLocationAndMap()
. The first thing we do is request location updates from the fused location
provider, which will use our locationCallback
to send the results to the view model.
show in full file app/src/main/java/com/androidbyexample/compose/google/google/maps/MainActivity.kt
// ...
class MainActivity : ComponentActivity() {
// ...
}
@SuppressLint("MissingPermission")
fun startLocationAndMap() {
val locationRequest =
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
.setWaitForAccurateLocation(false)
.setMinUpdateIntervalMillis(0)
.setMaxUpdateDelayMillis(5000)
.build()
fusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(this)
fusedLocationProviderClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
enableEdgeToEdge()
// ...
}
// ...
}
This will emit the location, which is collected and made available to our composition.
show in full file app/src/main/java/com/androidbyexample/compose/google/google/maps/MainActivity.kt
// ...
class MainActivity : ComponentActivity() {
// ...
@SuppressLint("MissingPermission")
fun startLocationAndMap() {
// ...
setContent {
GoogleMapsTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
// ...
}
val currentLocation by viewModel.currentLocation.collectAsStateWithLifecycle(
initialValue = null
)
GoogleMapDisplay(
// place = googleHQ,
// placeDescription = "Google HQ",
currentLocation = currentLocation,
cameraPositionState = cameraPositionState,
modifier = Modifier.padding(innerPadding).fillMaxSize(),
)
}
}
}
// ...
}
// ...
}
We pass the location to the GoogleMapDisplay
and create a MarkerState
to give us a location
for a new Marker
.
show in full file app/src/main/java/com/androidbyexample/compose/google/google/maps/GoogleMapDisplay.kt
// ...
@Composable
fun GoogleMapDisplay(
// place: LatLng,
// placeDescription: String,
currentLocation: Location?,
cameraPositionState: CameraPositionState,
modifier: Modifier,
) {
var mapLoaded by remember { mutableStateOf(false) }
var currentMapType by remember { mutableStateOf(MapType.NORMAL) }
var mapProperties by remember {
mutableStateOf(MapProperties(mapType = MapType.NORMAL))
}
// val placeState =
// rememberMarkerState(key = place.toString(), position = place)
val currentLocationState = remember(currentLocation) {
currentLocation?.let {
MarkerState(
LatLng(
it.latitude,
it.longitude
)
)
}
}
Box(
// ...
) {
Column(
// ...
) {
// ...
GoogleMap(
// ...
.weight(1f),
) {
currentLocationState?.let {
MarkerInfoWindowContent(
// state = placeState,
// title = placeDescription,
// onClick = {
// placeState.showInfoWindow()
// true
// }
state = it,
anchor = Offset(0.5f, 0.5f),
title = stringResource(
id = R.string.current_location
),
)
}
}
}
// ...
}
}
When we run the app, the location marker appears when it's available.
Note
You can use the "Extended Controls" at the top of the emulator to set a fake location. This will prove very useful when testing this application, as we can set a location, save the car at that location, change the location to simulate walking away from the car, then press navigate to see how to get back to the car.
All code changes
CHANGED: app/build.gradle.kts
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.secrets)
}
android {
namespace = "com.androidbyexample.compose.google.google.maps"
compileSdk = 35
defaultConfig {
applicationId = "com.androidbyexample.compose.google.google.maps"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(libs.location.services)
implementation(libs.lifecycle.runtime.compose)
implementation(libs.maps.compose)
implementation(libs.maps.compose.utils)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}
secrets {
propertiesFileName = "secrets.properties"
}
CHANGED: 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.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<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.GoogleMaps"
tools:targetApi="31">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${MAPS_API_KEY}" />
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.GoogleMaps">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
ADDED: app/src/main/java/com/androidbyexample/compose/google/google/maps/CarViewModel.kt
package com.androidbyexample.compose.google.google.maps
import android.app.Application
import android.location.Location
import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class CarViewModel(application: Application) : AndroidViewModel(application) {
private val _currentLocation = MutableStateFlow<Location?>(null)
val currentLocation: Flow<Location?>
get() = _currentLocation
fun updateLocation(location: Location?) {
_currentLocation.value = location
}
}
CHANGED: app/src/main/java/com/androidbyexample/compose/google/google/maps/GoogleMapDisplay.kt
package com.androidbyexample.compose.google.google.maps
import android.location.Location
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.res.stringResource
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.compose.CameraPositionState
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.MapProperties
import com.google.maps.android.compose.MapType
import com.google.maps.android.compose.MarkerInfoWindowContent
import com.google.maps.android.compose.MarkerState
import com.google.maps.android.compose.rememberMarkerState
@Composable
fun GoogleMapDisplay(
// place: LatLng,
// placeDescription: String,
currentLocation: Location?,
cameraPositionState: CameraPositionState,
modifier: Modifier,
) {
var mapLoaded by remember { mutableStateOf(false) }
var currentMapType by remember { mutableStateOf(MapType.NORMAL) }
var mapProperties by remember {
mutableStateOf(MapProperties(mapType = MapType.NORMAL))
}
// val placeState =
// rememberMarkerState(key = place.toString(), position = place)
val currentLocationState = remember(currentLocation) {
currentLocation?.let {
MarkerState(
LatLng(
it.latitude,
it.longitude
)
)
}
}
Box(
modifier = modifier,
) {
Column(
modifier = Modifier.fillMaxSize()
) {
MapTypeSelector(
currentValue = currentMapType,
modifier = Modifier.fillMaxWidth(),
) {
mapProperties = mapProperties.copy(mapType = it)
currentMapType = it
}
GoogleMap(
cameraPositionState = cameraPositionState,
onMapLoaded = {
mapLoaded = true
},
properties = mapProperties,
modifier = Modifier
.fillMaxWidth()
.weight(1f),
) {
currentLocationState?.let {
MarkerInfoWindowContent(
// state = placeState,
// title = placeDescription,
// onClick = {
// placeState.showInfoWindow()
// true
// }
state = it,
anchor = Offset(0.5f, 0.5f),
title = stringResource(
id = R.string.current_location
),
)
}
}
}
if (!mapLoaded) {
AnimatedVisibility(
visible = true,
modifier = Modifier.fillMaxSize(),
enter = EnterTransition.None,
exit = fadeOut()
) {
CircularProgressIndicator(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.wrapContentSize()
)
}
}
}
}
CHANGED: app/src/main/java/com/androidbyexample/compose/google/google/maps/MainActivity.kt
package com.androidbyexample.compose.google.google.maps
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.Looper
import android.provider.Settings
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
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.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.app.ActivityCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.androidbyexample.compose.google.google.maps.ui.theme.GoogleMapsTheme
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.location.FusedLocationProviderClient
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 com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.rememberCameraPositionState
class MainActivity : ComponentActivity() {
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
private val viewModel: CarViewModel by viewModels()
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
viewModel.updateLocation(locationResult.lastLocation)
}
}
private val getLocationPermission =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { isGranted ->
if (isGranted.values.any { it }) {
startLocationAndMap()
} else {
// if the user denied permissions, tell them they
// cannot use the app without them. In general,
// you should try to just reduce function and let the
// user continue, but location is a key part of this
// application.
// (Note that a real version of this application
// might allow the user to manually click on the map
// to set their current location, and we wouldn't
// show this dialog, or perhaps only show it once)
// NOTE: This is a normal Android-View-based dialog, not a compose one!
AlertDialog.Builder(this)
.setTitle("Permissions Needed")
.setMessage(
"We need coarse-location or fine-location permission " +
"to locate a car (fine location is highly " +
"recommended for accurate car locating). " +
"Please allow these permissions via App Info " +
"settings")
.setCancelable(false)
.setNegativeButton("Quit") { _, _ -> finish() }
.setPositiveButton("App Info") { _, _ ->
startActivity(
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS
).apply {
data = Uri.parse("package:$packageName")
}
)
finish()
}
.show()
}
}
@SuppressLint("MissingPermission")
fun startLocationAndMap() {
val locationRequest =
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
.setWaitForAccurateLocation(false)
.setMinUpdateIntervalMillis(0)
.setMaxUpdateDelayMillis(5000)
.build()
fusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(this)
fusedLocationProviderClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
enableEdgeToEdge()
setContent {
GoogleMapsTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
val googleHQ = LatLng(37.42423291057923, -122.08811454627153)
val defaultCameraPosition = CameraPosition.fromLatLngZoom(googleHQ, 11f)
val cameraPositionState = rememberCameraPositionState {
position = defaultCameraPosition
}
val currentLocation by viewModel.currentLocation.collectAsStateWithLifecycle(
initialValue = null
)
GoogleMapDisplay(
// place = googleHQ,
// placeDescription = "Google HQ",
currentLocation = currentLocation,
cameraPositionState = cameraPositionState,
modifier = Modifier.padding(innerPadding).fillMaxSize(),
)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GoogleApiAvailability.getInstance()
.makeGooglePlayServicesAvailable(this)
.addOnSuccessListener {
if (ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
getLocationPermission.launch(
arrayOf(
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
)
)
} else {
startLocationAndMap()
}
}.addOnFailureListener(this) {
Toast.makeText(
this,
"Google Play services required (or upgrade required)",
Toast.LENGTH_SHORT
).show()
finish()
}
}
}
CHANGED: app/src/main/res/values/strings.xml
<resources>
<string name="app_name">Google Maps</string>
<string name="map_type">Map Type</string>
<string name="current_location">Current Location</string>
</resources>
CHANGED: gradle/libs.versions.toml
[versions]
agp = "8.7.3"
kotlin = "2.0.21"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.9.3"
composeBom = "2024.12.01"
secrets = "2.0.1"
//
maps-compose = "6.2.1"
location-services = "21.3.0"
lifecycle-runtime-compose = "2.8.7"
[libraries]
location-services = { group = "com.google.android.gms", name = "play-services-location", version.ref = "location-services" }
lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle-runtime-compose" }
maps-compose = { group = "com.google.maps.android", name = "maps-compose", version.ref = "maps-compose" }
maps-compose-utils = { group = "com.google.maps.android", name = "maps-compose-utils", version.ref = "maps-compose" }
//
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" }