תחילת העבודה עם Places SDK ל-Android (Kotlin)

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

ב-codelab הזה נסביר איך לשלב את Places SDK ל-Android באפליקציה ואיך להשתמש בכל אחת מהתכונות של Places SDK.

אפליקציית הדגמה של Places

דרישות מוקדמות

  • ידע בסיסי ב-Kotlin ובפיתוח ל-Android

מה תלמדו

  • איך מתקינים את Places SDK ל-Android עם Kotlin Extensions.
  • איך לטעון את פרטי המקום של מקום ספציפי.
  • איך מוסיפים לאפליקציה ווידג'ט של השלמה אוטומטית של מקומות.
  • איך לטעון את המקום הנוכחי על סמך המיקום הנוכחי שמדווח על ידי המכשיר.

מה נדרש

כדי להשלים את ה-codelab הזה, תצטרכו את החשבונות, השירותים והכלים הבאים:

2. להגדרה

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

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

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

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

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

3. התחלה מהירה

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

  1. משכפלים את המאגר אם git מותקן.
git clone https://github.com/googlemaps/codelab-places-101-android-kotlin.git

אפשר גם ללחוץ על הכפתור הזה כדי להוריד את קוד המקור.

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

כדי לראות את קוד הפתרון המלא בפעולה, אפשר לעיין בקוד המלא בספרייה /solution.

4. הוספת מפתח ה-API לפרויקט

בקטע הזה מתואר איך לאחסן את מפתח ה-API כך שהאפליקציה תוכל להפנות אליו בצורה מאובטחת. לא מומלץ להוסיף את מפתח ה-API למערכת בקרת הגרסאות, ולכן אנחנו ממליצים לאחסן אותו בקובץ secrets.properties, שיוצב בעותק המקומי של ספריית הבסיס של הפרויקט. מידע נוסף על הקובץ secrets.properties זמין במאמר קובצי מאפיינים של Gradle.

כדי לייעל את המשימה הזו, מומלץ להשתמש בפלאגין של Secrets Gradle ל-Android.

כדי להתקין את הפלאגין Secrets Gradle ל-Android בפרויקט של מפות Google:

  1. ב-Android Studio, פותחים את הקובץ build.gradle.kts או build.gradle ברמה העליונה ומוסיפים את הקוד הבא לרכיב dependencies בקטע buildscript.

אם משתמשים ב-build.gradle.kts, מוסיפים:

buildscript {
    dependencies {
        classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
    }
}

אם משתמשים ב-build.gradle, מוסיפים:

buildscript {
    dependencies {
        classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
    }
}
  1. פותחים את הקובץ build.gradle.kts או build.gradle ברמת המודול ומוסיפים את הקוד הבא לרכיב plugins.

אם משתמשים ב-build.gradle.kts, מוסיפים:

plugins {
    // ...
    id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
}

אם משתמשים ב-build.gradle, מוסיפים:

