Android 向けの独自の Current Place Picker を作成する(Java)

1. 始める前に

Google Maps Platform と Places SDK for Android を使用して、場所の一覧をユーザーに表示し、ユーザーの現在地を特定する方法を学びます。

bd07a9ad2cb27a06.png

要件

  • 基本的な Java 操作技術

演習内容

  • Android アプリに地図を追加する
  • 位置情報の利用許可を使用して、ユーザーの位置情報を取得する
  • ユーザーの現在地の周辺にある場所を取得する
  • 現在地である可能性の高い場所をユーザーに表示し、ユーザーの現在地を特定する

作成内容

Android アプリを一から作成しますが、デバッグ用のサンプルコードを参照用としてダウンロードできます。GitHub からサンプルコードをダウンロードするか、コマンドライン用に Git が設定されている場合は次のように入力します。

git clone https://github.com/googlecodelabs/current-place-picker-android.git

この Codelab で何か問題(コードのバグ、文法的な誤り、不明確な表現など)が見つかりましたら、Codelab の左下にある [誤りを報告] から報告してください。

2. 作成を開始する

この Codelab を開始する前に、次を設定してください。

Android Studio

Android Studio を https://developer.android.com/studio からダウンロードします。

すでに Android Studio がインストールされている場合は、[Android Studio] > [Check for Updates] をクリックして最新バージョンであることを確認します。

1f36bae83b64e33.png

この Codelab は、Android Studio 3.4 を使用して作成されています。

Android SDK

Android Studio では、SDK Manager を使ってご希望の SDK を設定できます。この Codelab では、Android Q SDK を使用します。

  1. Android Studio の「ようこそ」画面で、[Configure] > [SDK Manager] をクリックします。

d3fa03c269ec231c.png

  1. 目的の SDK のチェックボックスをオンにして、[Apply] をクリックします。

パソコンに SDK がない場合は、ダウンロードされます。

884e0aa1314f70d.png

Google Play 開発者サービス

SDK Manager から、Google Play 開発者サービスもインストールする必要があります。

  1. [SDK Tools] タブをクリックし、[Google Play services] チェックボックスをオンにします。

ステータスが [Update available] の場合は更新します。

ad6211fd78f3b629.png

3. エミュレータを準備する

アプリを実行するには、ご自身のデバイスを接続するか、Android Emulator を使用します。

ご自身のデバイスを使用する場合は、このページの最後の実際のデバイスに関する手順: Google Play 開発者サービスを更新するに進んでください。

エミュレータを追加する

  1. Android Studio の「ようこそ」画面で、[Configure] > [AVD Manager] をクリックします。

5dd2d14c9c56d3f9.png

[Android Virtual Device Manager] ダイアログが開きます。

  1. [Create Virtual Device] をクリックすると、選択可能なデバイスのリストが表示されます。

2d44eada384f8b35.png

  1. [Play Store] 列で再生アイコン d5722488d80cd6be.png が表示されているデバイスを選択し、[Next] をクリックします。

e0248f1c6e85ab7c.png

インストール可能なシステム イメージの一覧が表示されます。Android 9 以降(Google Play)を対象とする Q の横に [Download] という語が表示されている場合は、[Download] をクリックします。

316d0d1efabd9f24.png

  1. [Next] をクリックして、仮想デバイスに名前を付け、[Finish] をクリックします。

[Your Virtual Devices] のリストに戻ります。

  1. 新しいデバイスの横にある開始アイコン ba8adffe56d3b678.png をクリックします。

7605864ed27f77ea.png

しばらくすると、エミュレータが開きます。

エミュレータに関する手順 - Google Play 開発者サービスを更新する

  1. エミュレータが起動したら、表示されるナビゲーション バーで [...] をクリックします**。**

2e1156e02643d018.png

[Extended controls] ダイアログが開きます。

  1. メニューで [Google Play] をクリックします。

利用可能なアップデートがある場合は、[Update] をクリックします。

5afd2686c5cad0e5.png

  1. Google アカウントでエミュレータにログインします。

ご自分のアカウントを使用できますが、個人情報をテストに関連付けたくない場合は新しいアカウントを作成(無料)することもできます。

Google Play 開発者サービスが開きます。

  1. [Update] をクリックして、Google Play 開発者サービスの最新バージョンを入手します。

f4bc067e80630b9c.png

アカウント設定を完了し、お支払い方法の追加を求められた場合は、[Skip] をクリックします。

エミュレータで位置情報を設定する

  1. エミュレータが起動したら、ホーム画面の検索バーに「maps」と入力して、Google マップ アプリのアイコンを表示させます。

2d996aadd53685a6.png

  1. アイコンをクリックして起動します。

デフォルトの地図が表示されます。

  1. 地図の右下にある現在地アイコン c5b4e2fda57a7e71.png をクリックします。

位置情報の利用権限をスマートフォンに付与するよう求められます。

f2b68044eabca151.png

  1. [...] をクリックして [Extended Controls] メニューを開きます。
  2. [Location] タブをクリックします。
  3. 緯度と経度を入力します。

任意の緯度と経度の値を入力できますが、場所が十分にある地域が表示されるようにしましょう。

(このコードラボの結果を再現するには、緯度 20.7818 と経度 -156.4624 を使用してください。ハワイのマウイ島キヘイが表示されます。)

  1. [Send] をクリックすると、地図にその位置が表示されます。

f9576b35218f4187.png

これで、アプリを実行してその位置でテストする準備が整いました。

実際のデバイスに関する手順 - Google Play 開発者サービスを更新する

実際の Android デバイスを使用している場合は、次の手順を行います。

  1. ホーム画面の検索バーを使用して、Google Play 開発者サービスを検索し、開きます。
  2. [もっと見る] をクリックします。

[更新] をクリックします(表示されている場合)。

ad16cdb975b5c3f7.png baf0379ef8a9c88c.png

4. Google マップ アクティビティでアプリケーション シェルを作成する

  1. Android Studio の「ようこそ」画面で、[Start a new Android Studio project] を選択します。
  2. [Phone and Tablet] タブで [Google Maps Activity] を選択します。

c9c80aa8211a8761.png

[Configure your project] ダイアログが開きます。ここでアプリの名前を指定し、ドメインに基づいてパッケージを作成します。

Current Place と呼ばれるアプリ(パッケージ com.google.codelab.currentplace に対応する)の設定は次のとおりです。

37f5b93b94ee118c.png

  1. 言語として [Java] を選択し、[Use androidx. artifacts] を選択します*。

その他の設定はデフォルトのままにします。

  1. [完了] をクリックします。

5. Google サービスの依存関係を Gradle ビルドファイルに追加する

Android で位置情報の利用許可を得るには、Google Play 開発者サービスの Google Location and Activity Recognition API が必要です。この API を含む Google Play 開発者サービス API の追加について詳しくは、Google Play 開発者サービスのセットアップをご覧ください。

Android Studio プロジェクトには、通常 2 つの build.gradle ファイルがあります。1 つはプロジェクト全体用、もう 1 つはアプリ用です。[Android] ビューに Android Studio プロジェクト エクスプローラがある場合、その両方が Gradle Scripts フォルダに表示されます。Google サービスを追加するには、build.gradle (Module: app) ファイルを編集する必要があります。

f3043429cf719c47.png

  1. dependencies セクションに 2 行を追加して、位置情報と Places API(コンテキスト内のサンプルコード)に Google サービスを追加します。

build.gradle(Module: app)

plugins {
  id 'com.android.application'
}

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.google.codelab.currentplace"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'com.google.android.gms:play-services-maps:16.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

    implementation 'com.google.android.gms:play-services-location:16.0.0'
    implementation 'com.google.android.libraries.places:places:1.1.0'
}

6 Google Maps Platform の API を有効にして、API キーを取得する

次のステップでは、Maps SDK for AndroidPlaces API を有効にします。

Google Maps Platform を設定する

課金を有効にした Google Cloud Platform アカウントとプロジェクトをまだ作成していない場合は、Google Maps Platform スタートガイドに沿って請求先アカウントとプロジェクトを作成してください。

  1. Cloud Console で、プロジェクトのプルダウン メニューをクリックし、この Codelab に使用するプロジェクトを選択します。

  1. Google Cloud Marketplace で、この Codelab に必要な Google Maps Platform API と SDK を有効にします。詳しい手順については、こちらの動画またはドキュメントをご覧ください。
  2. Cloud Console の [認証情報] ページで API キーを生成します。詳しい手順については、こちらの動画またはドキュメントをご覧ください。Google Maps Platform へのすべてのリクエストで API キーが必要になります。

作成した API キーをコピーします。Android Studio に戻り、[Android] > [app] > [res] > [values] で、ファイル google_maps_api.xml を見つけます。

