יצירת כלי לבחירת המיקום הנוכחי ב-Android (Java)

1. לפני שמתחילים

כאן מוסבר איך להשתמש בפלטפורמה של מפות Google וב-Places SDK ל-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

ה-Lab הזה נכתב באמצעות Android Studio 3.4.

Android SDK

ב-Android Studio, אפשר להגדיר את ערכות ה-SDK הרצויות באמצעות SDK Manager. בשיעור ה-Lab הזה נשתמש ב-Android Q SDK.

  1. במסך הפתיחה של Android Studio, לוחצים על Configure (הגדרה) > SDK Manager (כלי לניהול ערכות SDK).

d3fa03c269ec231c.png

  1. מסמנים את התיבה של ה-SDK הרצוי ולוחצים על אישור.

אם עדיין לא התקנתם את ה-SDK, הפעולה הזו תתחיל את ההורדה של ה-SDK למחשב שלכם.

884e0aa1314f70d.png

שירותי Google Play

בנוסף, צריך להתקין את Google Play Services דרך הכלי לניהול ערכות SDK.

  1. לוחצים על הכרטיסייה SDK Tools ומסמנים את תיבת הסימון Google Play services.

אם הסטטוס הוא יש עדכון זמין, מעדכנים.

ad6211fd78f3b629.png

3. הכנת האמולטור

כדי להפעיל את האפליקציה, אפשר לחבר מכשיר משלכם או להשתמש באמולטור Android.

אם אתם משתמשים במכשיר שלכם, דלגו אל ההוראות למכשיר אמיתי: עדכון Google Play Services בסוף הדף הזה.

הוספת אמולטור

  1. במסך הפתיחה של Android Studio, לוחצים על Configure (הגדרה) > AVD Manager (מנהל מכשירים וירטואליים).

5dd2d14c9c56d3f9.png

תיבת הדו-שיח Android Virtual Device Manager תיפתח.

  1. לוחצים על יצירת מכשיר וירטואלי... כדי לפתוח רשימה של מכשירים שאפשר לבחור מתוכה.

2d44eada384f8b35.png

  1. בוחרים מכשיר עם הסמל של Play d5722488d80cd6be.png בעמודה Play Store ולוחצים על הבא.

e0248f1c6e85ab7c.png

יוצג לכם אוסף של תמונות מערכת שתוכלו להתקין. אם לצד Q לטירגוט Android 9.+‎ (Google Play) מופיעה המילה הורדה, לוחצים על הורדה.

316d0d1efabd9f24.png

  1. לוחצים על הבא כדי לתת שם למכשיר הווירטואלי, ואז לוחצים על סיום.

חוזרים לרשימה של המכשירים הווירטואליים שלכם.

  1. לוחצים על סמל ההתחלה ba8adffe56d3b678.png לצד המכשיר החדש:

7605864ed27f77ea.png

אחרי כמה רגעים, האמולטור ייפתח.

הוראות לאמולטור – עדכון של Google Play Services

  1. אחרי שהאמולטור מופעל, לוחצים על ... בסרגל הניווט שמופיע**.**

2e1156e02643d018.png

תיבת הדו-שיח פקדים מתקדמים תיפתח.

  1. בתפריט, לוחצים על Google Play.

אם יש עדכון, לוחצים על עדכון.

5afd2686c5cad0e5.png

  1. נכנסים לאמולטור באמצעות חשבון Google.

אתם יכולים להשתמש בחשבון קיים או ליצור חשבון חדש בחינם כדי להפריד בין הבדיקות לבין המידע האישי שלכם.

אחרי זה, Google Play נפתחת אל Google Play Services.

  1. לוחצים על עדכון כדי לקבל את הגרסה האחרונה של Google Play Services.

f4bc067e80630b9c.png

אם תתבקשו להשלים את הגדרת החשבון ולהוסיף אמצעי תשלום, לחצו על דילוג.

הגדרת מיקום באמולטור

  1. אחרי שהאמולטור מופעל, מקלידים 'מפות' בסרגל החיפוש במסך הבית כדי להציג את הסמל של אפליקציית מפות Google.

2d996aadd53685a6.png

  1. לוחצים על הסמל כדי להפעיל את האפליקציה.

מוצגת מפת ברירת המחדל.

  1. בפינה השמאלית התחתונה של המפה, לוחצים על המיקום שלך c5b4e2fda57a7e71.png.

מתבקשים לתת לטלפון הרשאות להשתמש במיקום.

f2b68044eabca151.png

  1. לוחצים על ... כדי לפתוח את התפריט אמצעי בקרה מתקדמים.
  2. לחץ על הכרטיסייה מיקום.
  3. מזינים קו רוחב וקו אורך.