plugins {
    // ...
    id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
  1. בקובץ build.gradle.kts או build.gradle ברמת המודול, מוודאים שהערכים של targetSdk ו-compileSdk מוגדרים ל-34.
  2. שומרים את הקובץ ומסנכרנים את הפרויקט עם Gradle.
  3. פותחים את הקובץ secrets.properties בספרייה ברמה העליונה ומוסיפים את הקוד הבא. מחליפים את הערך YOUR_API_KEY במפתח ה-API שלכם. כדאי לאחסן את המפתח בקובץ הזה כי secrets.properties לא נכלל בבדיקה במערכת בקרת גרסאות.
PLACES_API_KEY=YOUR_API_KEY
  1. שומרים את הקובץ.
  2. יוצרים את הקובץ local.defaults.properties בתיקייה ברמה העליונה, באותה תיקייה שבה נמצא הקובץ secrets.properties, ואז מוסיפים את הקוד הבא.
PLACES_API_KEY=DEFAULT_API_KEY

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

  1. שומרים את הקובץ.
  2. ב-Android Studio, פותחים את הקובץ build.gradle.kts או build.gradle ברמת המודול ועורכים את המאפיין secrets. אם הנכס secrets לא קיים, מוסיפים אותו.

עורכים את המאפיינים של הפלאגין כדי להגדיר את propertiesFileName לערך secrets.properties, את defaultPropertiesFileName לערך local.defaults.properties ואת כל המאפיינים האחרים.

secrets {
    // Optionally specify a different file name containing your secrets.
    // The plugin defaults to "local.properties"
    propertiesFileName = "secrets.properties"

    // A properties file containing default secret values. This file can be
    // checked in version control.
    defaultPropertiesFileName = "local.defaults.properties"
}

5. התקנה של Places SDK ל-Android

בקטע הזה מוסיפים את Places SDK ל-Android לתלות של האפליקציה.

  1. עכשיו, כשמפתח ה-API נגיש בתוך האפליקציה, מוסיפים את התלות ב-Places SDK for Android לקובץ build.gradle של האפליקציה.

משנים את קובץ build.gradle ברמת האפליקציה כדי להוסיף את התלות ב-Places SDK ל-Android:

app-level build.gradle

dependencies {
   // Dependency to include Places SDK for Android
   implementation 'com.google.android.libraries.places:places:3.4.0'
}
  1. מפעילים את האפליקציה.

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

6. התקנה של Places Android KTX

באפליקציות Kotlin שמשתמשות באחד או יותר מ-Google Maps Platform Android SDKs, ספריות Kotlin extension (KTX) מאפשרות לכם ליהנות מתכונות של שפת Kotlin, כמו קורוטינות, מאפייני הרחבה/פונקציות הרחבה ועוד. לכל Google Maps SDK יש ספריית KTX תואמת, כמו שמוצג בהמשך:

תרשים של Google Maps Platform KTX

במשימה הזו תשתמשו בספריית Places Android KTX כדי להשתמש בתכונות שפה ספציפיות ל-Kotlin באפליקציה שלכם.

הוספת יחסי התלות של Places Android KTX

כדי ליהנות מהתכונות הספציפיות ל-Kotlin, צריך לכלול את ספריית ה-KTX המתאימה ל-SDK הזה בקובץ build.gradle ברמת האפליקציה.

build.gradle

dependencies {
    // ...

    // Places SDK for Android KTX Library
    implementation 'com.google.maps.android:places-ktx:3.1.1'
}

7. אתחול של Places Client

אתחול Places SDK בהיקף האפליקציה

בתוך הקובץ DemoApplication.kt של התיקייה app/src/main/java/com/google/codelabs/maps/placesdemo, מפעילים את Places SDK ל-Android. מדביקים את השורות הבאות בסוף הפונקציה onCreate:

        // Initialize the SDK with the Google Maps Platform API key
        Places.initialize(this, BuildConfig.PLACES_API_KEY)

כשאתם יוצרים את האפליקציה, הפלאגין של Secrets Gradle ל-Android הופך את מפתח ה-API בקובץ secrets.properties לזמין כ-BuildConfig.PLACES_API_KEY.

הוספת קובץ האפליקציה למניפסט

הארכת את Application באמצעות DemoApplication, ולכן צריך לעדכן את המניפסט. מוסיפים את המאפיין android:name לרכיב application בקובץ AndroidManifest.xml שנמצא במיקום app/src/main:

    <application
        android:name=".DemoApplication"
        ...
    </application>

הקוד הזה מפנה את מניפסט האפליקציה לכיתה DemoApplication בתיקייה src/main/java/com/google/codelabs/maps/placesdemo/.

8. אחזור פרטי מקום

יצירת מסך פרטים

פריסת activity_details.xml עם LinearLayout ריק זמינה בתיקייה app/src/main/res/layout/. מאכלסים את הפריסה הקווית על ידי הוספת הקוד הבא בין הסוגריים <LinearLayout>.

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/details_input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/details_input_hint"
            android:text="@string/details_input_default" />

    </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/details_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/button_details" />

    <TextView
        android:id="@+id/details_response_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        android:textIsSelectable="true" />

הקוד הזה מוסיף שדה להזנת טקסט שבו המשתמש יכול להזין מזהה מקום כלשהו או להשתמש בברירת המחדל שסופקה, לחצן להפעלת הבקשה לפרטי מקום ו-TextView להצגת המידע מהתגובה. המחרוזות המשויכות מוגדרות בשבילכם בקובץ src/main/res/values/strings.xml.

יצירת פעילות מסוג 'פרטים'

  1. יוצרים קובץ DetailsActivity.kt בתיקייה src/main/java/com/google/codelabs/maps/placesdemo/ ומשייכים אותו לפריסה שיצרתם. מדביקים את הקוד הבא בקובץ:
@ExperimentalCoroutinesApi
class DetailsActivity : AppCompatActivity() {
    private lateinit var placesClient: PlacesClient
    private lateinit var detailsButton: Button
    private lateinit var detailsInput: TextInputEditText
    private lateinit var responseView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_details)

        // Set up view objects
        detailsInput = findViewById(R.id.details_input)
        detailsButton = findViewById(R.id.details_button)
        responseView = findViewById(R.id.details_response_content)

        val apiKey = BuildConfig.PLACES_API_KEY

        // Log an error if apiKey is not set.
        if (apiKey.isEmpty() || apiKey == "DEFAULT_API_KEY") {
            Log.e(TAG, "No api key")
            finish()
            return
        }
    }
}
  1. יוצרים לקוח Places לשימוש בפעילות הזו. מדביקים את הקוד הזה אחרי הקוד לבדיקת מפתח ה-API בפונקציה onCreate.
        // Retrieve a PlacesClient (previously initialized - see DemoApplication)
        placesClient = Places.createClient(this)
  1. אחרי שמגדירים את Places Client, מצרפים ללחצן click listener. מדביקים את הקוד הזה אחרי יצירת Places Client בפונקציה onCreate.
        // Upon button click, fetch and display the Place Details
        detailsButton.setOnClickListener { button ->
            button.isEnabled = false
            val placeId = detailsInput.text.toString()
            val placeFields = listOf(
                Place.Field.NAME,
                Place.Field.ID,
                Place.Field.LAT_LNG,
                Place.Field.ADDRESS
            )
            lifecycleScope.launch {
                try {
                    val response = placesClient.awaitFetchPlace(placeId, placeFields)
                    responseView.text = response.prettyPrint()
                } catch (e: Exception) {
                    e.printStackTrace()
                    responseView.text = e.message
                }
                button.isEnabled = true
            }
        }

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

הוספת פעילות הפרטים למניפסט

מוסיפים אלמנט <activity> בשביל DetailsActivity כצאצא של האלמנט <application> בקובץ AndroidManifest.xml שנמצא במיקום app/src/main:

        <activity android:name=".DetailsActivity" android:label="@string/details_demo_title" />

הוספת פעילות הפרטים לתפריט ההדגמה

מודול Demo ריק מסופק כדי להציג את ההדגמות הזמינות במסך הבית. אחרי שיצרתם פעילות של פרטי מקום, מוסיפים אותה לקובץ Demo.kt בתיקייה src/main/java/com/google/codelabs/maps/placesdemo/ באמצעות הקוד הבא:

    DETAILS_FRAGMENT_DEMO(
        R.string.details_demo_title,
        R.string.details_demo_description,
        DetailsActivity::class.java
    ),

המחרוזות המשויכות מוגדרות בקובץ src/main/res/values/strings.xml.

בודקים את MainActivity.kt ורואים שהוא יוצר ListView שאוכלס על ידי איטרציה על התוכן של המודול Demo. אם המשתמש מקיש על פריט ברשימה, מאזין הקליקים פותח את הפעילות המשויכת.

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

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

פעילות בפרטי מקום עם תגובה

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

9. הוספת השלמה אוטומטית למקומות

יצירת מסך השלמה אוטומטית

פריסת activity_autocomplete.xml עם LinearLayout ריק מסופקת בתיקייה app/src/main/res/layout/. מאכלסים את הפריסה הקווית על ידי הוספת הקוד הזה בין הסוגריים <LinearLayout>.

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/autocomplete_fragment"
        android:background="@android:color/white"
        android:name="com.google.android.libraries.places.widget.AutocompleteSupportFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/autocomplete_response_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        android:textIsSelectable="true" />

הקוד הזה מוסיף ווידג'ט AutocompleteSupportFragment ו-TextView להצגת המידע מהתגובה. המחרוזות המשויכות מוגדרות בקובץ src/main/res/values/strings.xml.

יצירת פעילות של השלמה אוטומטית

  1. יוצרים קובץ AutocompleteActivity.kt בתיקייה src/main/java/com/google/codelabs/maps/placesdemo/ ומגדירים אותו באמצעות הקוד הזה:
@ExperimentalCoroutinesApi
class AutocompleteActivity : AppCompatActivity() {
    private lateinit var responseView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_autocomplete)

        // Set up view objects
        responseView = findViewById(R.id.autocomplete_response_content)
        val autocompleteFragment =
            supportFragmentManager.findFragmentById(R.id.autocomplete_fragment)
                    as AutocompleteSupportFragment
    }
}

הקוד הזה משייך את הפעילות לתצוגות ול-AutocompleteSupportFramgent שהגדרתם בקובץ הפריסה.

  1. לאחר מכן, מגדירים מה קורה כשהמשתמש בוחר אחת מהתחזיות שמוצגות על ידי השלמה אוטומטית של מקומות. מוסיפים את הקוד הזה לסוף הפונקציה onCreate:
        val placeFields: List<Place.Field> =
            listOf(Place.Field.NAME, Place.Field.ID, Place.Field.ADDRESS, Place.Field.LAT_LNG)
        autocompleteFragment.setPlaceFields(placeFields)

        // Listen to place selection events
        lifecycleScope.launchWhenCreated {
            autocompleteFragment.placeSelectionEvents().collect { event ->
                when (event) {
                    is PlaceSelectionSuccess -> {
                        val place = event.place
                        responseView.text = prettyPrintAutocompleteWidget(place, false)
                    }

                    is PlaceSelectionError -> Toast.makeText(
                        this@AutocompleteActivity,
                        "Failed to get place '${event.status.statusMessage}'",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
        }

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

הוספת פעילות ההשלמה האוטומטית למניפסט

מוסיפים אלמנט <activity> בשביל AutocompleteActivity כצאצא של האלמנט <application> בקובץ AndroidManifest.xml שנמצא במיקום app/src/main:

        <activity android:name=".AutocompleteActivity" android:label="@string/autocomplete_fragment_demo_title" />

הוספת פעילות ההשלמה האוטומטית לתפריט ההדגמה

כמו קודם, מוסיפים את ההדגמה של השלמה אוטומטית של מקומות למסך הבית על ידי הוספתה לרשימה במודול Demo. אחרי שיצרתם פעילות של השלמה אוטומטית של מקום, מוסיפים אותה לקובץ Demo.kt בתיקייה src/main/java/com/google/codelabs/maps/placesdemo/. מדביקים את הקוד הזה מיד אחרי הפריט DETAILS_FRAGMENT_DEMO:

    AUTOCOMPLETE_FRAGMENT_DEMO(
        R.string.autocomplete_fragment_demo_title,
        R.string.autocomplete_fragment_demo_description,
        AutocompleteActivity::class.java
    ),

המחרוזות המשויכות מוגדרות בקובץ src/main/res/values/strings.xml.

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

  1. מריצים את האפליקציה. הפעם אמורים להופיע שני פריטים ברשימה במסך הבית.
  2. מקישים על השורה 'השלמה אוטומטית של מקומות'. יופיע חלון קופץ עם קלט של השלמה אוטומטית של מקום, כמו שמוצג באיור 2.
  3. מתחילים להקליד את שם המקום. זה יכול להיות שם של עסק, כתובת או אזור גיאוגרפי. התחזיות צריכות להופיע תוך כדי הקלדה.
  4. בוחרים אחת מהתחזיות. התחזיות אמורות להיעלם, ועכשיו ב-TextView יוצגו הפרטים על המקום שנבחר, כמו שמוצג באיור 3.

פעילות ההשלמה האוטומטית אחרי שהמשתמש מקיש על שדה הקלט

איור 2. השלמה אוטומטית של פעילות אחרי שהמשתמש מקיש על שדה הקלט.

השלמה אוטומטית של פעילות אחרי שהמשתמש הקליד ובחר באפשרות &#39;מפלי ניאגרה&#39;

איור 3. פעילות של השלמה אוטומטית שבה מוצגים פרטי מקום אחרי שהמשתמש הקליד את המילים Niagara Falls (מפלי הניאגרה) ובחר אותן.

10. קבלת המיקום הנוכחי של המכשיר

יצירת מסך 'המקום הנוכחי'

פריסת activity_current.xml עם LinearLayout ריק מסופקת בתיקייה app/src/main/res/layout/. מאכלסים את הפריסה הלינארית על ידי הוספת הקוד הבא בין הסוגריים <LinearLayout>.

    <Button
        android:id="@+id/current_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/current_button" />

    <TextView
        android:id="@+id/current_response_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        android:scrollbars = "vertical"
        android:textIsSelectable="true" />

יצירת פעילות של המקום הנוכחי

  1. יוצרים קובץ CurrentPlaceActivity.kt בתיקייה src/main/java/com/google/codelabs/maps/placesdemo/ ומגדירים אותו באמצעות הקוד הבא:
@ExperimentalCoroutinesApi
class CurrentPlaceActivity : AppCompatActivity(), OnMapReadyCallback {
    private lateinit var placesClient: PlacesClient
    private lateinit var currentButton: Button
    private lateinit var responseView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_current)

        // Retrieve a PlacesClient (previously initialized - see DemoApplication)
        placesClient = Places.createClient(this)

        // Set view objects
        currentButton = findViewById(R.id.current_button)
        responseView = findViewById(R.id.current_response_content)

        // Set listener for initiating Current Place
        currentButton.setOnClickListener {
            checkPermissionThenFindCurrentPlace()
        }
    }
}

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

  1. מגדירים את checkPermissionThenFindCurrentPlace() כדי לבדוק אם יש הרשאה למיקום מדויק, ומבקשים את ההרשאה אם היא עדיין לא ניתנה. מדביקים את הקוד הזה אחרי הפונקציה onCreate.
    /**
     * Checks that the user has granted permission for fine or coarse location.
     * If granted, finds current Place.
     * If not yet granted, launches the permission request.
     * See https://developer.android.com/training/permissions/requesting
     */
    private fun checkPermissionThenFindCurrentPlace() {
        when {
            (ContextCompat.checkSelfPermission(
                this,
                ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(
                this,
                ACCESS_COARSE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED) -> {
                // You can use the API that requires the permission.
                findCurrentPlace()
            }

            shouldShowRequestPermissionRationale(ACCESS_FINE_LOCATION)
            -> {
                Log.d(TAG, "Showing permission rationale dialog")
                // TODO: In an educational UI, explain to the user why your app requires this
                // permission for a specific feature to behave as expected. In this UI,
                // include a "cancel" or "no thanks" button that allows the user to
                // continue using your app without granting the permission.
            }

            else -> {
                // Ask for both the ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions.
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf(
                        ACCESS_FINE_LOCATION,
                        ACCESS_COARSE_LOCATION
                    ),
                    PERMISSION_REQUEST_CODE
                )
            }
        }
    }

    companion object {
        private const val TAG = "CurrentPlaceActivity"
        private const val PERMISSION_REQUEST_CODE = 9
    }
  1. כשענף else של הפונקציה checkPermissionThenFindCurrentPlace קורא לפונקציה requestPermissions, האפליקציה תציג למשתמש תיבת דו-שיח של בקשת הרשאה. אם המשתמש מפעיל מכשיר עם מערכת הפעלה בגרסה נמוכה מ-Android 12, הוא יכול להעניק רק הרשאה למיקום מדויק. אם המשתמש משתמש במכשיר עם Android בגרסה 12 ואילך, הוא יוכל לספק מיקום משוער (גס) במקום מיקום מדויק (דק), כמו שמוצג באיור 4.

בקשת הרשאה מהמשתמשים במכשיר עם Android בגרסה 12 ומעלה

איור 4. כשמבקשים הרשאה מהמשתמשים במכשיר עם Android מגרסה 12 ואילך, מוצגת האפשרות להעניק הרשאה למיקום מדויק או למיקום משוער.

אחרי שהמשתמש מגיב לתיבת הדו-שיח של הרשאות המערכת, המערכת מפעילה את ההטמעה של onRequestPermissionsResult באפליקציה. המערכת מעבירה את תגובת המשתמש לתיבת הדו-שיח של ההרשאה, וגם את קוד הבקשה שהגדרתם. כדי לשנות את ההגדרה onRequestPermissionResult ולטפל בקוד הבקשה של הרשאות המיקום שקשורות לפעילות הזו של המקום הנוכחי, מדביקים את הקוד הבא מתחת ל-checkPermissionThenFindCurrentPlace.

    @SuppressLint("MissingPermission")
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>, grantResults: IntArray
    ) {
        if (requestCode != PERMISSION_REQUEST_CODE) {
            super.onRequestPermissionsResult(
                requestCode,
                permissions,
                grantResults
            )
            return
        } else if (
            permissions.toList().zip(grantResults.toList())
                .firstOrNull { (permission, grantResult) ->
                    grantResult == PackageManager.PERMISSION_GRANTED && (permission == ACCESS_FINE_LOCATION || permission == ACCESS_COARSE_LOCATION)
                } != null
        )
        // At least one location permission has been granted, so proceed with Find Current Place
        findCurrentPlace()
    }
  1. אחרי שמעניקים הרשאה, הפונקציה findCurrentPlace תפעל. מגדירים את הפונקציה באמצעות הקוד הזה אחרי הפונקציה onRequestPermissionsResult.
    /**
     * Fetches a list of [PlaceLikelihood] instances that represent the Places the user is
     * most likely to be at currently.
     */
    @RequiresPermission(anyOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
    private fun findCurrentPlace() {
        // Use fields to define the data types to return.
        val placeFields: List<Place.Field> =
            listOf(Place.Field.NAME, Place.Field.ID, Place.Field.ADDRESS, Place.Field.LAT_LNG)

        // Call findCurrentPlace and handle the response (first check that the user has granted permission).
        if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) ==
            PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, ACCESS_COARSE_LOCATION) ==
            PackageManager.PERMISSION_GRANTED
        ) {
            // Retrieve likely places based on the device's current location
            currentButton.isEnabled = false
            lifecycleScope.launch {
                val response = placesClient.awaitFindCurrentPlace(placeFields)

                responseView.text = response.prettyPrint()

                // Enable scrolling on the long list of likely places
                val movementMethod = ScrollingMovementMethod()
                responseView.movementMethod = movementMethod
            }
        } else {
            Log.d(TAG, "LOCATION permission not granted")
            checkPermissionThenFindCurrentPlace()
        }
    }

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

הוספת הפעילות Current Place למניפסט

מוסיפים אלמנט <activity> בשביל CurrentPlaceActivity כצאצא של האלמנט <application> בקובץ AndroidManifest.xml שנמצא במיקום app/src/main:

        <activity android:name=".CurrentPlaceActivity" android:label="@string/current_demo_title" />

הוספת הפעילות 'המקום הנוכחי' לתפריט ההדגמה

כמו קודם, מוסיפים את ההדגמה של המקום הנוכחי לרשימה במודול Demo כדי להוסיף אותה למסך הבית. אחרי שיצרתם פעילות של המקום הנוכחי, מוסיפים אותה לקובץ Demo.kt בתיקייה src/main/java/com/google/codelabs/maps/placesdemo/. מדביקים את הקוד הזה מיד אחרי הפריט AUTOCOMPLETE_FRAGMENT_DEMO:

    CURRENT_FRAGMENT_DEMO(
        R.string.current_demo_title,
        R.string.current_demo_description,
        CurrentPlaceActivity::class.java
    ),

המחרוזות המשויכות מוגדרות בקובץ src/main/res/values/strings.xml.

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

  1. מריצים את האפליקציה. הפעם אמורים להופיע שלושה פריטים ברשימה במסך הבית.
  2. מקישים על השורה 'המקום הנוכחי'. אמור להופיע לחצן במסך.
  3. מקישים על הלחצן. אם לא הענקתם לאפליקציה הזו הרשאת גישה למיקום בעבר, אמורה להופיע בקשת הרשאה.
  4. נותנים לאפליקציה הרשאה לגשת למיקום המכשיר.
  5. מקישים שוב על הלחצן. בפעם הזו, אמורה להופיע רשימה של עד 20 מקומות בסביבה וההסתברות שהם יהיו פתוחים, כמו שמוצג באיור 5.

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

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

11. הצגת המקום הנוכחי במפה

הוספת יחסי התלות של המפה

בקובץ build.gradle ברמת המודול, מוסיפים את התלות ב-Google Play Services עבור Maps SDK ל-Android.

app/build.gradle

dependencies {
    // ...
    implementation 'com.google.android.gms:play-services-maps:18.2.0'
}

עדכון של קובץ ה-manifest של Android כדי להוסיף תמיכה במפות

מוסיפים את רכיבי meta-data הבאים בתוך רכיב application.

הם מטמיעים את הגרסה של Google Play Services שהאפליקציה קומפלה איתה ומציינים את מפתח ה-API שלכם.

AndroidManifest.xml

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="${MAPS_API_KEY}" />

הוספת מפתח ה-API אל secrets.properties

פותחים את הקובץ secrets.properties בספרייה ברמה העליונה ומוסיפים את הקוד הבא. מחליפים את הערך YOUR_API_KEY במפתח ה-API שלכם.

MAPS_API_KEY=YOUR_API_KEY

פותחים את הקובץ local.defaults.properties בספרייה ברמה העליונה, באותה תיקייה שבה נמצא הקובץ secrets.properties, ואז מוסיפים את הקוד הבא.

MAPS_API_KEY=DEFAULT_API_KEY

בדיקה אם יש מפתח API

ב-onCreate(), האפליקציה תבדוק אם יש מפתח API של מפות ותפעיל את קטע התמיכה במפות. ‫getMapAsync() משמש לרישום לקריאה חוזרת של המפה.

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

CurrentPlaceActivity.kt

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_current)

        val apiKey = BuildConfig.MAPS_API_KEY

        // Log an error if apiKey is not set.
        if (apiKey.isEmpty() || apiKey == "DEFAULT_API_KEY") {
            Log.e("Places test", "No api key")
            finish()
            return
        }

        // Retrieve a PlacesClient (previously initialized - see DemoApplication)
        placesClient = Places.createClient(this)
        (supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment?)?.getMapAsync(this)

        // ...
    }