YOUR_KEY_HERE を、コピーした API キーに置き換えます。

aa576e551a7a1009.png

これで、アプリの設定が完了しました。

7. レイアウト ファイルを編集する

  1. プロジェクト エクスプローラで、[Android] > [app] > [res] > [layout] にある activity_maps.xml ファイルを開きます。

4e0d986480c57efa.png

  1. 画面の右側に基本の UI が表示されます。下部のタブで、レイアウト用に [Design] または [Text] のエディタを選択できます。[Text] を選択し、レイアウト ファイルのコンテンツ全体を次のように置き換えます。

activity_maps.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:minHeight="?attr/actionBarSize"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:titleTextColor="@android:color/white"
        android:background="@color/colorPrimary" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <fragment
            android:id="@+id/map"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="349dp"
            tools:context=".MapsActivity" />

        <ListView
            android:id="@+id/listPlaces"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

</LinearLayout>

次のようなユーザー インターフェースが表示されます。

1bf786808a4697ce.png

8. アプリバーを設定する

アイコン付きのアプリバーを追加して、ユーザーが現在地を選択する際にクリックできるボタンを提供します。ユーザーがこのアイコンをクリックすると、ユーザーの現在地を特定し、現在地である可能性の高い周辺の場所が表示されます。アプリバーは次のようになります。

3a17c92b613a26c5.png

スマートフォンでは、アイコンのみが表示されます。スペースに余裕があるタブレットでは、テキストも表示されます。

アイコンを作成する

  1. プロジェクト エクスプローラで、[Android] > [app] をクリックし、[res] フォルダを右クリックして [New] > [Image Asset] を選択します。

Asset Studio が開きます。

  1. [Icon Type] メニューで、[Action Bar and Tab Icons] をクリックします。
  2. アセットに ic_geolocate という名前を付けます。
  3. アセットタイプとして [Clip Art] を選択します**。**
  4. [Clip Art] の横にある画像をクリックします。

[Select Icon] ウィンドウが開きます。

  1. アイコンを選択します。

検索バーを使用して、趣旨にあったアイコンを見つけます。

  1. location を検索して、現在地に関連するアイコンを選択します。

[my location] アイコンは、現在地にカメラをスナップするために Google マップ アプリで使用するアイコンと同じです。

  1. [OK] > [Next] > [Finish] をクリックして、新しいアイコン ファイルが格納されている、drawable という新しいフォルダがあることを確認します。

b9e0196137ed18ae.png

文字列リソースを追加する

  1. プロジェクト エクスプローラで、[Android] > [app] > [res] > [values] をクリックして、strings.xml ファイルを開きます。
  2. <string name="title_activity_maps">Map</string> の後に次の行を追加します。

strings.xml

    <string name="action_geolocate">Pick Place</string>
    <string name="default_info_title">Default Location</string>
    <string name="default_info_snippet">No places found, because location permission is disabled.</string>

アプリバーの 1 行目は、アイコンの横にテキストラベルを表示するスペースがある場合に使用されます。もう 1 行は、マップに追加するマーカーに使用されます。

ファイル内のコードは次のようになります。

<resources>
    <string name="app_name">Current Place</string>
    <string name="title_activity_maps">Map</string>
    <string name="action_geolocate">Pick Place</string>
    <string name="default_info_title">Default Location</string>
    <string name="default_info_snippet">No places found, because location permission is disabled.</string>
</resources>

アプリバーを追加する

  1. プロジェクト エクスプローラで [Android] > [app] をクリックして、res フォルダを右クリックし、[New] > [Directory] を選択して、app/src/main/res に新しいサブディレクトリを作成します。
  2. ディレクトリに menu という名前を付けます。
  3. menu フォルダを右クリックして、[New] > [File] を選択します。
  4. ファイルに menu.xml という名前を付けます。
  5. 次のコードを貼り付けます。
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- "Locate me", should appear as action button if possible -->
    <item
        android:id="@+id/action_geolocate"
        android:icon="@drawable/ic_geolocate"
        android:title="@string/action_geolocate"
        app:showAsAction="always|withText" />

</menu>

アプリバーのスタイルを更新する

  1. プロジェクト エクスプローラで、[Android] > [app] > [res] > [values] の順に開き、中にあるファイル styles.xml を開きます。
  2. <style> タグで、親プロパティを "Theme.AppCompat.NoActionBar" に編集します。
  3. name プロパティをメモします。このプロパティは、次のステップで使用します。