אפשר להזין כאן כל דבר שרוצים, אבל חשוב לוודא שזהו אזור עם הרבה מקומות.

(כדי לשחזר את התוצאות מ-codelab הזה, צריך להשתמש בקו הרוחב 20.7818 ובקו האורך ‎-156.4624 של העיירה קיחיי במאווי שבהוואי).

  1. לוחצים על שליחה והמיקום הזה יתווסף למפה.

f9576b35218f4187.png

האפליקציה מוכנה להפעלה ולבדיקה עם מיקום.

הוראות למכשיר אמיתי – עדכון של Google Play Services

אם משתמשים במכשיר Android אמיתי, צריך לבצע את הפעולות הבאות:

  1. משתמשים בסרגל החיפוש במסך הבית כדי לחפש את Google Play Services ולפתוח אותו.
  2. לוחצים על פרטים נוספים.

אם האפשרות זמינה, לוחצים על עדכון.

ad16cdb975b5c3f7.png baf0379ef8a9c88c.png

4. יצירת מעטפת האפליקציה באמצעות פעילות במפות Google

  1. במסך הפתיחה של Android Studio, בוחרים באפשרות Start a new Android Studio project (התחלת פרויקט חדש ב-Android Studio).
  2. בכרטיסייה טלפון וטאבלט, בוחרים באפשרות פעילות במפות Google.

c9c80aa8211a8761.png

נפתחת תיבת הדו-שיח הגדרת הפרויקט. כאן נותנים שם לאפליקציה ויוצרים את החבילה על סמך הדומיין.

אלה ההגדרות של אפליקציה בשם Current Place, שמתאימה לחבילה com.google.codelab.currentplace.

37f5b93b94ee118c.png

  1. בוחרים באפשרות Java כשפה ובוחרים באפשרות Use androidx. artifacts*.

משאירים את הגדרות ברירת המחדל בשאר ההגדרות.

  1. לוחצים על סיום.

5. הוספת תלויות של שירותי Google לקובץ ה-build של Gradle

כדי לגשת להרשאות מיקום ב-Android, צריך את Google Location and Activity Recognition API מ-Google Play Services. למידע נוסף על הוספה של ממשקי ה-API האלה ושל ממשקי API אחרים של Google Play Services, אפשר לעיין במאמר בנושא הגדרת Google Play Services.

בדרך כלל יש שני קובצי build.gradle בפרויקטים של Android Studio. אחד מהם הוא לפרויקט כולו והשני הוא לאפליקציה. אם אתם משתמשים בסייר הפרויקטים של Android Studio בתצוגה Android, תוכלו לראות את שניהם בתיקייה Gradle Scripts. צריך לערוך את קובץ build.gradle (Module: app) כדי להוסיף שירותי Google.

f3043429cf719c47.png

  1. מוסיפים שני קווים לקטע dependencies כדי להוסיף את שירותי Google למיקום ואת Places API ( קוד לדוגמה בהקשר).

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. הפעלת ממשקי API של הפלטפורמה של מפות Google וקבלת מפתח API

בשלב הבא בהפעלה , צריך להפעיל את Maps SDK ל-Android ואת Places API.

הגדרת הפלטפורמה של מפות Google

אם עדיין אין לכם חשבון ב-Google Cloud Platform ופרויקט עם חיוב מופעל, תוכלו לעיין במדריך תחילת העבודה עם הפלטפורמה של מפות Google כדי ליצור חשבון לחיוב ופרויקט.

  1. בCloud Console, לוחצים על התפריט הנפתח של הפרויקט ובוחרים את הפרויקט שבו רוצים להשתמש ב-codelab הזה.

  1. מפעילים ב-Google Cloud Marketplace את ממשקי ה-API וערכות ה-SDK של הפלטפורמה של מפות Google שנדרשים ל-codelab הזה. כדי לעשות זאת, פועלים לפי השלבים בסרטון הזה או בתיעוד הזה.
  2. יוצרים מפתח API בדף Credentials במסוף Cloud. אפשר לפעול לפי השלבים שמפורטים בסרטון הזה או בתיעוד הזה. כל הבקשות אל הפלטפורמה של מפות Google מחייבות מפתח API.

מעתיקים את מפתח ה-API שיצרתם. חוזרים ל-Android Studio ומחפשים את הקובץ google_maps_api.xml בקטע Android > app > res > values.

מחליפים את הערך YOUR_KEY_HERE במפתח ה-API שהעתקתם.

aa576e551a7a1009.png

האפליקציה מוגדרת עכשיו.

7. עריכת קובץ הפריסה

  1. בסייר הפרויקטים, פותחים את הקובץ activity_maps.xml בתיקייה Android > app > res > layout.