יצירת פריסת המפה

  1. בתיקייה app/src/main/res/layout/, יוצרים את קובץ הפריסה fragment_map.xml ומאכלסים את הפריסה בקוד הבא.

res/layout/fragment_map.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="com.google.codelabs.maps.placesdemo.CurrentPlaceActivity" />

המאפיין הזה מגדיר SupportMapFragment שישמש כמאגר למפה ויספק גישה לאובייקט GoogleMap.

  1. בפריסה activity_current.xml שזמינה בתיקייה app/src/main/res/layout/, מוסיפים את הקוד הבא לתחתית הפריסה הלינארית.

res/layout/activity_current.xml

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        android:paddingBottom="16dp"
        android:text="@string/showing_most_likely_place"
        style="@style/TextAppearance.AppCompat.Title"/>

    <include layout="@layout/fragment_map"/>

ההפניה TextView שנוספה מתייחסת למשאב מחרוזת חדש שצריך ליצור.

  1. ב-app/src/main/res/values/strings.xml, מוסיפים את משאב המחרוזת הבא.

res/values/strings.xml

<string name="showing_most_likely_place">Showing most likely place</string>

נוספו תצוגות נוספות למפה, ולכן צריך להגדיר את הגובה של TextView שמציג את רשימת המקומות כדי שהתצוגות האלה יישארו גלויות.

  1. מוסיפים את מאפיין maxHeight לרכיב TextView עם המזהה current_response_content

res/layout/activity_current.xml

android:maxHeight="200dp"

הטמעה של OnMapReadyCallback

מטמיעים את הממשק OnMapReadyCallback על ידי הוספתו להצהרת המחלקה, ומבטלים את השיטה onMapReady() כדי להגדיר את המפה כשהאובייקט GoogleMap זמין:

CurrentPlaceActivity.kt

class CurrentPlaceActivity : AppCompatActivity(), OnMapReadyCallback {

בסוף הכיתה, מוסיפים את הקוד הבא:

CurrentPlaceActivity.kt

    override fun onMapReady(map: GoogleMap) {
        this.map = map
        lastKnownLocation?.let { location ->
            map.moveCamera(
                CameraUpdateFactory.newLatLngZoom(
                    location,
                    DEFAULT_ZOOM
                )
            )
        }
    }

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

CurrentPlaceActivity.kt

private var map: GoogleMap? = null
private var lastKnownLocation: LatLng? = null

מוסיפים את הקוד הבא לאובייקט הנלווה של הכיתה:

CurrentPlaceActivity.kt

private const val DEFAULT_ZOOM = 15f

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

  1. מפעילים את האפליקציה.
  2. מקישים על השורה 'המקום הנוכחי'. אמור להופיע לחצן במסך.
  3. מקישים על הלחצן. אם לא הענקתם לאפליקציה הזו הרשאת גישה למיקום בעבר, אמורה להופיע בקשת הרשאה.
  4. נותנים לאפליקציה הרשאה לגשת למיקום המכשיר.
  5. מקישים שוב על הלחצן. המפה תוצג.

פעילות במקום הנוכחי עם מפה

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

עדכון המפה עם מקום

בסוף הכיתה, מוסיפים את הקוד הבא:

CurrentPlaceActivity.kt

private data class LikelyPlace(
    val name: String,
    val address: String,
    val attribution: List<String>,
    val latLng: LatLng
)

private fun PlaceLikelihood.toLikelyPlace(): LikelyPlace? {
    val name = this.place.name
    val address = this.place.address
    val latLng = this.place.latLng
    val attributions = this.place.attributions ?: emptyList()

    return if (name != null && address != null && latLng != null) {
        LikelyPlace(name, address, attributions, latLng)
    } else {
        null
    }
}

הם משמשים לאחסון הנתונים של המקום ולעיצוב שלהם.

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

CurrentPlaceActivity.kt

private val likelyPlaces = mutableListOf<LikelyPlace>()

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

בפונקציה findCurrentPlace, בבלוק lifecycleScope.launch לפני שורת הקוד הזו

CurrentPlaceActivity.kt

responseView.text = response.prettyPrint()

מוסיפים את הקוד הבא:

CurrentPlaceActivity.kt

                likelyPlaces.clear()

                likelyPlaces.addAll(
                    response.placeLikelihoods.take(M_MAX_ENTRIES).mapNotNull { placeLikelihood ->
                        placeLikelihood.toLikelyPlace()
                    }
                )