styles.xml

<style name="AppTheme" parent="Theme.AppCompat.NoActionBar">

AndroidManifest.xml でアプリのテーマを更新する

  1. [Android] > [app] > [manifests] をクリックして、AndroidManifest.xml ファイルを開きます。
  2. android:theme の行を見つけ、値を @style/AppTheme に編集または確定します。

AndroidManifest.xml

   <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

これで、コーディングの準備が整いました。

9. アプリを初期化する

  1. プロジェクト エクスプローラで、MapsActivity.java ファイルを見つけます。

このファイルは、ステップ 1 でアプリ用に作成したパッケージに対応しています。

8b0fa27d417f5f55.png

  1. ファイルを開くと、Java コードエディタが開きます。

Places SDK とその他の依存関係をインポートする

下記の行を MapsActivity.java の先頭に追加して、既存の import ステートメントを置き換えます。

これには、既存の import に加え、この Codelab のコードで使用される import も追加されています。

MapsActivity.java

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.Places;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.model.PlaceLikelihood;
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest;
import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;
import com.google.android.libraries.places.api.net.PlacesClient;

import java.util.Arrays;
import java.util.List;

クラスの署名を更新する

Places API では、下位互換性をサポートするために AndroidX コンポーネントが使用されているため、AppCompatActivity を拡張するように定義する必要があります。これは、マップ アクティビティにデフォルトで定義されている FragmentActivity 拡張機能に代わるものです。

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback {

クラス変数を追加する

次に、異なるクラスメソッドで使用されるさまざまなクラス変数を宣言します。これらには、UI 要素とステータス コードが含まれており、GoogleMap mMap の変数宣言のすぐ下に配置する必要があります。

    // New variables for Current Place picker
    private static final String TAG = "MapsActivity";
    ListView lstPlaces;
    private PlacesClient mPlacesClient;
    private FusedLocationProviderClient mFusedLocationProviderClient;

    // The geographical location where the device is currently located. That is, the last-known
    // location retrieved by the Fused Location Provider.
    private Location mLastKnownLocation;

    // A default location (Sydney, Australia) and default zoom to use when location permission is
    // not granted.
    private final LatLng mDefaultLocation = new LatLng(-33.8523341, 151.2106085);
    private static final int DEFAULT_ZOOM = 15;
    private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1;
    private boolean mLocationPermissionGranted;

    // Used for selecting the Current Place.
    private static final int M_MAX_ENTRIES = 5;
    private String[] mLikelyPlaceNames;
    private String[] mLikelyPlaceAddresses;
    private String[] mLikelyPlaceAttributions;
    private LatLng[] mLikelyPlaceLatLngs;

onCreate メソッドを更新する

位置情報サービスに対するランタイムの使用許可を処理するよう onCreate メソッドを更新して、UI 要素の設定と Places API クライアントの作成を行う必要があります。

既存の onCreate() メソッドの末尾に、アクション ツールバー、ビュー設定、プレイス クライアントに関する次のコードを追加します。

MapsActivity.java onCreate()

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

        //
        // PASTE THE LINES BELOW THIS COMMENT
        //

        // Set up the action toolbar
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Set up the views
        lstPlaces = (ListView) findViewById(R.id.listPlaces);

        // Initialize the Places client
        String apiKey = getString(R.string.google_maps_key);
        Places.initialize(getApplicationContext(), apiKey);
        mPlacesClient = Places.createClient(this);
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
    }

アプリバー メニューのコードを追加する

これら 2 つのメソッドは、アプリバー メニューと 1 つのアイテム([場所を選択] アイコン)を追加し、ユーザーによるアイコンのクリックを処理します。

これらの 2 つのメソッドを、ファイルの onCreate メソッドの後にコピーします。

MapsActivity.java onCreateOptionsMenu() と onOptionsItemSelected()

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu, menu);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
           case R.id.action_geolocate:

                // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Present the current place picker
                // pickCurrentPlace();
                return true;

            default:
                // If we got here, the user's action was not recognized.
                // Invoke the superclass to handle it.
                return super.onOptionsItemSelected(item);

        }
    }

テスト

  1. Android Studio から [Run] または [Run menu] > [Run 'アプリ'] をクリックします。

28bea91c68c36fb2.png

  1. デプロイの対象を選択するように求められます。実行中のエミュレータがこのリストに表示されます。これを選択すると、アプリがエミュレータにデプロイされます。

