From 7e86ac92f1bc76955d08110eff0a7b40c97901bf Mon Sep 17 00:00:00 2001 From: dkhawk <107309+dkhawk@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:21:30 -0600 Subject: [PATCH] fix: moves the key function call to inside of the GoogleMap composable feat: update to Places SDK 4.0.0. remove places-ktx dependency chore: clean up log statements and unused code --- gradle/libs.versions.toml | 30 ++-- places-compose-demo/build.gradle.kts | 13 -- .../compose/demo/data/models/Country.kt | 3 + .../AddressValidationRepository.kt | 165 ------------------ .../data/repositories/CountriesRepository.kt | 4 + .../data/repositories/LocationProvider.kt | 3 +- .../repositories/MergedLocationRepository.kt | 26 +-- .../repositories/MockLocationRepository.kt | 28 +-- .../data/repositories/PlacesRepository.kt | 47 ++--- .../places/compose/demo/di/AppModule.kt | 18 +- .../autocomplete/AutocompleteViewModel.kt | 2 +- .../landmark/LandmarkSelectionViewModel.kt | 20 +-- .../landmark/NearbyLandmarksMap.kt | 54 +++--- .../addresshandlers/AddressTextField.kt | 3 +- .../addresshandlers/DisplayAddressMapper.kt | 2 - .../addresshandlers/in/IndiaAddressForm.kt | 16 +- .../addresshandlers/us/UsAddressForm.kt | 18 +- .../components/NearbyObjectsSelector.kt | 1 - .../PlacesAutocompleteMinimalActivity.kt | 2 +- 19 files changed, 104 insertions(+), 351 deletions(-) delete mode 100644 places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/AddressValidationRepository.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ad55d8..1093cca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ accompanistPermissions = "0.34.0" activityCompose = "1.9.2" agp = "8.6.1" appcompat = "1.7.0" -composeBom = "2024.09.01" +composeBom = "2024.09.02" coreKtx = "1.13.1" dokka = "1.9.20" espressoCore = "3.6.1" @@ -16,23 +16,23 @@ kotlin = "2.0.0" kotlinReflect = "2.0.0" kotlinxCoroutinesPlayServices = "1.8.1" ksp = "2.0.0-1.0.21" -lifecycleRuntimeKtx = "2.8.5" -lifecycleViewmodelCompose = "2.8.5" +lifecycleRuntimeKtx = "2.8.6" +lifecycleViewmodelCompose = "2.8.6" mapsCompose = "6.1.0" mapsUtilsKtx = "5.1.1" -materialVersion = "1.13.0-alpha05" -navigationCompose = "2.8.0" +materialVersion = "1.13.0-alpha06" +navigationCompose = "2.8.1" org-jacoco-core = "0.8.11" -places = "3.5.0" -placesKtx = "3.2.0" +places = "4.0.0" robolectric = "4.12.2" secretsGradlePlugin = "2.0.1" truth = "1.4.2" -uiToolingVersion = "1.7.1" -uiTestAndroid = "1.7.1" +uiTestAndroid = "1.7.2" +uiToolingVersion = "1.7.2" [libraries] accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" } +android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } @@ -46,16 +46,20 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3" androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-test-android = { group = "androidx.compose.ui", name = "ui-test-android", version.ref = "uiTestAndroid" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } dagger = { group = "com.google.dagger", name = "dagger", version.ref = "hiltVersion" } +dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } google-truth = { group = "com.google.truth", name = "truth", version.ref = "truth" } gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hiltVersion" } hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltVersion" } +jacoco-android-plugin = { module = "com.mxalbert.gradle:jacoco-android", version.ref = "jacoco-plugin", version.require = "0.2.1" } junit = { group = "junit", name = "junit", version.ref = "junit" } +kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", version.ref = "kotlinReflect" } kotlinx-coroutines-play-services = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" } maps-compose = { group = "com.google.maps.android", name = "maps-compose", version.ref = "mapsCompose" } @@ -63,17 +67,11 @@ maps-compose-utils = { module = "com.google.maps.android:maps-compose-utils", ve maps-compose-widgets = { module = "com.google.maps.android:maps-compose-widgets", version.ref = "mapsCompose" } maps-utils-ktx = { module = "com.google.maps.android:maps-utils-ktx", version.ref = "mapsUtilsKtx" } material = { group = "com.google.android.material", name = "material", version.ref = "materialVersion" } +org-jacoco-core = { module = "org.jacoco:org.jacoco.core", version.ref = "org-jacoco-core" } places = { group = "com.google.android.libraries.places", name = "places", version.ref = "places" } -places-ktx = { group = "com.google.maps.android", name = "places-ktx", version.ref = "placesKtx" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } -androidx-ui-test-android = { group = "androidx.compose.ui", name = "ui-test-android", version.ref = "uiTestAndroid" } ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "uiToolingVersion" } ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "uiToolingVersion" } -jacoco-android-plugin = { module = "com.mxalbert.gradle:jacoco-android", version.ref = "jacoco-plugin", version.require = "0.2.1" } -org-jacoco-core = { module = "org.jacoco:org.jacoco.core", version.ref = "org-jacoco-core" } -kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } -android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } -dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/places-compose-demo/build.gradle.kts b/places-compose-demo/build.gradle.kts index 12e8c74..98025e2 100644 --- a/places-compose-demo/build.gradle.kts +++ b/places-compose-demo/build.gradle.kts @@ -81,20 +81,7 @@ dependencies { debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) - //////////////////////////////////////// - - // probably not needed... - // implementation(libs.androidx.material3.android) - - // probably needed... - // implementation(libs.kotlinx.coroutines.play.services) - - - // implementation(project(":maps-compose-widgets")) - // implementation(libs.androidx.foundation) - implementation(libs.places) - implementation(libs.places.ktx) implementation(libs.kotlin.reflect) implementation(libs.androidx.navigation.compose) diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/models/Country.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/models/Country.kt index 1c344da..8380615 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/models/Country.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/models/Country.kt @@ -13,4 +13,7 @@ // limitations under the License. package com.google.android.libraries.places.compose.demo.data.models +/** + * Holds a county with its name, country code, and flag in unicode. + */ data class Country(val name: String, val flag: String, val code: String) \ No newline at end of file diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/AddressValidationRepository.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/AddressValidationRepository.kt deleted file mode 100644 index de4aa2b..0000000 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/AddressValidationRepository.kt +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package com.google.android.libraries.places.compose.demo.data.repositories - -import com.android.volley.Request -import com.android.volley.RequestQueue -import com.android.volley.VolleyError -import com.android.volley.toolbox.JsonObjectRequest -import com.google.gson.Gson -import org.json.JSONObject -import java.net.HttpURLConnection -import java.nio.charset.StandardCharsets -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -class AddressValidationRepository( - private val apiKeyProvider: ApiKeyProvider, - private val requestQueue: RequestQueue -) { - private val gson = Gson() - - suspend fun validateAddress( - request: AddressValidationRequest - ): AddressValidationResponse { - val url = buildString { - append(BASE_URL) - append("?key=${apiKeyProvider.mapsApiKey}") - } - - return suspendCoroutine { continuation -> - val jsonObjectRequest = JsonObjectRequest( - Request.Method.POST, url, JSONObject(gson.toJson(request)), - { response -> - val addressValidationResponse = gson.fromJson(response.toString(), AddressValidationResponse::class.java) - continuation.resume(addressValidationResponse) - }, - { error -> - val errorMessage = if (error.networkResponse != null) { - String(error.networkResponse.data, StandardCharsets.UTF_8) - } else { - error.message ?: "Unknown error" - } - continuation.resumeWithException(AddressValidationException(error.networkResponse?.statusCode ?: 0, errorMessage)) - } - ) - - requestQueue.add(jsonObjectRequest) - } - } - - companion object { - const val BASE_URL = "https://addressvalidation.googleapis.com/v1:validateAddress" - } -} - -data class AddressValidationResponse( - val result: ValidationResult, - val responseId: String? = null // Optional unique identifier for the request -) { - - data class ValidationResult( - val verdict: Verdict, - val address: Address? = null, // Corrected/standardized address - val validationGranularity: Granularity? = null - ) { - - enum class Verdict { - UNSPECIFIED_VERDICT, // Default value, indicates an unknown status - VALIDATION_SUCCESS, - PARTIAL_MATCH, - NO_MATCH, // Address could not be matched - AMBIGUOUS_VERDICT // Address is too ambiguous to validate - } - - enum class Granularity { - SUB_PREMISE, // Most granular level (e.g., apartment number) - PREMISE, - BLOCK, - ROUTE, - LOCALITY, - POSTAL_CODE, - ADMINISTRATIVE_AREA, - COUNTRY, - UNKNOWN_GRANULARITY - } - - data class Address( - val revisedAddress: String? = null, // Fully revised address (if available) - val addressComponents: List = emptyList() - ) { - - data class AddressComponent( - val componentName: String, - val componentType: String - ) - } - } -} - -interface AddressValidationRequestInterface { - val regionCode: String - val addressLines: List - val locality: String -} - -data class AddressValidationRequest( - override val regionCode: String, - override val locality: String, - override val addressLines: List -): AddressValidationRequestInterface - -data class UsAddressValidationRequest( - override val regionCode: String, - override val addressLines: List, - override val locality: String, - val postalCode: String? = null, - val enableUspsCass: Boolean = false, -) : AddressValidationRequestInterface - -class AddressValidationException( - val responseCode: Int, - val errorMessage: String, - val errorType: ErrorType? = null -) : Exception("Address Validation Error: $responseCode - $errorMessage") { - - enum class ErrorType { - API_KEY_INVALID, - REQUEST_INVALID, - ADDRESS_NOT_FOUND, - QUOTA_EXCEEDED, - SERVER_ERROR, - UNKNOWN_ERROR - } - - companion object { - fun fromVolleyError(error: VolleyError): AddressValidationException { - val responseCode = error.networkResponse?.statusCode ?: 0 - val errorMessage = String(error.networkResponse?.data ?: "Unknown error".toByteArray(), StandardCharsets.UTF_8) - - val errorType = when (responseCode) { - HttpURLConnection.HTTP_UNAUTHORIZED -> ErrorType.API_KEY_INVALID - HttpURLConnection.HTTP_BAD_REQUEST -> ErrorType.REQUEST_INVALID - HttpURLConnection.HTTP_NOT_FOUND -> ErrorType.ADDRESS_NOT_FOUND - HttpURLConnection.HTTP_FORBIDDEN -> ErrorType.QUOTA_EXCEEDED - in 500..599 -> ErrorType.SERVER_ERROR - else -> ErrorType.UNKNOWN_ERROR - } - - return AddressValidationException(responseCode, errorMessage, errorType) - } - } -} - diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/CountriesRepository.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/CountriesRepository.kt index 1cf2e22..229c11a 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/CountriesRepository.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/CountriesRepository.kt @@ -17,6 +17,10 @@ import com.google.android.libraries.places.compose.demo.data.models.Country class CountriesRepository { companion object { + + /** + * Returns a flag emoji for the given country code. + */ private fun getFlagEmoji(countryCode: String): String { val firstLetter = Character.codePointAt(countryCode, 0) - 0x41 + 0x1F1E6 val secondLetter = Character.codePointAt(countryCode, 1) - 0x41 + 0x1F1E6 diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/LocationProvider.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/LocationProvider.kt index 8cbbe68..e7befba 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/LocationProvider.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/LocationProvider.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.WhileSubscribed import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn @@ -115,7 +116,7 @@ class LocationRepository(context: Context, private val scope: CoroutineScope) { return _locationUpdates.stateIn( scope = scope, - SharingStarted.WhileSubscribed(5000), + SharingStarted.WhileSubscribed(5.seconds), initialValue = Location("MockLocation") ) } diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/MergedLocationRepository.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/MergedLocationRepository.kt index 42b6ee3..4f0c771 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/MergedLocationRepository.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/MergedLocationRepository.kt @@ -2,7 +2,6 @@ package com.google.android.libraries.places.compose.demo.data.repositories import android.Manifest import android.annotation.SuppressLint -import android.util.Log import androidx.annotation.RequiresPermission import com.google.android.gms.maps.model.LatLng import kotlinx.coroutines.CoroutineScope @@ -13,7 +12,6 @@ import kotlinx.coroutines.flow.WhileSubscribed import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.shareIn import javax.inject.Inject import kotlin.time.Duration.Companion.seconds @@ -24,6 +22,9 @@ data class CompositeLocation( val isMockLocation: Boolean = true, ) +/** + * A repository class that allows switching between the actual device location and a mock location. + */ class MergedLocationRepository @Inject constructor( @@ -33,31 +34,11 @@ constructor( ) { private val _useMockLocation = MutableStateFlow(true) - val mergedLocation = merge( - locationRepository.latestLocation.mapNotNull { latLng -> - latLng?.let { - CompositeLocation( - latLng = it, - label = "Current Location", - isMockLocation = false, - ) - } - }, - mockLocationRepository.location.map { - CompositeLocation( - latLng = it.latLng, - label = it.label, - isMockLocation = true, - ) - }, - ) - @SuppressLint("MissingPermission") @ExperimentalCoroutinesApi @RequiresPermission(allOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION]) val location = _useMockLocation.flatMapLatest { useMockLocation -> if (useMockLocation) { - Log.d("MergedLocationRepository", "Emitting mock location") mockLocationRepository.location.map { CompositeLocation( latLng = it.latLng, @@ -66,7 +47,6 @@ constructor( ) } } else { - Log.d("MergedLocationRepository", "Emitting system location") locationRepository.latestLocation.mapNotNull { latLng -> latLng?.let { CompositeLocation( diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/MockLocationRepository.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/MockLocationRepository.kt index 2956b08..70a28d7 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/MockLocationRepository.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/MockLocationRepository.kt @@ -1,30 +1,23 @@ package com.google.android.libraries.places.compose.demo.data.repositories import com.google.android.gms.maps.model.LatLng -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.stateIn data class LabeledLocation( val latLng: LatLng, val label: String, ) -class MockLocationRepository(scope: CoroutineScope) { +class MockLocationRepository { private val _mockLocationNumber = MutableStateFlow(0) private val mockLocationNumber: StateFlow = _mockLocationNumber.asStateFlow() - // TODO: remove this - private val mockLocationFlow = MutableStateFlow("" to LatLng(0.0, 0.0)) - @OptIn(ExperimentalCoroutinesApi::class) private val mockLocation = mockLocationNumber.mapLatest { LabeledLocation( @@ -35,14 +28,12 @@ class MockLocationRepository(scope: CoroutineScope) { private val userClickedLocation = MutableStateFlow(null) - // KEEP THIS ONE! val location = merge(mockLocation, userClickedLocation).mapNotNull { location -> location } fun setMockLocation(location: LatLng) { userClickedLocation.value = LabeledLocation(location, "User selected") - mockLocationFlow.value = "User selected" to location } fun nextMockLocation(): Pair { @@ -50,23 +41,6 @@ class MockLocationRepository(scope: CoroutineScope) { return locations[_mockLocationNumber.value] } - val labeledLocation: StateFlow> = mockLocationNumber.map { index -> - locations[index] - }.stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = locations[0] - ) - - @OptIn(ExperimentalCoroutinesApi::class) - val selectedMockLocation = labeledLocation.mapLatest { location -> - location.second - }.stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = locations[0].second - ) - companion object { val mockLocations = listOf( "Boulder, CO" to LatLng(40.01924246438453, -105.259858527573), diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/PlacesRepository.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/PlacesRepository.kt index 2ec99af..05224c6 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/PlacesRepository.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/data/repositories/PlacesRepository.kt @@ -16,10 +16,9 @@ package com.google.android.libraries.places.compose.demo.data.repositories import androidx.compose.runtime.mutableStateMapOf import com.google.android.libraries.places.api.model.Place import com.google.android.libraries.places.api.net.PlacesClient -import com.google.android.libraries.places.ktx.api.net.awaitFetchPlace +import com.google.android.libraries.places.api.net.kotlin.awaitFetchPlace import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import kotlinx.coroutines.ExperimentalCoroutinesApi /** * Repository for managing places data. @@ -31,40 +30,46 @@ class PlaceRepository( ) { private val placesIdsWithLocations = mutableStateMapOf() - @OptIn(ExperimentalCoroutinesApi::class) suspend fun getPlaceLatLng(placeId: String): Pair { - return placeId to placesIdsWithLocations.getOrElse(placeId) { - withContext(Dispatchers.IO) { - val place = placesClient.awaitFetchPlace(placeId, listOf(Place.Field.LAT_LNG)).place - withContext(Dispatchers.Main) { - placesIdsWithLocations[placeId] = place + return withContext(Dispatchers.Main) { + placeId to placesIdsWithLocations.getOrElse(placeId) { + withContext(Dispatchers.IO) { + placesClient.awaitFetchPlace( + placeId = placeId, + placeFields = listOf(Place.Field.LOCATION) + ).place.also { place -> + withContext(Dispatchers.Main) { + placesIdsWithLocations[placeId] = place + } + } } - place } } } private val placesIdsWithAddresses = mutableStateMapOf() - @OptIn(ExperimentalCoroutinesApi::class) suspend fun getPlaceAddress(placeId: String): Place { val placeFields = listOf( Place.Field.ID, Place.Field.ADDRESS_COMPONENTS, - Place.Field.ADDRESS, - Place.Field.LAT_LNG + Place.Field.FORMATTED_ADDRESS, + Place.Field.LOCATION, ) - if (placesIdsWithAddresses.containsKey(placeId)) { - return placesIdsWithAddresses.getValue(placeId) - } - - return withContext(Dispatchers.IO) { - val place = placesClient.awaitFetchPlace(placeId, placeFields).place - withContext(Dispatchers.Main) { - placesIdsWithAddresses[placeId] = place + return withContext(Dispatchers.Main) { + placesIdsWithAddresses.getOrElse(placeId) { + withContext(Dispatchers.IO) { + placesClient.awaitFetchPlace( + placeId = placeId, + placeFields = placeFields + ).place.also { place -> + withContext(Dispatchers.Main) { + placesIdsWithAddresses[placeId] = place + } + } + } } - place } } } diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/di/AppModule.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/di/AppModule.kt index a0283f3..7708ea3 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/di/AppModule.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/di/AppModule.kt @@ -22,7 +22,6 @@ import com.google.android.libraries.places.api.Places import com.google.android.libraries.places.api.net.PlacesClient import com.google.android.libraries.places.compose.autocomplete.repositories.AutocompleteRepository import com.google.android.libraries.places.compose.demo.PlacesComposeDemoApplication -import com.google.android.libraries.places.compose.demo.data.repositories.AddressValidationRepository import com.google.android.libraries.places.compose.demo.data.repositories.ApiKeyProvider import com.google.android.libraries.places.compose.demo.data.repositories.CountriesRepository import com.google.android.libraries.places.compose.demo.data.repositories.GeocoderRepository @@ -104,31 +103,16 @@ object AppModule { return Volley.newRequestQueue(application.applicationContext) } - @Provides - @Singleton - fun provideAddressValidationRepository( - apiKeyProvider: ApiKeyProvider, - requestQueue: RequestQueue - ): AddressValidationRepository { - return AddressValidationRepository( - apiKeyProvider = apiKeyProvider, - requestQueue = requestQueue - ) - } - @Provides @Singleton fun provideCountriesRepository(): CountriesRepository { return CountriesRepository() } - @OptIn(DelicateCoroutinesApi::class) @Provides @Singleton fun provideMockLocationRepository(application: Application): MockLocationRepository { - return MockLocationRepository( - (application as PlacesComposeDemoApplication).applicationScope - ) + return MockLocationRepository() } @OptIn(DelicateCoroutinesApi::class) diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/autocomplete/AutocompleteViewModel.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/autocomplete/AutocompleteViewModel.kt index f00726a..35e642b 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/autocomplete/AutocompleteViewModel.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/autocomplete/AutocompleteViewModel.kt @@ -99,7 +99,7 @@ constructor( @OptIn(ExperimentalCoroutinesApi::class) val selectedPlaceWithLocation = selectedPlace.mapLatest { selectedPlace -> selectedPlace?.let { place -> - placesRepository.getPlaceLatLng(place.placeId).second.latLng?.let { latLng -> + placesRepository.getPlaceLatLng(place.placeId).second.location?.let { latLng -> place.copy(latLng = latLng) } } diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/LandmarkSelectionViewModel.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/LandmarkSelectionViewModel.kt index 9b7b591..1e3bfe4 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/LandmarkSelectionViewModel.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/LandmarkSelectionViewModel.kt @@ -14,7 +14,6 @@ package com.google.android.libraries.places.compose.demo.presentation.landmark import android.annotation.SuppressLint -import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -44,7 +43,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import javax.inject.Inject import kotlin.time.Duration.Companion.seconds @@ -71,21 +69,17 @@ class LandmarkSelectionViewModel private var selectedNearbyObject by mutableStateOf(null) @OptIn(ExperimentalCoroutinesApi::class) - val location = mergedLocationRepository.location.map { it.latLng }.onEach { - Log.e("LandmarkSelectionViewModel", "Location changed: $it") - }.stateIn( + val location = mergedLocationRepository.location.map { it.latLng }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5.seconds), initialValue = LatLng(0.0, 0.0) ) private val geocoderResult = location.filterNotNull().mapNotNull { location -> - Log.e("LandmarkSelectionViewModel", "Geocoding location: $location") geocoderRepository.reverseGeocode(location, includeAddressDescriptors = true) } private val nearbyObjects = geocoderResult.mapNotNull { result -> - Log.e("LandmarkSelectionViewModel", "Nearby objects: ${result.addressDescriptor}") result.addressDescriptor?.toNearbyObjects() }.stateIn( scope = viewModelScope, @@ -95,13 +89,9 @@ class LandmarkSelectionViewModel @OptIn(ExperimentalCoroutinesApi::class) val nearbyObjectsWithLatLngs = nearbyObjects.mapLatest { nearbyObjects -> - Log.e("LandmarkSelectionViewModel", "Getting latlngs for nearby objects: $nearbyObjects") - - val places = nearbyObjects.map { nearbyObject -> + nearbyObjects.map { nearbyObject -> viewModelScope.async { placesRepository.getPlaceLatLng(nearbyObject.placeId) } - } - - places.awaitAll().map { place -> + }.awaitAll().map { place -> nearbyObjects.first { address -> address.placeId == place.first } to place.second } }.stateIn( @@ -128,9 +118,7 @@ class LandmarkSelectionViewModel nearbyObjectsWithLatLngs.filter{ it.first is NearbyObject.NearbyLandmark }.mapNotNull { (nearbyObject, place) -> - Log.e("LandmarkSelectionViewModel", "Getting marker for nearby object: $nearbyObject") - Log.e("LandmarkSelectionViewModel", "Getting place: ${place.name} ${place.latLng} ${place.id}") - place.latLng?.let { latLng -> + place.location?.let { latLng -> LandmarkMarker( landmark = nearbyObject as NearbyObject.NearbyLandmark, latLng = latLng, diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/NearbyLandmarksMap.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/NearbyLandmarksMap.kt index 1c63bf8..beb9264 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/NearbyLandmarksMap.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/NearbyLandmarksMap.kt @@ -24,20 +24,33 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.GenericShape import androidx.compose.material3.Card +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.PinConfig import com.google.android.libraries.places.compose.demo.R import com.google.android.libraries.places.compose.demo.ui.theme.AndroidPlacesComposeDemoTheme import com.google.maps.android.compose.AdvancedMarker @@ -45,19 +58,6 @@ import com.google.maps.android.compose.CameraPositionState import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.MarkerComposable import com.google.maps.android.compose.MarkerState -import androidx.compose.material3.Icon -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.res.painterResource -import com.google.android.gms.maps.CameraUpdateFactory -import com.google.android.gms.maps.model.PinConfig @Composable fun NearbyLandmarksMap( @@ -95,21 +95,19 @@ fun NearbyLandmarksMap( .fillMaxWidth() .padding(16.dp), ) { - // TODO: remove the key - key(landmarkMarkers) { - GoogleMap( - modifier = Modifier.fillMaxSize(), - cameraPositionState = cameraPositionState, - onMapClick = onMapClick, - googleMapOptionsFactory = { - GoogleMapOptions().mapId(mapId) - }, - ) { - AdvancedMarker( - state = userMarker, - zIndex = 2f - ) - + GoogleMap( + modifier = Modifier.fillMaxSize(), + cameraPositionState = cameraPositionState, + onMapClick = onMapClick, + googleMapOptionsFactory = { + GoogleMapOptions().mapId(mapId) + }, + ) { + AdvancedMarker( + state = userMarker, + zIndex = 2f + ) + key(landmarkMarkers) { if (showAsMarkers) { landmarkMarkers.forEach { landmarkMarker -> val (pinConfig, zIndex) = if (landmarkMarker.landmark.placeId == selectedPlaceId) { diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/AddressTextField.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/AddressTextField.kt index 3c3b5bc..1a366a5 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/AddressTextField.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/AddressTextField.kt @@ -26,8 +26,7 @@ fun AddressTextField( value: String, @StringRes label: Int, modifier: Modifier = Modifier, - readonly: Boolean = false, - leadingIcon: @Composable (() -> Unit)? = null, + leadingIcon: @Composable() (() -> Unit)? = null, onValueChange: ((String) -> Unit)? = null, onFocusChanged: ((Boolean) -> Unit)? = null, ) { diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/DisplayAddressMapper.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/DisplayAddressMapper.kt index bb361cd..74a5060 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/DisplayAddressMapper.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/DisplayAddressMapper.kt @@ -13,7 +13,6 @@ // limitations under the License. package com.google.android.libraries.places.compose.demo.presentation.landmark.addresshandlers -import android.util.Log import com.google.android.libraries.places.compose.autocomplete.models.Address import com.google.android.libraries.places.compose.demo.presentation.landmark.addresshandlers.`in`.toIndiaDisplayAddress import com.google.android.libraries.places.compose.demo.presentation.landmark.addresshandlers.us.toUsDisplayAddress @@ -24,7 +23,6 @@ fun Address.toDisplayAddress(): DisplayAddress { "US" -> this.toUsDisplayAddress() "IN" -> this.toIndiaDisplayAddress() else -> { - Log.e("Address", "Unsupported country code: $countryCode") // Fallback to US address format this.toUsDisplayAddress() } diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/in/IndiaAddressForm.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/in/IndiaAddressForm.kt index 5771d0e..9586832 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/in/IndiaAddressForm.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/in/IndiaAddressForm.kt @@ -41,16 +41,16 @@ fun IndiaAddressForm( verticalArrangement = Arrangement.spacedBy(16.dp) ) { AddressTextField( - modifier = Modifier.fillMaxWidth(), value = address.aptSuiteUnit, label = R.string.india_address_unit_number, + modifier = Modifier.fillMaxWidth(), onValueChange = { onAddressChanged?.invoke(address.copy(aptSuiteUnit = it)) }, ) AddressTextField( - modifier = Modifier.fillMaxWidth(), value = address.streetAddress, label = R.string.india_address_street_address, + modifier = Modifier.fillMaxWidth(), onValueChange = { onAddressChanged?.invoke(address.copy(streetAddress = it)) }, ) @@ -63,9 +63,9 @@ fun IndiaAddressForm( } AddressTextField( - modifier = Modifier.fillMaxWidth(), value = address.city, label = R.string.india_address_city, + modifier = Modifier.fillMaxWidth(), onValueChange = { onAddressChanged?.invoke(address.copy(city = it)) }, ) @@ -75,17 +75,17 @@ fun IndiaAddressForm( ) { AddressTextField( - modifier = Modifier.weight(1f), value = address.state, label = R.string.india_address_state, + modifier = Modifier.weight(1f), onValueChange = { onAddressChanged?.invoke(address.copy(state = it)) }, ) AddressTextField( - modifier = Modifier.weight(1f), - value = address.pinCode, - label = R.string.india_address_pincode, - onValueChange = { onAddressChanged?.invoke(address.copy(pinCode = it)) }, + value = address.pinCode, + label = R.string.india_address_pincode, + modifier = Modifier.weight(1f), + onValueChange = { onAddressChanged?.invoke(address.copy(pinCode = it)) }, ) } diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/us/UsAddressForm.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/us/UsAddressForm.kt index 0a5ca7c..65aa38d 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/us/UsAddressForm.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/addresshandlers/us/UsAddressForm.kt @@ -49,27 +49,27 @@ fun UsAddressForm( ) { AddressTextField( - modifier = Modifier.fillMaxWidth(), value = address.streetAddress, label = R.string.us_address_address, + modifier = Modifier.fillMaxWidth(), onValueChange = onAddressChanged?.let { { onAddressChanged.invoke(address.copy(streetAddress = it)) } } ) AddressTextField( - modifier = Modifier.fillMaxWidth(), value = address.additionalAddressInfo, label = R.string.us_address_suite_unit_floor, + modifier = Modifier.fillMaxWidth(), onValueChange = onAddressChanged?.let { { onAddressChanged.invoke(address.copy(additionalAddressInfo = it)) } }, ) AddressTextField( - modifier = Modifier.fillMaxWidth(), value = address.city, label = R.string.us_address_city, + modifier = Modifier.fillMaxWidth(), onValueChange = onAddressChanged?.let { { onAddressChanged.invoke(address.copy(city = it)) } }, @@ -80,17 +80,17 @@ fun UsAddressForm( horizontalArrangement = Arrangement.spacedBy(16.dp) ) { AddressTextField( - modifier = Modifier.weight(1f), value = address.state, label = R.string.us_address_state, + modifier = Modifier.weight(1f), onValueChange = onAddressChanged?.let { { onAddressChanged.invoke(address.copy(state = it)) } }, ) AddressTextField( - modifier = Modifier.weight(1f), value = address.zipCode, label = R.string.us_address_zip, + modifier = Modifier.weight(1f), onValueChange = onAddressChanged?.let { { onAddressChanged.invoke(address.copy(zipCode = it)) } }, @@ -106,14 +106,14 @@ fun UsAddressForm( } AddressTextField( - modifier = Modifier.fillMaxWidth(), value = address.country, label = R.string.us_address_country, - onValueChange = onAddressChanged?.let { - { onAddressChanged.invoke(address.copy(country = it)) } - }, + modifier = Modifier.fillMaxWidth(), leadingIcon = CountriesRepository.countries[address.countryCode]?.flag?.let { { Text(it) } + }, + onValueChange = onAddressChanged?.let { + { onAddressChanged.invoke(address.copy(country = it)) } } ) } diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/components/NearbyObjectsSelector.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/components/NearbyObjectsSelector.kt index a78ca27..c5c13e8 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/components/NearbyObjectsSelector.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/landmark/components/NearbyObjectsSelector.kt @@ -64,7 +64,6 @@ fun NearbyObjectsSelector( titleId = R.string.nearby_landmarks, nearbyObjects = landmarks, onNearbyObjectSelected = { - Log.d("NearbyObjectsSelector", "Nearby landmark selected: ${it.name}") onNearbyLandmarkSelected(it.placeId) }, selectedPlaceId = selectedPlaceId, diff --git a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/minimal/PlacesAutocompleteMinimalActivity.kt b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/minimal/PlacesAutocompleteMinimalActivity.kt index 67ab7ad..70d803b 100644 --- a/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/minimal/PlacesAutocompleteMinimalActivity.kt +++ b/places-compose-demo/src/main/java/com/google/android/libraries/places/compose/demo/presentation/minimal/PlacesAutocompleteMinimalActivity.kt @@ -38,6 +38,7 @@ import com.google.android.gms.maps.model.LatLng import com.google.android.libraries.places.api.Places import com.google.android.libraries.places.api.model.PlaceTypes import com.google.android.libraries.places.api.model.RectangularBounds +import com.google.android.libraries.places.api.net.kotlin.awaitFindAutocompletePredictions import com.google.android.libraries.places.compose.autocomplete.components.PlacesAutocompleteTextField import com.google.android.libraries.places.compose.autocomplete.data.LocalUnitsConverter import com.google.android.libraries.places.compose.autocomplete.data.Meters @@ -51,7 +52,6 @@ import com.google.android.libraries.places.compose.demo.data.repositories.Geocod import com.google.android.libraries.places.compose.demo.data.repositories.LocationRepository import com.google.android.libraries.places.compose.demo.presentation.landmark.GetLocationPermission import com.google.android.libraries.places.compose.demo.ui.theme.AndroidPlacesComposeDemoTheme -import com.google.android.libraries.places.ktx.api.net.awaitFindAutocompletePredictions import com.google.maps.android.ktx.utils.withSphericalOffset import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi