Google Places SDK for Androidを使って検索機能を実装する方法
この記事はLuup Advent Calendarの15日目の記事です。
こんにちは、LuupのAndroidチームに業務委託としてジョインしている向中野(@mukky620)です。
今回は検索機能でAutocomplete検索を実装した際に使用したGoogle Places SDK for Androidを使った実装方法について簡単にご紹介したいと思います。
Google Places SDK for Androidとは
Google Places APIを使用するためのAndroid用SDKです。
主に下記のようなGoogle Map上のデータを取得する事が出来ます。
- Autocomplete検索による予測場所リスト
- 場所の名前や住所等の詳細情報
- 現在地情報
- 場所の写真情報
今回は上記のうち、LUUPの検索機能で使用しているAutocomplete検索に絞って紹介したいと思います。
セットアップ
APIの有効化やAPIキーの作成
Google Places APIを使用するにはGCPにてPlaces APIを有効にしたり、APIキーを作成したりと必要な作業がいくつかあります。
本記事ではそちらの詳細は割愛しますので、詳しくは公式のドキュメントを参照してください。
インストール
下記の依存を追加します。
※バージョンは執筆時点の最新です。
dependencies {
implementation "com.google.android.libraries.places:places:2.7.0"
}
KTXも用意されているので使用する場合はこちらも追加します。
dependencies {
implementation "com.google.maps.android:places-ktx:2.0.0"
}
APIキーをプロジェクトに追加
アプリからGoogle Places APIを使用する際にAPIキーを参照する必要があるのですが、安全に参照する方法としてSecrets Gradle Plugin for Androidを使用して参照する方法があります。
APIキーの追加に関しても詳しくは公式のドキュメントを参照してください。
Places API clientの初期化
ApplicationクラスでPlaces SDK for Androidを初期化します。
第2引数には先述したAPIキーをセットします。
// Initialize the SDK
Places.initialize(applicationContext, apiKey)
また、実際に使用するクラスでPlacesClientを生成しておきます。
DIを使用している場合や頻繁にPlacesClient生成をする場合は、Singletonで保持しても良いかもしれません。
// Create a new PlacesClient instance
val placesClient = Places.createClient(this)
実装
Autocomplete検索
入力した文字列からAutocomplete検索をする検索機能を実装する場合、下記の3種類の方法があります。
-
AutocompleteSupportFragment
をレイアウトに埋め込んで使用する。- SDKで用意されたAutocompleteWidgetをそのまま使用できるので、開発コストは少ない
- 検索Widgetの自由なカスタムが難しい
-
Autocomplete.IntentBuilder
を使用して専用のActivityを起動する。- SDKで用意されたAutocompleteWidgetをそのまま使用できるので、開発コストは少ない
- 検索Widgetの自由なカスタムが難しい
-
PlacesClient.findAutocompletePredictions()
を使用してデータを取得する。- 検索Widgetの自由なカスタムが可能
- プログラムでデータを取得するので検索Widgetやロジック等を独自で実装する必要があり、開発コストは少し高い
3種類の方法には先述した通りそれぞれメリットとデメリットがありますので、サービス仕様に合わせて選定すると良いと思います。
LUUPの検索機能では、デザイン仕様を満たすために自由なカスタムができる3.
の方法を採用しましたのでそちらの方法について紹介します。
PlacesClient.findAutocompletePredictions()での場所情報取得
PlacesClient.findAutocompletePredictions()
はFindAutocompletePredictionsRequest
を指定する事でデータを取得するAPIです。
FindAutocompletePredictionsRequest
パラメータ | 型 | 必須 | 説明 |
---|---|---|---|
query | String | ○ | 入力した文字列 |
sessionToken | AutocompleteSessionToken | セッション化するために使用されるトークン 詳しくはこちらを参照 |
|
cancellationToken | CancellationToken | 操作をキャンセルする必要がある場合に使用するトークン | |
locationBias | LocationBias | 緯度と経度の境界を指定して領域内の結果を優先する | |
locationRestriction | LocationRestriction | 緯度と経度の境界を指定して領域内の結果のみを表示する | |
countries | List<String> | 結果の場所を制限する国のリスト 最大で5か国までサポート |
|
typesFilter | List<String> | 結果のタイプを制限するフィルター サポートされているタイプはPlaceTypesを参照 |
|
origin | LatLng | リクエストの起点となる位置情報を指定 指定する事で distanceMeters という指定された起点との間の直線距離をメートル単位で取得できる |
FindAutocompletePredictionsRequest
を指定して下記のようにAPIをコールし、データを取得します。
※位置情報の値はサンプルとして適当なものを代入しています。
// 文字列入力して決定するまでを1セッションとしてトークンとする事で無駄なAPIリクエスト費用を減らす
val token = AutocompleteSessionToken.newInstance()
// LocationBiasに設定する位置情報を生成
val bounds = RectangularBounds.newInstance(
LatLng(-33.880490, 151.184363),
LatLng(-33.858754, 151.229596)
)
val request =
FindAutocompletePredictionsRequest.builder()
// LocationBias() または LocationRestriction()のどちらかを指定する事を推奨
.setLocationBias(bounds)
.setOrigin(LatLng(-33.8749937, 151.2041382))
.setCountries("jp")
.setTypesFilter(listOf(PlaceTypes.ESTABLISHMENT))
.setSessionToken(token)
.setQuery(query)
.build()
placesClient.findAutocompletePredictions(request)
.addOnSuccessListener { response: FindAutocompletePredictionsResponse ->
for (prediction in response.autocompletePredictions) {
Log.i(TAG, prediction.placeId)
Log.i(TAG, prediction.getPrimaryText(null).toString())
}
}.addOnFailureListener { exception: Exception? ->
if (exception is ApiException) {
Log.e(TAG, "Place not found: " + exception.statusCode)
}
}
また、KTXのPlacesClient.awaitFindAutocompletePredictions()
を使用して下記のように取得もできます。
val token = AutocompleteSessionToken.newInstance()
val bounds = RectangularBounds.newInstance(
LatLng(-33.880490, 151.184363),
LatLng(-33.858754, 151.229596)
)
runCatching {
placesClient.awaitFindAutocompletePredictions {
locationBias = bounds
origin = LatLng(-33.8749937, 151.2041382)
countries = listOf("jp")
typesFilter = listOf(PlaceTypes.ESTABLISHMENT)
sessionToken = token
query = keyword
}
}
.onSuccess {
for (prediction in it.autocompletePredictions) {
Log.i(TAG, prediction.placeId)
Log.i(TAG, prediction.getPrimaryText(null).toString())
}
}
.onFailure {
if (it is ApiException) {
Log.e(TAG, "Place not found: " + exception.statusCode)
}
}
}
LUUPでは下記の5つのパラメーターを指定してリクエストしています。
- API費用の最適化のための
sessionToken
- 指定した文字列の予測場所をより近い場所から優先するための
locationBias
- 予測場所を日本国内に絞るための
countries
- 予測場所を分かりやすい場所名や建物名で表示するための
typesFilter
(ESTABLISHMENTを指定) - 予測場所を現在地から近い順に表示するための
origin
そして、上記のAPIのResponseのFindAutocompletePredictionsResponse
に、文字列から予測された場所情報がリストで返却されます。
FindAutocompletePredictionsResponse
パラメータ | 型 | 説明 |
---|---|---|
autocompletePredictions | List<AutocompletePrediction> | 場所情報リスト |
AutocompletePrediction
メソッド | 型 | 説明 |
---|---|---|
getFullText(CharacterStyle) | SpannableString | 場所の説明の全文 例:「エッフェル塔、アベニュー アナトール フランス、パリ、フランス」 |
getPrimaryText(CharacterStyle) | SpannableString | 場所を説明する本文 例:「エッフェル塔、123 ピット ストリート」 |
getSecondaryText(CharacterStyle) | SpannableString | 場所の説明の補助テキスト 例:「アベニュー アナトール フランス、パリ、フランス、シドニー、ニュー サウス ウェールズ」 |
getPlaceId() | String | 予測された場所の場所ID 場所を一意に識別するテキスト識別子でこのplaceIDを使用してさらに詳細情報を取得する事ができる 詳しくはこちら |
getPlaceTypes() | List<Type> | 関連付けられたプレイスタイプのリスト |
getDistanceMeters() | Int | この場所とリクエストで指定された起点との間の直線距離(メートル単位) |
上記のFindAutocompletePredictionsResponse
の予測された場所情報リストをparseしてデータを参照する事で、検索結果として表示します。
LUUPでは、getPlaceId()
、getPrimaryText(CharacterStyle)
、getSecondaryText(CharacterStyle)
、getDistanceMeters()
などを使用してソートしたりResponseデータを内部データにparseしたりする事で最終的に下のスクショのように検索結果として表示しています。
終わりに
以上がGoogle Places SDK for Androidのセットアップから場所情報のデータを取得して表示するまでの簡単な流れとなります。
最初のセットアップ部分さえすんなり完了できれば意外と簡単に実装できると思います。
また、今回は省略しましたが
・検索結果をタップしたタイミングで、対象の場所の詳細情報を取得してマップを移動させる
・検索文字を1文字入力するたびにPlaces APIをコールしないように上手く間引く
などの処理も行なっていたりするので機会があればご紹介できればと思います。
今後、検索機能をユーザーの皆様がさらに使いやすい機能になるようにブラッシュアップを重ねていこうと考えていますので期待して待っていただければなと思います。
Discussion
はじめましてm(__)m
PlaceApiど初心者です
やりたいことが
「latとlngから、placeIdを特定(取得)する」
なのですが
方法あると思いますかm(__)m?
AutocompletePredictionにgetPlaceId()というものはあるが、AutocompletePredictionのビルダーにplaceId食わせないとインスタンス作れない、でグルグルしておりますm(__)m
(コメントするのにアカウント必要で、zennデビューさせて頂きましたm(__)m)
質問です。
私は初期画面に地図をFragmentを利用して表示させて
同じ初期画面に入力のための検索窓?Widget?部分を作成し
検索窓部分押下で検索窓が立ち上がるように作成しました。
初期地図画面に検索窓につながるボタンをのっけて地図を表示させたい場合
どういう処理で実現可能でしょうか。
Fragmentでは無理ですかね。。。