4e0d986480c57efa.png

  1. ממשק המשתמש הבסיסי ייפתח בצד שמאל של המסך, עם כרטיסיות בתחתית שמאפשרות לבחור את עורך העיצוב או את עורך הטקסט של הפריסה. בוחרים באפשרות טקסט ומחליפים את כל התוכן של קובץ הפריסה בתוכן הבא:

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. בתפריט סוג הסמל, לוחצים על סמלים של סרגל פעולות וכרטיסיות.
  2. נותנים שם לנכס ic_geolocate.
  3. בוחרים באפשרות Clip Art בתור סוג הנכס**.**
  4. לוחצים על הגרפיקה לצד Clip Art (אוסף תמונות).

ייפתח החלון בחירת סמל.

  1. בוחרים סמל.

אפשר להשתמש בסרגל החיפוש כדי למצוא סמלים שקשורים לכוונת החיפוש.

  1. מחפשים את location ובוחרים סמל שקשור למיקום.

סמל המיקום שלי זהה לסמל שמופיע באפליקציית מפות Google כשמשתמש רוצה להעביר את המצלמה למיקום הנוכחי שלו.

  1. לוחצים על אישור > הבא > סיום,ומוודאים שיש תיקייה חדשה בשם 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>

השורה הראשונה משמשת בסרגל האפליקציות כשיש מקום לכלול תווית טקסט לצד הסמל. הסמלים האחרים משמשים לסמנים שאתם מוסיפים למפה.

עכשיו הקוד בקובץ נראה כך:

<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 ובוחרים באפשרות חדש > קובץ.
  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, ומחליפים את הצהרות הייבוא הקיימות.

הם כוללים את הייבוא הקיים ומוסיפים עוד הרבה ייבואים שמשמשים בקוד במעבדת הקוד הזו.

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 {

הוספת משתנים לכיתה

לאחר מכן, מצהירים על משתני המחלקה השונים שמשמשים בשיטות שונות של המחלקה. הם כוללים את רכיבי ממשק המשתמש ואת קודי הסטטוס. הם צריכים להופיע מיד מתחת להצהרת המשתנה 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;

עדכון ה-method‏ onCreate

תצטרכו לעדכן את השיטה onCreate כדי לטפל בהרשאות משתמש בזמן ריצה לשירותי מיקום, להגדיר את רכיבי ממשק המשתמש וליצור את לקוח Places API.

מוסיפים את שורות הקוד הבאות לגבי סרגל הכלים של הפעולות, הגדרת התצוגות ולקוח Places בסוף השיטה הקיימת 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);
    }

הוספת קוד לתפריט של סרגל האפליקציות

שתי השיטות האלה מוסיפות את תפריט סרגל האפליקציות (עם פריט אחד, הסמל של בחירת מקום) ומטפלות בקליק של המשתמש על הסמל.

מעתיקים את שתי השיטות האלה לקובץ אחרי השיטה onCreate.

‫onCreateOptionsMenu() ו-onOptionsItemSelected() ב-MapsActivity.java

    @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 ‘app'.

28bea91c68c36fb2.png

  1. תתבקשו לבחור את יעד הפריסה. האמולטור הפועל אמור להופיע ברשימה הזו. בוחרים אותו, ו-Android Studio פורס את האפליקציה באמולטור בשבילכם.

f44658ca91f6f41a.png

אחרי כמה רגעים, האפליקציה תופעל. המפה מוצגת כשהיא ממוקדת בסידני, אוסטרליה, עם לחצן אחד ורשימת מקומות ריקה.

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 כדי להפעיל את אמצעי הבקרה של הזום ולבקש מהמשתמש הרשאות גישה למיקום.

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 קוראת לפונקציית ה-callback הזו.

מדביקים את הקוד הזה אחרי השיטה 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,, אחרי אחזור המיקום האחרון של המכשיר.

שליחת קריאה ל-API של findCurrentPlace וטיפול בתגובה

getCurrentPlaceLikelihoods יוצר findCurrentPlaceRequest וקורא למשימה Places API findCurrentPlace. אם המשימה מצליחה, היא מחזירה findCurrentPlaceResponse, שמכיל רשימה של אובייקטים מסוג placeLikelihood. לכל אחד מהם יש מספר מאפיינים, כולל השם והכתובת של המקום, וההסתברות שאתם נמצאים במקום הזה (ערך כפול מ-0 עד 1). השיטה הזו מטפלת בתגובה על ידי בניית רשימות של פרטי מקומות מתוך placeLikelihoods.