f44658ca91f6f41a.png

しばらくすると、アプリが起動します。1 つのボタンと未入力の場所のリストとともに、オーストラリアのシドニーを中心とした地図が表示されます。

68eb8c70f4748350.png

デバイスの位置情報の利用許可をリクエストしない限り、地図はユーザーの位置に移動しません。

10. 位置情報の利用許可をリクエストして処理する

地図の準備ができたら位置情報の利用許可をリクエストする

  1. getLocationPermission というメソッドを定義して、ユーザーからの利用許可をリクエストします。

作成した onOptionsSelected メソッドの下に以下のコードを貼り付けます。

MapsActivity.java getLocationPermission()

    private void getLocationPermission() {
        /*
         * Request location permission, so that we can get the location of the
         * device. The result of the permission request is handled by a callback,
         * onRequestPermissionsResult.
         */
        mLocationPermissionGranted = false;
        if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
                android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
            mLocationPermissionGranted = true;
        } else {
            ActivityCompat.requestPermissions(this,
                    new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
                    PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
        }
    }
  1. 既存の onMapReady メソッドの末尾に 2 行を追加して、ズーム コントロールを有効にし、ユーザーからの位置情報の利用許可をリクエストします。

MapsActivity.java onMapReady()

   @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        // Add a marker in Sydney and move the camera
        LatLng sydney = new LatLng(-34, 151);
        mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
        mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));

        //
        // PASTE THE LINES BELOW THIS COMMENT
        //

        // Enable the zoom controls for the map
        mMap.getUiSettings().setZoomControlsEnabled(true);

        // Prompt the user for permission.
        getLocationPermission();

    }

リクエストされた利用許可の結果を処理する

ユーザーが利用許可リクエスト ダイアログに応答すると、このコールバックが Android によって呼び出されます。

次のコードを getLocationPermission() メソッドの後に貼り付けます。

