Google Map

Manage Car Location

We need to keep track of our car across application runs. To do this, we'll use a Preferences Datastore to store the latitude and longitude of the car.

The Datastore API exposes the data from the preference as a Kotlin Flow; whenever the preference is updated, the new value is emitted. We'll expose the car location to the UI from the view model, allowing new values to be collected. The preferences data store is a simple file that stores values. This is useful when a database would be overkill for storing small amounts of data. The datastore is typically used to hold simple application state across application runs.

We start by Adding the Datastore Dependency. This allows us to store and load the car's location.

  • carLatLng is a Flow created by mapping the data store's Flow into a LatLng instance. If either latitude or longitude are missing, a null is emitted.
  • clearCarLocation removes the latitude and longitude from the data store.
  • setCarLocation stores the current location's latitude and longitude in datastore (or removes them if there is no current location)

When clearCarLocation or setCarLocation change the latitude/longitude in the data store, new data is emitted to the data store Flow, which will trigger the carLatLng to emit a new LatLng or null.

The state in the GoogleMapDisplay is similar to the current location setup.

Code Changes

CHANGED: /app/build.gradle.kts
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.kotlinAndroid)
alias(libs.plugins.secrets)
} kotlin { jvmToolchain(17) } android { namespace = "com.androidbyexample.googlemap" compileSdk = 34 defaultConfig { applicationId = "com.androidbyexample.googlemap" minSdk = 24 targetSdk = 34 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.3" } packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } } dependencies {
implementation(libs.datastore.preferences)
// implementation(libs.icons.extended)
implementation(libs.location.services) implementation(libs.runtime.compose)
implementation(libs.maps.compose) implementation(libs.maps.compose.utils)
implementation(libs.core.ktx) implementation(libs.lifecycle.runtime.ktx) implementation(libs.activity.compose) implementation(platform(libs.compose.bom)) implementation(libs.ui) implementation(libs.ui.graphics) implementation(libs.ui.tooling.preview) implementation(libs.material3) testImplementation(libs.junit) androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.espresso.core) androidTestImplementation(platform(libs.compose.bom)) androidTestImplementation(libs.ui.test.junit4) debugImplementation(libs.ui.tooling) debugImplementation(libs.ui.test.manifest) }
CHANGED: /app/src/main/java/com/androidbyexample/googlemap/CarViewModel.kt
package com.androidbyexample.googlemap

import android.app.Application
import android.content.Contextimport android.location.Location
import androidx.datastore.core.DataStoreimport androidx.datastore.preferences.core.Preferencesimport androidx.datastore.preferences.core.editimport androidx.datastore.preferences.core.stringPreferencesKeyimport androidx.datastore.preferences.preferencesDataStoreimport androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScopeimport com.google.android.gms.maps.model.LatLngimport kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.mapimport kotlinx.coroutines.launchprivate val LAT_PREF = stringPreferencesKey("lat")private val LON_PREF = stringPreferencesKey("lon")
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 }
private val Context.preferencesDataStore: DataStore<Preferences> by preferencesDataStore(name = "carfinder") val carLatLng = application.preferencesDataStore.data.map { preferences -> preferences[LAT_PREF]?.let { latString -> preferences[LON_PREF]?.let { lonString -> LatLng(latString.toDouble(), lonString.toDouble()) } } } fun clearCarLocation() { viewModelScope.launch { getApplication<Application>().preferencesDataStore.edit { preferences -> preferences.remove(LAT_PREF) preferences.remove(LON_PREF) } } } fun setCarLocation() { viewModelScope.launch { _currentLocation.value?.let { location -> getApplication<Application>().preferencesDataStore.edit { preferences -> preferences[LAT_PREF] = location.latitude.toString() preferences[LON_PREF] = location.longitude.toString() } } ?: run { clearCarLocation() } } }
}
CHANGED: /app/src/main/java/com/androidbyexample/googlemap/MainActivity.kt
package com.androidbyexample.googlemap