                openPlacesDialog()

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

באובייקט הנלווה, מוסיפים את הקוד של הקבוע.

CurrentPlaceActivity.kt

private const val M_MAX_ENTRIES = 5

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

CurrentPlaceActivity.kt

    /**
     * Displays a form allowing the user to select a place from a list of likely places.
     */
    private fun openPlacesDialog() {
        // Ask the user to choose the place where they are now.
        val listener =
            DialogInterface.OnClickListener { _, which -> // The "which" argument contains the position of the selected item.
                val likelyPlace = likelyPlaces[which]
                lastKnownLocation = likelyPlace.latLng

                val snippet = buildString {
                    append(likelyPlace.address)
                    if (likelyPlace.attribution.isNotEmpty()) {
                        append("\n")
                        append(likelyPlace.attribution.joinToString(", "))
                    }
                }

                val place = Place.builder().apply {
                    name = likelyPlace.name
                    latLng = likelyPlace.latLng
                }.build()

                map?.clear()

                setPlaceOnMap(place, snippet)
            }

        // Display the dialog.
        AlertDialog.Builder(this)
            .setTitle(R.string.pick_place)
            .setItems(likelyPlaces.map { it.name }.toTypedArray(), listener)
            .setOnDismissListener {
                currentButton.isEnabled = true
            }
            .show()
    }

בהתאם לשיטות המומלצות של Android, התיבת הדו-שיח מפנה למשאב מחרוזת שצריך להוסיף לקובץ המשאבים strings.xml שנמצא בתיקייה app/src/main/res/values/.

מי יצורף למרחב strings.xml:

res/values/strings.xml

    <string name="pick_place">Choose a place</string>

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

מוסיפים את הקוד הבא:

CurrentPlaceActivity.kt

    private fun setPlaceOnMap(place: Place?, markerSnippet: String?) {
        val latLng = place?.latLng ?: defaultLocation
        map?.moveCamera(
            CameraUpdateFactory.newLatLngZoom(
                latLng,
                DEFAULT_ZOOM
            )
        )
        map?.addMarker(
            MarkerOptions()
                .position(latLng)
                .title(place?.name)
                .snippet(markerSnippet)
        )
    }

מומלץ גם לשמור ולשחזר את מצב המפות.

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

CurrentPlaceActivity.kt

    /**
     * Saves the state of the map when the activity is paused.
     */
    override fun onSaveInstanceState(outState: Bundle) {
        outState.putParcelable(KEY_LOCATION, lastKnownLocation)
        super.onSaveInstanceState(outState)
    }

כדי לשחזר את המצב שלו, מוסיפים את הקוד הבא אחרי הקריאה ל-setContentView ב-onCreate:

CurrentPlaceActivity.kt

        if (savedInstanceState != null) {
            lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION)
        }

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

בבלוק של אובייקט הליווי, מוסיפים את הפרטים הבאים:

CurrentPlaceActivity.kt

        // Key for storing activity state.
        private const val KEY_LOCATION = "location"

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

  1. מפעילים את האפליקציה.
  2. מקישים על השורה 'המקום הנוכחי'. אמור להופיע לחצן במסך.
  3. מקישים על הלחצן. אם לא הענקתם לאפליקציה הזו הרשאת גישה למיקום בעבר, אמורה להופיע בקשת הרשאה.
  4. נותנים לאפליקציה הרשאה לגשת למיקום המכשיר.
  5. מקישים שוב על הלחצן.
  6. מקישים על מקום כדי לבחור אותו. התצוגה של המפה תגדל והיא תתמרכז עם סמן שיוצב במיקום שנבחר.

מפה עם סמן במיקום שנבחר

איור 7. מפה עם סמן במיקום שנבחר.

12. מזל טוב

יצרתם בהצלחה אפליקציית Android באמצעות Places SDK ל-Android.

מה למדתם

מה השלב הבא?

  • כדי לקבל עוד השראה, אפשר לעיין במאגר הדוגמאות וההדגמות ב-GitHub android-places-demos או ליצור ממנו עותק (fork).
  • בסדנאות התכנות הנוספות של Kotlin אפשר ללמוד איך ליצור אפליקציות ל-Android באמצעות הפלטפורמה של מפות Google.
  • כדי לעזור לנו ליצור תוכן שיהיה לך הכי שימושי, נשמח לקבל ממך תשובה לשאלה הבאה:

אילו codelabs נוספים היית רוצה לראות?

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

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