MapsActivity.java onRequestPermissionsResult()

   /**
     * Handles the result of the request for location permissions
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
    }

11. 現在地を取得して、現在地である可能性の高い場所をフェッチする

ユーザーがアプリバーの [場所を選択] をクリックすると、アプリで pickCurrentPlace() メソッドが呼び出されます。このメソッドは、前に定義した getDeviceLocation() メソッドを呼び出します。getDeviceLocation メソッドは、デバイスの最新の位置情報を取得してから、別の getCurrentPlaceLikelihoods, メソッドを呼び出します。

findCurrentPlace API を呼び出してレスポンスを処理する

getCurrentPlaceLikelihoodsfindCurrentPlaceRequest を構築して、Places API findCurrentPlace タスクを呼び出します。タスクが成功すると、findCurrentPlaceResponse が返されます。これには、placeLikelihood オブジェクトのリストが含まれます。各オブジェクトには、場所の名前と住所、ユーザーがその場所にいる可能性(0 ~ 1 の倍精度数)など、多くのプロパティが含まれています。このメソッドは、placeLikelihoods から場所詳細のリストを作成することでレスポンスを処理します。

このコードは、現在地の可能性が最も高い 5 つの場所に対して反復処理を行い、可能性が 0 より高い場所をリストに追加して、レンダリングします。表示する場所の個数を 5 個以外にする場合は、M_MAX_ENTRIES 定数を編集します。

以下のコードを onMapReady メソッドの後に貼り付けます。

MapsActivity.java getCurrentPlaceLikelihoods()

   private void getCurrentPlaceLikelihoods() {
        // Use fields to define the data types to return.
        List<Place.Field> placeFields = Arrays.asList(Place.Field.NAME, Place.Field.ADDRESS,
                Place.Field.LAT_LNG);

        // Get the likely places - that is, the businesses and other points of interest that
        // are the best match for the device's current location.
        @SuppressWarnings("MissingPermission") final FindCurrentPlaceRequest request =
                FindCurrentPlaceRequest.builder(placeFields).build();
        Task<FindCurrentPlaceResponse> placeResponse = mPlacesClient.findCurrentPlace(request);
        placeResponse.addOnCompleteListener(this,
                new OnCompleteListener<FindCurrentPlaceResponse>() {
                    @Override
                    public void onComplete(@NonNull Task<FindCurrentPlaceResponse> task) {
                        if (task.isSuccessful()) {
                            FindCurrentPlaceResponse response = task.getResult();
                            // Set the count, handling cases where less than 5 entries are returned.
                            int count;
                            if (response.getPlaceLikelihoods().size() < M_MAX_ENTRIES) {
                                count = response.getPlaceLikelihoods().size();
                            } else {
                                count = M_MAX_ENTRIES;
                            }

                            int i = 0;
                            mLikelyPlaceNames = new String[count];
                            mLikelyPlaceAddresses = new String[count];
                            mLikelyPlaceAttributions = new String[count];
                            mLikelyPlaceLatLngs = new LatLng[count];

                            for (PlaceLikelihood placeLikelihood : response.getPlaceLikelihoods()) {
                                Place currPlace = placeLikelihood.getPlace();
                                mLikelyPlaceNames[i] = currPlace.getName();
                                mLikelyPlaceAddresses[i] = currPlace.getAddress();
                                mLikelyPlaceAttributions[i] = (currPlace.getAttributions() == null) ?
                                        null : TextUtils.join(" ", currPlace.getAttributions());
                                mLikelyPlaceLatLngs[i] = currPlace.getLatLng();

                                String currLatLng = (mLikelyPlaceLatLngs[i] == null) ?
                                        "" : mLikelyPlaceLatLngs[i].toString();

                                Log.i(TAG, String.format("Place " + currPlace.getName()
                                        + " has likelihood: " + placeLikelihood.getLikelihood()
                                        + " at " + currLatLng));

                                i++;
                                if (i > (count - 1)) {
                                    break;
                                }
                            }

                            // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                            // Populate the ListView
                            // fillPlacesList();
                        } else {
                            Exception exception = task.getException();
                            if (exception instanceof ApiException) {
                                ApiException apiException = (ApiException) exception;
                                Log.e(TAG, "Place not found: " + apiException.getStatusCode());
                            }
                        }
                    }
                });
    }

デバイスの現在地にマップのカメラを移動する

ユーザーが許可を与えると、アプリはユーザーの最新の位置情報を取得して、その位置が中心になるようカメラが移動します。

ユーザーが許可を拒否した場合、このページの先頭の定数に設定されたデフォルトの位置(デフォルトのサンプルコードではオーストラリアのシドニー)にカメラが移動します。

次のコードを getPlaceLikelihoods() メソッドの後に貼り付けます。

MapsActivity.java getDeviceLocation()

    private void getDeviceLocation() {
        /*
         * Get the best and most recent location of the device, which may be null in rare
         * cases when a location is not available.
         */
        try {
            if (mLocationPermissionGranted) {
                Task<Location> locationResult = mFusedLocationProviderClient.getLastLocation();
                locationResult.addOnCompleteListener(this, new OnCompleteListener<Location>() {
                    @Override
                    public void onComplete(@NonNull Task<Location> task) {
                        if (task.isSuccessful()) {
                            // Set the map's camera position to the current location of the device.
                            mLastKnownLocation = task.getResult();
                            Log.d(TAG, "Latitude: " + mLastKnownLocation.getLatitude());
                            Log.d(TAG, "Longitude: " + mLastKnownLocation.getLongitude());
                            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
                                    new LatLng(mLastKnownLocation.getLatitude(),
                                            mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
                        } else {
                            Log.d(TAG, "Current location is null. Using defaults.");
                            Log.e(TAG, "Exception: %s", task.getException());
                            mMap.moveCamera(CameraUpdateFactory
                                    .newLatLngZoom(mDefaultLocation, DEFAULT_ZOOM));
                        }

                       getCurrentPlaceLikelihoods();
                    }
                });
            }
        } catch (SecurityException e)  {
            Log.e("Exception: %s", e.getMessage());
        }
    }

ユーザーが [場所を選択] をクリックしたときに、位置情報の利用許可を確認する

ユーザーが [場所を選択] をタップすると、位置情報の利用許可が確認され、許可されていない場合はユーザーに許可を求めるプロンプトが表示されます。

ユーザーが許可している場合、getDeviceLocation が呼び出され、現在地の可能性が高い場所を取得するプロセスが開始します。

  1. getDeviceLocation() の後に次のメソッドを追加します。