import android.Manifest
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
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.result.contract.ActivityResultContracts
import androidx.activity.viewModels
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.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.app.ActivityCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.androidbyexample.googlemap.ui.theme.GoogleMapTheme
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.CameraUpdateFactory
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.CameraPosition
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.rememberCameraPositionState
import com.google.maps.android.compose.rememberMarkerStateimport kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    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() } }
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
GoogleApiAvailability.getInstance() .makeGooglePlayServicesAvailable(this) .addOnSuccessListener {
if (ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( this, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { getLocationPermission.launch( arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) ) } else { startLocationAndMap() }
}.addOnFailureListener(this) { Toast.makeText( this, "Google Play services required (or upgrade required)", Toast.LENGTH_SHORT ).show() finish() }
} @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() )
setContent { GoogleMapTheme { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) {
val cameraPositionState = rememberCameraPositionState()
val currentLocation by viewModel.currentLocation.collectAsStateWithLifecycle( initialValue = null )
val carLatLng by viewModel.carLatLng.collectAsStateWithLifecycle(initialValue = null)
GoogleMapDisplay( currentLocation = currentLocation, carLatLng = carLatLng, cameraPositionState = cameraPositionState, onSetCarLocation = viewModel::setCarLocation, onClearCarLocation = viewModel::clearCarLocation, modifier = Modifier.fillMaxSize(), )
} } } } }
@Composable fun GoogleMapDisplay( currentLocation: Location?, carLatLng: LatLng?, cameraPositionState: CameraPositionState, onSetCarLocation: () -> Unit, onClearCarLocation: () -> Unit, 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 currentLocationState = remember(currentLocation) { currentLocation?.let { MarkerState( LatLng( it.latitude, it.longitude ) ) } }
val carState = rememberMarkerState("car")
val context = LocalContext.current var currentLocationIcon by remember { mutableStateOf<BitmapDescriptor?>(null) } var carIcon by remember { mutableStateOf<BitmapDescriptor?>(null) } val scope = rememberCoroutineScope()
var initialBoundsSet by remember { mutableStateOf(false) } LaunchedEffect(key1 = currentLocation) { if (currentLocation != null) { if (!initialBoundsSet) { initialBoundsSet = true val current = LatLng(currentLocation.latitude, currentLocation.longitude) cameraPositionState.animate( CameraUpdateFactory.newLatLngZoom( current, 16f ), 1000 ) } } }
Scaffold( topBar = { CarTopBar( currentLocation = currentLocation, // carLatLng = null,// onSetCarLocation = { TODO() },// onClearCarLocation = { TODO() }, carLatLng = carLatLng, onSetCarLocation = onSetCarLocation, onClearCarLocation = onClearCarLocation, onWalkToCar = { TODO() }, onGoToCurrentLocation = { currentLocation?.let { curr -> scope.launch { cameraPositionState.animate( CameraUpdateFactory.newLatLngZoom( LatLng(curr.latitude, curr.longitude), 16f ), 1000 ) } } ?: Toast.makeText( context, "No current location available", Toast.LENGTH_LONG ).show() }, ) }, content = { paddingValues -> Box(
modifier = modifier.padding(paddingValues),
) {
Column( modifier = Modifier.fillMaxSize() ) { MapTypeSelector( currentValue = currentMapType, modifier = Modifier.fillMaxWidth(), ) { mapProperties = mapProperties.copy(mapType = it) currentMapType = it }
GoogleMap(
cameraPositionState = cameraPositionState,
onMapLoaded = { mapLoaded = true
scope.launch(Dispatchers.IO) { currentLocationIcon = context.loadBitmapDescriptor( R.drawable.ic_current_location ) carIcon = context.loadBitmapDescriptor( R.drawable.ic_car ) }
},
properties = mapProperties,
modifier = Modifier .fillMaxWidth() .weight(1f),
) {
currentLocationState?.let { MarkerInfoWindowContent( state = it,
icon = currentLocationIcon, anchor = Offset(0.5f, 0.5f),
title = stringResource( id = R.string.current_location ), ) }
carLatLng?.let { carState.position = it MarkerInfoWindowContent( state = carState, icon = carIcon, anchor = Offset(0.5f, 0.5f), title = stringResource( id = R.string.car_location ), ) } } } }
if (!mapLoaded) { AnimatedVisibility( visible = true, modifier = Modifier.fillMaxSize(), enter = EnterTransition.None, exit = fadeOut() ) { CircularProgressIndicator( modifier = Modifier .background(MaterialTheme.colorScheme.background) .wrapContentSize() ) } }
} ) }
ADDED: /app/src/main/res/drawable/ic_car.xml
<!--Car icon made by Vectors Market (https://www.flaticon.com/authors/vectors-market) from https://www.flaticon.com--><vector android:height="48dp" android:viewportHeight="496.474"    android:viewportWidth="496.474" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">    <path android:fillColor="#E95353" android:pathData="M489.544,269.628c-0.729,-14.739 -6.206,-28.858 -15.205,-40.572c-20.294,-26.422 -24.25,-47.399 -24.25,-47.399c-15.934,-41.751 -30.099,-69.57 -40.51,-87.226c-10.954,-18.541 -29.898,-31.03 -51.262,-33.776c-42.961,-5.554 -177.23,-5.554 -220.191,0c-21.349,2.762 -40.293,15.251 -51.247,33.776c-10.411,17.656 -24.56,45.475 -40.51,87.226c0,0 -3.956,20.977 -24.25,47.399c-8.983,11.714 -14.476,25.833 -15.189,40.572c-1.536,31.977 7.727,39.005 16.012,111.368c0.652,5.694 5.461,9.976 11.202,9.976h428.203c5.741,0 10.55,-4.298 11.202,-9.976c2.327,-20.294 9.232,-61.456 9.232,-61.456C488.83,299.076 490.149,282.397 489.544,269.628z"/>    <path android:fillColor="#168DE2" android:pathData="M441.074,179.982c-12.567,-31.449 -25.383,-58.911 -38.136,-80.803c-9.666,-16.555 -26.717,-28.113 -45.583,-30.642c-43.055,-5.71 -175.476,-5.663 -218.221,0c-18.866,2.529 -35.918,14.088 -45.584,30.642c-12.924,22.171 -25.91,50.005 -38.291,80.803C183.662,190.082 312.655,190.082 441.074,179.982z"/>    <path android:fillColor="#FFFFFF" android:pathData="M422.844,216.349c-4.903,1.536 -9.464,3.693 -13.281,5.834c-6.951,3.879 -14.088,7.494 -21.535,10.318c-12.35,4.686 -14.445,11.683 -14.445,11.683c-0.372,0.481 -0.729,0.962 -1.071,1.458c-9.371,13.917 2.932,32.442 19.549,30.084c24.033,-3.398 43.83,-10.364 53.884,-14.398c5.756,-2.312 10.255,-7.121 11.729,-13.157c0.14,-0.574 0.264,-1.148 0.372,-1.707C461.74,226.543 442.191,210.314 422.844,216.349z"/>    <path android:fillColor="#FFE21F" android:pathData="M448.522,327.857h-26.733c-4.018,0 -6.035,4.856 -3.196,7.711l26.733,26.733c2.839,2.839 7.711,0.822 7.711,-3.196v-26.733C453.036,329.874 451.019,327.857 448.522,327.857z"/>    <path android:fillColor="#454545" android:pathData="M473.547,380.965c-0.652,5.71 -5.477,10.007 -11.217,10.007h-51.433v29.851c0,10.566 8.564,19.146 19.146,19.146h32.303c10.566,0 19.146,-8.564 19.146,-19.146v-93.448C479.366,340.315 475.239,366.179 473.547,380.965z"/>    <path android:fillColor="#ED6262" android:pathData="M496.107,159.083c-1.552,-6.951 -8.083,-11.667 -15.205,-11.667h-12.909c-8.27,0 -14.972,6.703 -14.972,14.972v9.2h-6.842l3.925,9.325h15.05c7.789,0 15.5,-1.676 22.575,-4.918C494.105,173.078 497.674,166.096 496.107,159.083z"/>    <path android:fillColor="#FFBD49" android:pathData="M431.315,240.46m-18.82,0a18.82,18.82 0,1 1,37.64 0a18.82,18.82 0,1 1,-37.64 0"/>    <path android:fillColor="#FFDB6F" android:pathData="M393.458,255.308m-13.824,0a13.824,13.824 0,1 1,27.648 0a13.824,13.824 0,1 1,-27.648 0"/>    <path android:fillColor="#6F6F6F" android:pathData="M357.634,255.324l-21.613,37.639l-175.569,0l-21.613,-37.639z"/>    <path android:fillColor="#FFFFFF" android:pathData="M123.945,245.642c-0.326,-0.496 -0.683,-0.977 -1.071,-1.458c0,0 -2.095,-6.997 -14.445,-11.683c-7.447,-2.824 -14.569,-6.439 -21.535,-10.318c-3.832,-2.141 -8.378,-4.298 -13.281,-5.834c-19.332,-6.051 -38.896,10.193 -35.219,30.115c0.109,0.574 0.233,1.133 0.357,1.707c1.474,6.035 5.973,10.845 11.729,13.157c10.054,4.034 29.851,11 53.9,14.398C121.012,278.084 133.316,259.559 123.945,245.642z"/>    <path android:fillColor="#FFE21F" android:pathData="M74.684,327.857H47.951c-2.498,0 -4.515,2.017 -4.515,4.515v26.733c0,4.018 4.872,6.035 7.711,3.196l26.733,-26.733C80.719,332.729 78.702,327.857 74.684,327.857z"/>    <path android:fillColor="#777777" android:pathData="M426.226,374.511c-27.834,-48.159 -59.842,-46.654 -59.842,-46.654H130.073c0,0 -31.992,-1.505 -59.842,46.654H22.119l0.59,6.206c0.543,5.834 5.415,10.256 11.248,10.256h428.56c5.834,0 10.705,-4.422 11.249,-10.255l0.59,-6.206L426.226,374.511L426.226,374.511z"/>    <path android:fillColor="#454545" android:pathData="M34.143,390.988c-5.741,0 -10.566,-4.313 -11.217,-10.007c-1.691,-14.786 -5.834,-40.665 -7.944,-53.589v93.448c0,10.566 8.564,19.146 19.146,19.146H66.43c10.566,0 19.146,-8.564 19.146,-19.146v-29.851H34.143z"/>    <path android:fillColor="#ED6262" android:pathData="M43.436,171.588v-9.2c0,-8.27 -6.703,-14.972 -14.957,-14.972H15.571c-7.121,0 -13.653,4.717 -15.205,11.667c-1.567,6.997 2.017,13.995 8.378,16.896c7.09,3.243 14.786,4.918 22.575,4.918h15.05l3.925,-9.325h-6.858V171.588z"/>    <path android:fillColor="#FFBD49" android:pathData="M65.142,240.46m-18.82,0a18.82,18.82 0,1 1,37.64 0a18.82,18.82 0,1 1,-37.64 0"/>    <path android:fillColor="#FFDB6F" android:pathData="M103.03,255.308m-13.824,0a13.824,13.824 0,1 1,27.648 0a13.824,13.824 0,1 1,-27.648 0"/>    <path android:fillColor="#6F6F6F" android:pathData="M348.294,341.029H148.179c-3.134,0 -5.663,2.544 -5.663,5.663c0,3.134 2.529,5.663 5.663,5.663h200.099c3.134,0 5.663,-2.544 5.663,-5.663C353.957,343.558 351.412,341.029 348.294,341.029z"/>    <path android:fillColor="#6F6F6F" android:pathData="M348.294,363.635H148.179c-3.134,0 -5.663,2.544 -5.663,5.663c0,3.134 2.529,5.663 5.663,5.663h200.099c3.134,0 5.663,-2.544 5.663,-5.663C353.957,366.164 351.412,363.635 348.294,363.635z"/>    <path android:fillColor="#3AA2EB" android:pathData="M420.098,132.009v1.939c0,7.804 -6.408,14.057 -14.367,13.731c-7.339,-0.465 -12.955,-7.028 -12.955,-14.522v-27.834c0,-6.563 -5.461,-11.559 -12.024,-11.388c-0.155,0 -0.155,0 -0.31,0c0,0 0,0 -0.155,0c-6.563,-0.155 -12.024,4.841 -12.024,11.388v25.181c0,7.804 -6.718,14.057 -14.522,13.576c-7.339,-0.31 -12.955,-7.028 -12.955,-14.367v-21.892c0,-6.563 -5.461,-12.024 -12.179,-12.024h-0.931c-6.253,0 -11.404,5.151 -11.404,11.404v28.47c0,7.804 -6.563,14.041 -14.522,13.731c-7.339,-0.465 -12.8,-7.028 -12.8,-14.522V96.712c0,-6.082 -5.306,-11.233 -11.559,-10.768h-0.931c-6.408,0 -11.543,5.151 -11.543,11.404v39.021c0,7.494 -6.082,13.731 -13.731,13.731c-7.339,0 -13.421,-5.927 -13.576,-13.265v-32.752c0,-7.494 -5.616,-14.041 -12.955,-14.522c-7.959,-0.31 -14.367,5.927 -14.367,13.731l-0.155,30.255c0,7.804 -6.392,14.041 -14.367,13.731c-7.199,-0.45 -12.66,-6.78 -12.878,-13.948V97.829c0,-7.37 -5.539,-13.964 -12.909,-14.398c-7.897,-0.341 -14.414,5.88 -14.414,13.7v37.19c-0.217,7.618 -6.609,13.684 -14.445,13.374c-7.339,-0.465 -12.8,-7.028 -12.8,-14.522v-27.85c0,-6.563 -5.616,-11.559 -12.179,-11.388c-0.155,0 -0.155,0 -0.155,0c-0.155,0 -0.155,0 -0.155,0c-6.718,-0.155 -12.179,4.841 -12.179,11.388v25.181c0,7.804 -6.563,14.057 -14.367,13.576c-6.796,-0.264 -11.854,-5.942 -12.722,-12.521c-7.215,14.941 -14.367,31.123 -21.318,48.407c128.403,10.116 257.412,10.116 385.815,0C434.139,162.636 427.126,146.655 420.098,132.009z"/></vector>
CHANGED: /app/src/main/res/values/strings.xml
<resources>
    <string name="app_name">Car finder</string>
    <string name="map_type">Map Type</string>
    <string name="current_location">Current Location</string>
    <string name="go_to_current_location">Go to Current Location</string>
    <string name="remember_location">Remember Location</string>
    <string name="navigate">Walk to Car</string>
    <string name="forget_location">Forget Car Location</string>
    <string name="car_location">Car Location</string></resources>
CHANGED: /gradle/libs.versions.toml
[versions]
agp = "8.2.0-beta06"
kotlin = "1.9.10"
core-ktx = "1.12.0"
junit = "4.13.2"
androidx-test-ext-junit = "1.1.5"
espresso-core = "3.5.1"
lifecycle-runtime-ktx = "2.6.2"
activity-compose = "1.8.0"
compose-bom = "2023.10.00"
secrets = "2.0.1"
maps-compose = "3.0.0" # bug in 3.1.0 - map only takes up 50% of column
location-services = "21.0.1" runtime-compose = "2.6.2" icons-extended = "1.6.0-alpha06" # needed to bump to version that has source datastore-preferences = "1.0.0" [libraries] core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" } activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } ui = { group = "androidx.compose.ui", name = "ui" } ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } material3 = { group = "androidx.compose.material3", name = "material3" }
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" }
location-services = { group = "com.google.android.gms", name = "play-services-location", version.ref = "location-services" } runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "runtime-compose" } icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "icons-extended" } datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore-preferences" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets" }