|
| 1 | +--- |
| 2 | +name: places-android |
| 3 | +description: Guide for integrating the Places SDK for Android into an application. Use when users ask to add Places, autocomplete, place details, or search for places. |
| 4 | +--- |
| 5 | + |
| 6 | +# Places SDK for Android Integration |
| 7 | + |
| 8 | +You are an expert Android developer specializing in modern Android architecture. Before generating any code, ask the user the following questions to tailor the solution: |
| 9 | + |
| 10 | +### 📋 Design & Architectural Questions to Ask the User |
| 11 | + |
| 12 | +* **UI Framework**: Are you using Jetpack Compose or standard UI Views? |
| 13 | +* **Widget vs Custom UI**: Do you want to use the pre-built Google Autocomplete Widget (Dialog/Overlay) or build a completely custom programmatic search bar? |
| 14 | +* **Compact vs Full Details**: Do you want a compact half-sheet overlay (`PlaceDetailsCompactFragment`) or a full-page details viewer (`PlaceDetailsFragment`)? |
| 15 | +* **Cost & Field Scoping**: What exact fields do you need (e.g., `DISPLAY_NAME`, `FORMATTED_ADDRESS`, `PHOTO_METADATAS`)? Limiting fields saves costs! |
| 16 | +* **Theming Options**: Are you using Material 3 themes so we can bridge default styling automatically? |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +## 1. Setup Dependencies |
| 21 | + |
| 22 | +Add the necessary dependencies to your module-level `build.gradle.kts` file. It is recommended to use the Versions Catalog if available: |
| 23 | + |
| 24 | +```toml |
| 25 | +[versions] |
| 26 | +places = "5.1.1" # x-release-please-version |
| 27 | + |
| 28 | +[libraries] |
| 29 | +places = { group = "com.google.android.libraries.places", name = "places", version.ref = "places" } |
| 30 | +``` |
| 31 | + |
| 32 | +Then in `build.gradle.kts`: |
| 33 | + |
| 34 | +```kotlin |
| 35 | +dependencies { |
| 36 | + implementation(libs.places) |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +## 2. Setup the Secrets Gradle Plugin |
| 41 | + |
| 42 | +Use the Secrets Gradle Plugin for Android to inject the API key securely into your project (e.g., via `BuildConfig`), so you can access it programmatically during initialization. |
| 43 | + |
| 44 | +Ensure you have the plugin applied in your app-level `build.gradle.kts`: |
| 45 | + |
| 46 | +```kotlin |
| 47 | +plugins { |
| 48 | + alias(libs.plugins.secrets.gradle.plugin) |
| 49 | +} |
| 50 | + |
| 51 | +secrets { |
| 52 | + propertiesFileName = "secrets.properties" |
| 53 | + defaultPropertiesFileName = "local.defaults.properties" |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +Add your API Key to `secrets.properties`: |
| 58 | + |
| 59 | +```properties |
| 60 | +PLACES_API_KEY=YOUR_API_KEY |
| 61 | +``` |
| 62 | + |
| 63 | +## 3. Initialize the Places SDK |
| 64 | + |
| 65 | +In your `Application` or `Activity` (before accessing any Places APIs, usually inside `onCreate`), initialize the Places SDK. |
| 66 | + |
| 67 | +### Kotlin |
| 68 | + |
| 69 | +```kotlin |
| 70 | +import com.google.android.libraries.places.api.Places |
| 71 | + |
| 72 | +class DemoApplication : Application() { |
| 73 | + override fun onCreate() { |
| 74 | + super.onCreate() |
| 75 | + |
| 76 | + val apiKey = BuildConfig.PLACES_API_KEY |
| 77 | + if (apiKey.isNotEmpty()) { |
| 78 | + Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey) |
| 79 | + } |
| 80 | + } |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +### Java |
| 85 | + |
| 86 | +```java |
| 87 | +import com.google.android.libraries.places.api.Places; |
| 88 | + |
| 89 | +public class DemoApplication extends Application { |
| 90 | + @Override |
| 91 | + public void onCreate() { |
| 92 | + super.onCreate(); |
| 93 | + |
| 94 | + String apiKey = BuildConfig.PLACES_API_KEY; |
| 95 | + if (!apiKey.isEmpty()) { |
| 96 | + Places.initializeWithNewPlacesApiEnabled(getApplicationContext(), apiKey); |
| 97 | + } |
| 98 | + } |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +## 4. Best Practices & Guidelines |
| 103 | + |
| 104 | +* **Prefer Places UI Kit**: For displaying place details (photos, reviews, addresses), prefer using the **Places UI Kit** over manual programmatic retrieval. It provides pre-built, beautifully designed, and automatically maintained UI components! |
| 105 | +* **Null Safety & Validation**: Handle nulls defensively for optional parameters (e.g. Place fields). |
| 106 | +* **Scoped Fields**: Always specify *only* parameters that are needed (e.g. `Place.Field.ID`, `Place.Field.DISPLAY_NAME`) to avoid over-billing. |
| 107 | +* **Coroutine Extensions**: Use Kotlin Coroutines extensions (`places-ktx` if available) to make code cleaner. |
| 108 | +* **Location Permission**: Location permissions are optional but helpful. `ACCESS_COARSE_LOCATION` is sufficient for biasing prediction calls (like searching search results) to general cities. `ACCESS_FINE_LOCATION` is necessary only for exact current position tracking. Declare them in your `AndroidManifest.xml`: |
| 109 | + |
| 110 | + ```xml |
| 111 | + <manifest xmlns:android="http://schemas.android.com/apk/res/android" ...> |
| 112 | + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> |
| 113 | + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> |
| 114 | + </manifest> |
| 115 | + ``` |
| 116 | + |
| 117 | +## 5. Compose Interop with Places UI Kit |
| 118 | + |
| 119 | +The Places UI Kit (`PlaceDetailsCompactFragment` and `PlaceDetailsFragment`) are View-based. To use them in Jetpack Compose, use `AndroidView` to host a `FragmentContainerView`. |
| 120 | + |
| 121 | +### Key Pattern: Fragment Container in Compose |
| 122 | + |
| 123 | +* **Access FragmentManager**: Use standard `LocalActivity.current as FragmentActivity` to access the support FragmentManager. Avoid casting `LocalContext.current` directly to Activity. |
| 124 | +* **Deferred Updates**: Inside the `AndroidView` `update` block, always wrap calls (like `.loadWithPlaceId()`) in `view.post { ... }` to ensure updates run *after* the layout is inflated and bindings are stable. |
| 125 | + |
| 126 | +```kotlin |
| 127 | +@Composable |
| 128 | +fun PlaceDetailsCompactView( |
| 129 | + placeId: String, |
| 130 | + onDismiss: () -> Unit |
| 131 | +) { |
| 132 | + val fragmentContainerId = remember { View.generateViewId() } |
| 133 | + val fragmentManager = (LocalActivity.current as FragmentActivity).supportFragmentManager |
| 134 | + |
| 135 | + val fragment = remember { |
| 136 | + PlaceDetailsCompactFragment.newInstance( |
| 137 | + PlaceDetailsCompactFragment.ALL_CONTENT, |
| 138 | + Orientation.VERTICAL |
| 139 | + ) |
| 140 | + } |
| 141 | + |
| 142 | + Box(modifier = Modifier.fillMaxWidth()) { |
| 143 | + AndroidView( |
| 144 | + modifier = Modifier.fillMaxWidth(), |
| 145 | + factory = { context -> |
| 146 | + FragmentContainerView(context).apply { |
| 147 | + id = fragmentContainerId |
| 148 | + if (fragmentManager.findFragmentById(fragmentContainerId) == null) { |
| 149 | + fragmentManager.beginTransaction() |
| 150 | + .add(fragmentContainerId, fragment) |
| 151 | + .commit() |
| 152 | + } |
| 153 | + } |
| 154 | + }, |
| 155 | + update = { view -> |
| 156 | + // Ensures updates run after view hierarchy is ready |
| 157 | + view.post { |
| 158 | + fragment.loadWithPlaceId(placeId) |
| 159 | + } |
| 160 | + } |
| 161 | + ) |
| 162 | + } |
| 163 | +} |
| 164 | +``` |
| 165 | + |
| 166 | +## 📏 6. Advanced Compose Viewports & BottomSheetScaffold |
| 167 | + |
| 168 | +When hosting UI Kit fragments inside navigation drawers or overlays, follow these architectural bounds to avoid viewport clipping snags: |
| 169 | + |
| 170 | +* **System Viewport Edge Overlap**: If using `enableEdgeToEdge()` and your container loses standard `Scaffold` body padding context, manually append `Modifier.statusBarsPadding()` to avoid overlapping with system status bar text: |
| 171 | + ```kotlin |
| 172 | + Column(modifier = modifier.statusBarsPadding()) { |
| 173 | + // Beautiful search results sit safely under the status bar |
| 174 | + } |
| 175 | + ``` |
| 176 | +* **Unified Sheet Content State**: To achieve clean mutual exclusivity between "Compact" and "Full" details click toggles, hoist the viewport state to a high enum variable level: |
| 177 | + ```kotlin |
| 178 | + enum class DetailsUiType { COMPACT, FULL } |
| 179 | + ``` |
| 180 | + Track `currentUiType` at the Activity level and pass it to a single shared `BottomSheetScaffold`. Users can swipe to dismiss natively without custom button overrides! |
| 181 | + |
| 182 | +## 7. Autocomplete with Compose (Widget) |
| 183 | + |
| 184 | +To implement autocomplete in Compose, use `ActivityResultContracts.StartActivityForResult` with an Intent from `Autocomplete.IntentBuilder`. This is the recommended way to use the pre-built widget, as it handles session tokens and debouncing automatically. |
| 185 | + |
| 186 | +```kotlin |
| 187 | +@Composable |
| 188 | +fun AutocompleteSearchButton() { |
| 189 | + val context = LocalContext.current |
| 190 | + |
| 191 | + val fields = listOf(Place.Field.ID, Place.Field.DISPLAY_NAME, Place.Field.FORMATTED_ADDRESS) |
| 192 | + val intent = Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields) |
| 193 | + .build(context) |
| 194 | + |
| 195 | + val launcher = rememberLauncherForActivityResult( |
| 196 | + contract = ActivityResultContracts.StartActivityForResult() |
| 197 | + ) { result -> |
| 198 | + if (result.resultCode == Activity.RESULT_OK) { |
| 199 | + val place = Autocomplete.getPlaceFromIntent(result.data!!) |
| 200 | + Log.d("Autocomplete", "Place selected: ${place.name}") |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + Button(onClick = { launcher.launch(intent) }) { |
| 205 | + Text("Search Places") |
| 206 | + } |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | +## 8. Execution Steps |
| 211 | + |
| 212 | +1. Add the Places SDK dependencies to `build.gradle.kts`. |
| 213 | +2. Set up the Secrets Gradle Plugin in `build.gradle.kts`. |
| 214 | +3. Implement initialization (e.g., in a subclass of `Application`). |
| 215 | +4. Provide a summary of how to use it (retrieve place details, display autocomplete). |
0 commit comments