MapsActivity.java pickCurrentPlace()

   private void pickCurrentPlace() {
        if (mMap == null) {
            return;
        }

        if (mLocationPermissionGranted) {
            getDeviceLocation();
        } else {
            // The user has not granted permission.
            Log.i(TAG, "The user did not grant location permission.");

            // Add a default marker, because the user hasn't selected a place.
            mMap.addMarker(new MarkerOptions()
                    .title(getString(R.string.default_info_title))
                    .position(mDefaultLocation)
                    .snippet(getString(R.string.default_info_snippet)));

            // Prompt the user for permission.
            getLocationPermission();
        }
    }
  1. pickCurrentPlace が定義されたら、pickCurrentPlace を呼び出してコメント化を解除する onOptionsItemSelected() の行を見つけます。

MapsActivity.java onOptionItemSelected()

           case R.id.action_geolocate:

                // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Present the Current Place picker
                pickCurrentPlace();
                return true;

テスト

アプリを実行し、[場所を選択] をタップすると、位置情報の利用許可を求めるプロンプトが表示されます。

  • 許可した場合、その設定は保存され、以後はプロンプトは表示されなくなります。許可を拒否した場合は、次回ボタンをタップしたときにプロンプトが表示されます。
  • getPlaceLikelihoods で現在地である可能性の高い場所が取得されましたが、ListView によってまだ表示されません。Android Studio で [⌘6] をクリックすると、logcat のログに MapsActivity というタグが付いたステートメントが表示され、新しいメソッドが適切に機能しているかどうかを確認できます。
  • 許可した場合は、検出されたデバイスの位置を示す Latitude: のステートメントと Longitude: のステートメントがログに含まれます。Google マップとエミュレータの拡張メニューを使用してエミュレータの位置を以前に指定している場合、これらのステートメントにはその位置が表示されます。
  • findCurrentPlace の呼び出しが成功した場合、現在地である可能性の最も高い 5 つの場所の名前と位置を出力する 5 つのステートメントがログに含まれます。

d9896a245b81bf3.png

12. Current Place Picker にデータを読み込む

選択した場所のハンドラを設定する

ユーザーが ListView のアイテムをタップした後の流れについて考えましょう。ユーザーが選択した現在地を確認できるように、選択した場所にマーカーを追加して地図上に示すことができます。ユーザーがマーカーをクリックすると、情報ウィンドウが開き、場所の名前と住所が表示されます。

以下のクリック ハンドラを pickCurrentPlace メソッドの後に貼り付けます。

MapsActivity.java listClickedHandler

    private AdapterView.OnItemClickListener listClickedHandler = new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView parent, View v, int position, long id) {
            // position will give us the index of which place was selected in the array
            LatLng markerLatLng = mLikelyPlaceLatLngs[position];
            String markerSnippet = mLikelyPlaceAddresses[position];
            if (mLikelyPlaceAttributions[position] != null) {
                markerSnippet = markerSnippet + "\n" + mLikelyPlaceAttributions[position];
            }

            // Add a marker for the selected place, with an info window
            // showing information about that place.
            mMap.addMarker(new MarkerOptions()
                    .title(mLikelyPlaceNames[position])
                    .position(markerLatLng)
                    .snippet(markerSnippet));

           // Position the map's camera at the location of the marker.
            mMap.moveCamera(CameraUpdateFactory.newLatLng(markerLatLng));
        }
    };

ListView にリストが表示されるようにする

ユーザーが現在訪問中の可能性が高い場所のリストを取得したら、そのリストを ListView でユーザーに表示できます。ListView クリック リスナーを設定して、定義したクリック ハンドラを使用することもできます。

クリック ハンドラの後に次のメソッドを貼り付けます。

MapsActivity.java fillPlacesList()

    private void fillPlacesList() {
        // Set up an ArrayAdapter to convert likely places into TextViews to populate the ListView
        ArrayAdapter<String> placesAdapter =
                new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mLikelyPlaceNames);
        lstPlaces.setAdapter(placesAdapter);
        lstPlaces.setOnItemClickListener(listClickedHandler);
    }

fillPlacesList が定義されたら、fillPlacesList を呼び出してコメント化を解除する findPlaceLikelihoods の最後の方に行を見つけます。

MapsActivity.java fillPlaceLikelihoods()

               // COMMENTED OUT UNTIL WE DEFINE THE METHOD
                // Populate the ListView
                fillPlacesList();

これで、Current Place Picker に必要なコードがすべて揃いました。

13. アプリを実行する

場所の選択をテストする

  1. アプリを再度実行します。

