A modern, drop-in replacement for the long-deprecated Google Place Picker. Built on
the Places SDK for Android (New) β searchNearby for the picker grid,
programmatic autocomplete for the search bar, and a Coil-loaded photo preview in
the confirmation dialog.
- Places SDK 5.x (New) β uses
SearchNearbyRequest,FindAutocompletePredictionsRequest,FetchPlaceRequest, andFetchResolvedPhotoUriRequest - Coroutines / Flow end to end β no RxJava, no
LiveData, noAsyncTask - ActivityResultContract API β no
startActivityForResult, no static result callbacks - min SDK 24, AGP 9, Kotlin 2.3, Gradle 9.4
Add JitPack to your settings:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven(url = "https://jitpack.io")
}
}Add the dependency:
// app/build.gradle.kts
dependencies {
implementation("com.google.android.libraries.places:places:5.1.1")
implementation("com.github.rtchagas:pingplacepicker:<latest-version>")
}- Enable APIs in Google Cloud Console on the project tied to your keys:
- Places API (New) β required for
searchNearby,findAutocompletePredictions,fetchPlace, andfetchResolvedPhotoUri - Maps SDK for Android β required for the map fragment
- Maps Static API β only if you want the static map preview in the confirmation dialog
- Places API (New) β required for
- Add Google Play Services to your project β setup guide
- Create API keys β sign up guide
- Add your Places key to your manifest as
com.google.android.geo.API_KEYβ see the sample manifest
class MyActivity : AppCompatActivity() {
private val placePickerLauncher = registerForActivityResult(PingPlacePicker.Contract()) { result ->
result ?: return@registerForActivityResult
// result.place is the Google Places SDK Place
// result.latLng is the camera target at confirmation time
toast("You picked ${result.place.displayName}")
}
private fun showPicker() {
placePickerLauncher.launch(
PingPlacePicker.Request(
androidApiKey = getString(R.string.places_api_key),
mapsApiKey = getString(R.string.maps_static_api_key), // optional
initialLocation = null, // optional
),
)
}
}That's it. The contract:
- Returns
PingPlacePicker.Result?βnullwhen the user cancels - Throws
GooglePlayServicesNotAvailableExceptionfromlaunch(...)if Play Services isn't available β wrap in a try/catch if you need to handle it
PING uses one or two keys depending on which features you turn on:
| Key | Required? | Used for | Restriction |
|---|---|---|---|
androidApiKey |
always | Places SDK calls (nearby search, autocomplete, fetch place, photo URI) | Android-package restriction is fine |
mapsApiKey |
only if show_confirmation_map is true |
The static-map preview rendered in the confirmation dialog (Maps Static API) | The Static API doesn't accept Android-package restrictions β use an IP or unrestricted key |
Tip: don't ship a Maps Static API key inside your APK if you can avoid it β anyone can decompile and lift it. Prefer fetching it at runtime from a backend or remote config.
Override these bools/integers in your app's resources to change behaviour:
<!-- Show the place photo in the confirmation dialog.
Place Photo (New) β Enterprise tier, $7/1000 calls after 1000/month free -->
<bool name="show_confirmation_photo">true</bool>
<!-- Show a static-map preview in the confirmation dialog.
Maps Static API β Essentials tier, $2/1000 calls after 10000/month free -->
<bool name="show_confirmation_map">true</bool>
<!-- Pan the map to the marker when the user taps it -->
<bool name="auto_center_on_marker_click">false</bool>
<!-- Radius in metres used for both nearby search and the autocomplete location bias -->
<integer name="autocomplete_search_bias_radius">5000</integer>
<!-- Initial zoom level applied to the map -->
<integer name="default_zoom">17</integer>PING uses these Places API (New) and Maps Platform SKUs. Prices below are the default pay-as-you-go rates for the first 100,000 calls per month, after the free monthly allowance is exhausted:
| Action | SKU | Tier | Free / month | Price |
|---|---|---|---|---|
| Opening the picker | Nearby Search (New) | Pro | 5,000 | $32 / 1K |
| Tapping a search prediction | Place Details (New) β fetchPlace |
Pro | 5,000 | $17 / 1K |
| Typing in the search bar | Autocomplete (New) | β | unlimitedΒΉ | $0 |
| Confirmation photo (optional) | Place Photo (New) β fetchResolvedPhotoUri |
Enterprise | 1,000 | $7 / 1K |
| Confirmation static-map preview (optional) | Maps Static API | Essentials | 10,000 | $2 / 1K |
ΒΉ Autocomplete keystrokes are billed under "Autocomplete Session Usage" ($0)
because each session is terminated by a Place Details Pro call (fetchPlace).
If the user dismisses the picker without selecting a prediction the keystrokes
fall back to "Autocomplete Requests" at $2.83 / 1K (first 10,000/month free).
The tier of searchNearby and fetchPlace is driven by the
field mask
PING requests β ID, DISPLAY_NAME, FORMATTED_ADDRESS, LOCATION, TYPES,
PHOTO_METADATAS. Adding atmosphere/enterprise fields (rating, reviews, opening
hours, etc.) would bump the whole call up to the Enterprise tier.
Disable show_confirmation_photo and show_confirmation_map if you want to
keep usage strictly within the Places SDK Pro tier and avoid the Static Maps
key altogether.
For the full SKU breakdown and volume discounts, see the Google Maps Platform pricing page and the SKU details.
PING follows your app's Material colors. Override these in res/values/colors.xml (and res/values-night/colors.xml for dark mode):
<color name="colorPrimary">@color/material_teal500</color>
<color name="colorPrimaryDark">@color/material_teal800</color>
<color name="colorOnPrimary">@color/material_white</color>
<color name="colorSecondary">@color/material_deeporange500</color>
<color name="colorSecondaryDark">@color/material_deeporange800</color>
<color name="colorOnSecondary">@color/material_white</color>
<color name="colorBackground">@color/material_grey200</color>
<color name="colorOnBackground">@color/material_black</color>
<color name="colorSurface">@color/material_white</color>
<color name="colorOnSurface">@color/material_black</color>
<color name="textColorPrimary">@color/material_on_surface_emphasis_high_type</color>
<color name="textColorSecondary">@color/material_on_surface_emphasis_medium</color>
<color name="colorMarker">@color/material_deeporange400</color>
<color name="colorMarkerInnerIcon">@color/material_white</color>See the sample app for a complete example.
This release is a major rewrite. Highlights of the breaking changes:
| Before (β€ 3.0.1) | After |
|---|---|
PingPlacePicker.Builder().build(activity) + startActivityForResult |
registerForActivityResult(PingPlacePicker.Contract()) + launcher.launch(Request(...)) |
OnPlaceSelectedListener (static) |
Result(place, latLng) returned via the contract |
PingPlacePicker.getPlace(data) in onActivityResult |
Receive Result in the registered callback |
setMapsApiKey (required) |
mapsApiKey (optional β only needed for the static-map preview) |
setUrlSigningSecret |
Removed β pass a non-URL-restricted key instead |
setShouldReturnActualLatLng |
Removed β result.latLng is always the camera target at confirmation |
enable_nearby_search bool |
Removed β searchNearby is the only path now (Places SDK New) |
Place.Type enum results |
String types via place.placeTypes: List<String> |
Place.name / Place.address / Place.latLng getters |
place.displayName / place.formattedAddress / place.location |
| min SDK 19 | min SDK 24 (required by Places SDK 5.x) |
You'll also need to enable Places API (New) in your Google Cloud project β the legacy "Places API" is not enough.
The 3.x library exposed an enable_nearby_search boolean that toggled between
two backends:
false(default) β Places SDKfindCurrentPlace, $30 / 1Ktrueβ Places Web API/nearbysearch, $40 / 1K
Google deprecated both of these in favor of the single new SDK call
searchNearby, which is what PING 4.x uses unconditionally. There's nothing
left to toggle to, so the boolean is gone.
The new call costs $32 / 1K at the Pro tier with PING's default field mask
β $0.002 per call more than the old default (findCurrentPlace) and $0.008
per call less than the old opt-in (/nearbysearch). If you were relying on
the default cheaper path, expect a ~7 % bump in nearby-search billing; if you
had enable_nearby_search=true, you'll actually pay less.
See Pricing for the full per-flow cost breakdown.
Copyright 2020 Rafael Chagas
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.