הקוד הזה מבצע איטרציה על חמשת המקומות הסבירים ביותר ומוסיף לרשימה את המקומות עם סבירות גבוהה מ-0, ואז מעבד את הרשימה. אם רוצים להציג יותר או פחות מחמש, צריך לערוך את הקבוע 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, מאתרים את השורה ב-onOptionsItemSelected() שקוראת ל-pickCurrentPlace ומבטלים את ההערה שלה.

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 הצליחה, היומנים יכללו חמש הצהרות שמדפיסות את השמות והמיקומים של חמשת המקומות הסבירים ביותר.

d9896a245b81bf3.png

12. איכלוס הכלי לבחירת המקום הנוכחי

הגדרת handler למקומות שנבחרו

בואו נחשוב מה אנחנו רוצים שיקרה כשהמשתמש ילחץ על פריט ב-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 click listener כך שישתמש ב-click handler שהגדרתם זה עתה.

מדביקים את השיטה הזו אחרי click handler:

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, מחפשים את השורה לקראת סוף findPlaceLikelihoods שקוראת ל-fillPlacesList ומבטלים את ההערה שלה.

MapsActivity.java fillPlaceLikelihoods()

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

זה כל הקוד שצריך בשביל הכלי לבחירת המיקום הנוכחי.

13. הפעלת האפליקציה

בדיקת בחירת מקום

  1. מריצים את האפליקציה שוב.

הפעם, כשתקישו על בחירת מקום, האפליקציה תמלא את הרשימה במקומות עם שמות שנמצאים קרוב למיקום. בקרבת המיקום הזה במאווי נמצאים מקומות כמו Ululani's Hawaiian Shave Ice ו-Sugar Beach Bake Shop. הרשימה הזו כוללת מקומות שסביר להניח שאתם נמצאים בהם, כי הם קרובים מאוד לקואורדינטות של המיקום.

  1. לוחצים על שם של מקום בListView.

סמן יתווסף למפה.

  1. מקישים על הסמן.

תוכלו לראות את פרטי המקום.

e52303cc0de6a513.png 864c74342fb52a01.png

בדיקת מיקום אחר

אם אתם רוצים לשנות את המיקום ואתם משתמשים באמולטור, מיקום המכשיר לא מתעדכן אוטומטית כשאתם מעדכנים את קואורדינטות המיקום בתפריט המורחב של האמולטור.

כדי לעקוף את הבעיה הזו, צריך לפעול לפי השלבים הבאים כדי להשתמש באפליקציית מפות Google המקורית כדי לכפות עדכונים למיקום של האמולטור:

  1. פתח את מפות Google.
  2. מקישים על ... > מיקום כדי לשנות את קו האורך וקו הרוחב לקואורדינטות חדשות, ואז מקישים על שליחה.
  3. לדוגמה, אפשר להשתמש בקו הרוחב: 49.2768 ובקו האורך: ‎-123.1142 כדי להגדיר את המיקום למרכז העיר ונקובר, קנדה.
  4. מוודאים שמפות Google התמקדה מחדש בקואורדינטות החדשות. יכול להיות שתצטרכו להקיש על הלחצן המיקום שלי באפליקציית מפות Google כדי לבקש את המיקום המרכזי.
  5. חוזרים לאפליקציה 'המקום הנוכחי שלי' ומקישים על בחירת מקום כדי לראות את המפה עם הקואורדינטות החדשות ואת הרשימה החדשה של המקומות הנוכחיים האפשריים.

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 מספרים הקסדצימליים דו-ספרתיים שמופרדים באמצעות נקודתיים.

כשמוכנים לפרסם אפליקציה, משתמשים בהוראות שבמסמכי התיעוד האלה כדי לאחזר את אישור הפרסום.

הוספת הגבלות למפתח ה-API

  1. ב-Cloud Console, עוברים אל APIs & Services (ממשקי API ושירותים) > Credentials (אמצעי אימות).

המפתח שבו השתמשתם לאפליקציה הזו אמור להופיע בקטע 'מפתחות API'.

  1. לוחצים על 6454a04865d551e6.png כדי לערוך את הגדרות המקשים.

316b052c621ee91c.png

  1. בדף של מפתח ה-API, אחרי Key restrictions, מגדירים את Application restrictions באופן הבא:
  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, בוחרים באפשרות Restrict key.
  3. בוחרים באפשרות Maps SDK for Android (SDK של מפות ל-Android) וב-Places API (Places API).
  4. לוחצים על סיום ואז על שמירה.

15. מזל טוב

יצרתם אפליקציה פשוטה שבודקת מהם המקומות הסבירים ביותר במיקום הנוכחי ומוסיפה סמן למפה עבור המקום שהמשתמש בוחר.

מידע נוסף