[場所を選択] をタップすると今回は、位置情報に近い名前付きの場所のリストがアプリに表示されます。マウイ島のこの位置の周辺には、Ululani's Hawaiian Shave Ice や Sugar Beach Bake Shop などの場所があります。この位置の座標の非常に近くにある複数の場所が、現在地である可能性の高い場所としてリストに表示されます。

  1. ListView で場所の名前をクリックします。

地図にマーカーが追加されます。

  1. マーカーをタップします。

場所の詳細が表示されます。

e52303cc0de6a513.png 864c74342fb52a01.png

別の位置をテストする

エミュレータを使用している際に位置を変更する場合、エミュレータの拡張メニューで位置座標を更新してもデバイスの位置情報は自動的には更新されません。

この問題を回避するには、次の手順でネイティブの Google マップ アプリを使用してエミュレータの位置を強制的に更新します。

  1. Google マップを開きます。
  2. [...] > [Location] をタップし、緯度と経度の座標を新しい座標に変更して、[Send] をタップします。
  3. たとえば、緯度: 49.2768、経度: -123.1142 を使用すると、カナダ バンクーバーのダウンタウンに位置情報を設定することができます。
  4. Google マップの中心が新しい座標に更新されたことを確認します。中心の変更をリクエストするには、Google マップ アプリの [現在地] ボタンをタップします。
  5. Current Place アプリに戻り、[場所を選択] をタップすると、新しい座標で地図を取得できます。また、現在地である可能性の高い場所の新しいリストが表示されます。

9adb99d1ce25c184.png

以上で完了です。現在地の近くにある場所を確認して、現在地である可能性の高い場所のリストを提供することができるシンプルなアプリを作成しました。

変更を加えたアプリを実行して、次のボーナス ステップを完了しましょう。

14. 次のステップ

API キーの盗難を防ぐには、ご自身の Android アプリだけがキーを使用できるように保護する必要があります。制限されていないキーがあれば、誰でもそのキーを使って Google Maps Platform API を呼び出し、課金を発生させることができます。

SHA-1 証明書を取得する

これは、後ほど API キーを制限するときに必要になります。デバッグ用証明書を取得するための一連の手順は、次のとおりです。

Linux や macOS の場合は、ターミナル ウィンドウを開いて次のコマンドを入力します。

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

Windows Vista と Windows 7 の場合は、次のコマンドを実行します。

keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android

次のような出力が表示されます。

Alias name: androiddebugkey
Creation date: Jan 01, 2013
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Android Debug, O=Android, C=US
Issuer: CN=Android Debug, O=Android, C=US
Serial number: 4aa9b300
Valid from: Mon Jan 01 08:04:04 UTC 2013 until: Mon Jan 01 18:04:04 PST 2033
Certificate fingerprints:
     MD5:  AE:9F:95:D0:A6:86:89:BC:A8:70:BA:34:FF:6A:AC:F9
     SHA1: BB:0D:AC:74:D3:21:E1:43:07:71:9B:62:90:AF:A1:66:6E:44:5D:75
     Signature algorithm name: SHA1withRSA
     Version: 3

SHA1 で始まる行に、証明書の SHA-1 フィンガープリントが含まれています。このフィンガープリントは、コロンで区切られた 20 個の 2 桁の 16 進数で構成されるシーケンスです。

アプリのリリースの準備が整ったら、こちらのドキュメントの手順に沿ってリリース証明書を取得してください。

API キーに制限を追加する

  1. Cloud Console で、[API とサービス] > [認証情報] に移動します。

このアプリに使用したキーが [API キー] の下に表示されます。

  1. 6454a04865d551e6.png をクリックして、キーの設定を編集します。

316b052c621ee91c.png

  1. API キーのページで [キーの制限] の下の [アプリケーションの制限] を設定します。
  2. [Android アプリ] を選択し、表示される手順に沿って操作します。
  3. [項目を追加] をクリックします。
  4. パッケージ名と SHA-1 証明書のフィンガープリント(前のセクションで取得)を入力します。

例:

com.google.codelab.currentplace
BB:0D:AC:74:D3:21:E1:43:07:71:9B:62:90:AF:A1:66:6E:44:5D:75s
  1. さらに保護するには、次の手順に沿って [API の制限] を設定します。
  2. [API の制限] の下で [キーを制限] を選択します。
  3. Maps SDK for Android と Places API を選択します。
  4. [完了]、[保存] の順にクリックします。

15. 完了

現在地の可能性が最も高い場所を確認し、ユーザーが選択した場所のマーカーを地図に追加する簡単なアプリを作成しました。

さらに学びましょう