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 aFlow
created by mapping the data store'sFlow
into aLatLng
instance. If either latitude or longitude are missing, anull
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() = _currentLocationfun 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 {} @SuppressLint("MissingPermission") fun startLocationAndMap() {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() }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 = truescope.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,carLatLng?.let { carState.position = it MarkerInfoWindowContent( state = carState, icon = carIcon, anchor = Offset(0.5f, 0.5f), title = stringResource( id = R.string.car_location ), ) } } } }icon = currentLocationIcon, 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() ) } }} ) }
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 columnlocation-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" }