1. לפני שתתחיל
ב-codelab הזה נסביר איך לשלב את Places SDK ל-Android באפליקציה ואיך להשתמש בכל אחת מהתכונות של Places SDK.
דרישות מוקדמות
- ידע בסיסי ב-Kotlin ובפיתוח ל-Android
מה תלמדו
- איך מתקינים את Places SDK ל-Android עם Kotlin Extensions.
- איך לטעון את פרטי המקום של מקום ספציפי.
- איך מוסיפים לאפליקציה ווידג'ט של השלמה אוטומטית של מקומות.
- איך לטעון את המקום הנוכחי על סמך המיקום הנוכחי שמדווח על ידי המכשיר.
מה נדרש
כדי להשלים את ה-codelab הזה, תצטרכו את החשבונות, השירותים והכלים הבאים:
- חשבון Google שמופעל בו חיוב.
- Android Studio Bumblebee או גרסה מתקדמת יותר.
- Google Play Services מותקן ב-Android Studio.
- מכשיר Android או אמולטור Android שמריץ את פלטפורמת Google APIs על בסיס Android מגרסה 8 ומעלה (הוראות ההתקנה מפורטות במאמר הרצת אפליקציות באמולטור Android).
2. להגדרה
בשלב ההפעלה שבהמשך, מפעילים את Places API ואת Maps SDK ל-Android.
הגדרת הפלטפורמה של מפות Google
אם עדיין אין לכם חשבון ב-Google Cloud Platform ופרויקט עם חיוב מופעל, תוכלו לעיין במדריך תחילת העבודה עם הפלטפורמה של מפות Google כדי ליצור חשבון לחיוב ופרויקט.
- בCloud Console, לוחצים על התפריט הנפתח של הפרויקט ובוחרים את הפרויקט שבו רוצים להשתמש ב-codelab הזה.
- מפעילים ב-Google Cloud Marketplace את ממשקי ה-API וערכות ה-SDK של הפלטפורמה של מפות Google שנדרשים ל-codelab הזה. כדי לעשות זאת, פועלים לפי השלבים בסרטון הזה או בתיעוד הזה.
- יוצרים מפתח API בדף Credentials במסוף Cloud. אפשר לפעול לפי השלבים שמפורטים בסרטון הזה או בתיעוד הזה. כל הבקשות אל הפלטפורמה של מפות Google מחייבות מפתח API.
3. התחלה מהירה
כדי שתוכלו להתחיל כמה שיותר מהר, אתם יכולים להוריד את קוד ההתחלה שיעזור לכם לעקוב אחרי ההוראות ב-Codelab הזה. אתם יכולים לדלג לפתרון, אבל אם אתם רוצים לבצע את כל השלבים בעצמכם, המשיכו לקרוא.
- משכפלים את המאגר אם
git
מותקן.
git clone https://github.com/googlemaps/codelab-places-101-android-kotlin.git
אפשר גם ללחוץ על הכפתור הזה כדי להוריד את קוד המקור.
- אחרי שמורידים את הקוד, פותחים את הפרויקט שנמצא בספרייה
/starter
ב-Android Studio. הפרויקט הזה כולל את מבנה הקבצים הבסיסי שדרוש לכם כדי להשלים את ה-codelab. כל מה שצריך לעבודה נמצא בספרייה/starter
.
כדי לראות את קוד הפתרון המלא בפעולה, אפשר לעיין בקוד המלא בספרייה /solution
.
4. הוספת מפתח ה-API לפרויקט
בקטע הזה מתואר איך לאחסן את מפתח ה-API כך שהאפליקציה תוכל להפנות אליו בצורה מאובטחת. לא מומלץ להוסיף את מפתח ה-API למערכת בקרת הגרסאות, ולכן אנחנו ממליצים לאחסן אותו בקובץ secrets.properties
, שיוצב בעותק המקומי של ספריית הבסיס של הפרויקט. מידע נוסף על הקובץ secrets.properties
זמין במאמר קובצי מאפיינים של Gradle.
כדי לייעל את המשימה הזו, מומלץ להשתמש בפלאגין של Secrets Gradle ל-Android.
כדי להתקין את הפלאגין Secrets Gradle ל-Android בפרויקט של מפות Google:
- ב-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"
}
}
- פותחים את הקובץ
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'
}
- בקובץ
build.gradle.kts
אוbuild.gradle
ברמת המודול, מוודאים שהערכים שלtargetSdk
ו-compileSdk
מוגדרים ל-34. - שומרים את הקובץ ומסנכרנים את הפרויקט עם Gradle.
- פותחים את הקובץ
secrets.properties
בספרייה ברמה העליונה ומוסיפים את הקוד הבא. מחליפים את הערךYOUR_API_KEY
במפתח ה-API שלכם. כדאי לאחסן את המפתח בקובץ הזה כיsecrets.properties
לא נכלל בבדיקה במערכת בקרת גרסאות.
PLACES_API_KEY=YOUR_API_KEY
- שומרים את הקובץ.
- יוצרים את הקובץ
local.defaults.properties
בתיקייה ברמה העליונה, באותה תיקייה שבה נמצא הקובץsecrets.properties
, ואז מוסיפים את הקוד הבא.
PLACES_API_KEY=DEFAULT_API_KEY
מטרת הקובץ הזה היא לספק מיקום גיבוי למפתח ה-API אם הקובץ secrets.properties
לא נמצא, כדי שהגרסאות לא ייכשלו. מצב כזה יכול לקרות אם משכפלים את האפליקציה ממערכת בקרת גרסאות שבה לא מופיע secrets.properties
, ועדיין לא יצרתם קובץ secrets.properties
באופן מקומי כדי לספק את מפתח ה-API.
- שומרים את הקובץ.
- ב-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 לתלות של האפליקציה.
- עכשיו, כשמפתח ה-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'
}
- מפעילים את האפליקציה.
עכשיו אמורה להופיע אפליקציה עם מסך ריק. ממשיכים לאכלס את המסך הזה בשלוש הדגמות.
6. התקנה של Places Android KTX
באפליקציות Kotlin שמשתמשות באחד או יותר מ-Google Maps Platform Android SDKs, ספריות Kotlin extension (KTX) מאפשרות לכם ליהנות מתכונות של שפת Kotlin, כמו קורוטינות, מאפייני הרחבה/פונקציות הרחבה ועוד. לכל Google Maps SDK יש ספריית 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
.
יצירת פעילות מסוג 'פרטים'
- יוצרים קובץ
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
}
}
}
- יוצרים לקוח Places לשימוש בפעילות הזו. מדביקים את הקוד הזה אחרי הקוד לבדיקת מפתח ה-API בפונקציה
onCreate
.
// Retrieve a PlacesClient (previously initialized - see DemoApplication)
placesClient = Places.createClient(this)
- אחרי שמגדירים את 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.
איור 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
.
יצירת פעילות של השלמה אוטומטית
- יוצרים קובץ
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
שהגדרתם בקובץ הפריסה.
- לאחר מכן, מגדירים מה קורה כשהמשתמש בוחר אחת מהתחזיות שמוצגות על ידי השלמה אוטומטית של מקומות. מוסיפים את הקוד הזה לסוף הפונקציה
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
.
הפעלת האפליקציה
- מריצים את האפליקציה. הפעם אמורים להופיע שני פריטים ברשימה במסך הבית.
- מקישים על השורה 'השלמה אוטומטית של מקומות'. יופיע חלון קופץ עם קלט של השלמה אוטומטית של מקום, כמו שמוצג באיור 2.
- מתחילים להקליד את שם המקום. זה יכול להיות שם של עסק, כתובת או אזור גיאוגרפי. התחזיות צריכות להופיע תוך כדי הקלדה.
- בוחרים אחת מהתחזיות. התחזיות אמורות להיעלם, ועכשיו ב-TextView יוצגו הפרטים על המקום שנבחר, כמו שמוצג באיור 3.
איור 2. השלמה אוטומטית של פעילות אחרי שהמשתמש מקיש על שדה הקלט.
איור 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" />
יצירת פעילות של המקום הנוכחי
- יוצרים קובץ
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
כשלוחצים על הלחצן.
- מגדירים את
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
}
- כשענף
else
של הפונקציהcheckPermissionThenFindCurrentPlace
קורא לפונקציהrequestPermissions
, האפליקציה תציג למשתמש תיבת דו-שיח של בקשת הרשאה. אם המשתמש מפעיל מכשיר עם מערכת הפעלה בגרסה נמוכה מ-Android 12, הוא יכול להעניק רק הרשאה למיקום מדויק. אם המשתמש משתמש במכשיר עם Android בגרסה 12 ואילך, הוא יוכל לספק מיקום משוער (גס) במקום מיקום מדויק (דק), כמו שמוצג באיור 4.
איור 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()
}
- אחרי שמעניקים הרשאה, הפונקציה
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
.
הפעלת האפליקציה
- מריצים את האפליקציה. הפעם אמורים להופיע שלושה פריטים ברשימה במסך הבית.
- מקישים על השורה 'המקום הנוכחי'. אמור להופיע לחצן במסך.
- מקישים על הלחצן. אם לא הענקתם לאפליקציה הזו הרשאת גישה למיקום בעבר, אמורה להופיע בקשת הרשאה.
- נותנים לאפליקציה הרשאה לגשת למיקום המכשיר.
- מקישים שוב על הלחצן. בפעם הזו, אמורה להופיע רשימה של עד 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)
// ...
}
יצירת פריסת המפה
- בתיקייה
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
.
- בפריסה
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
שנוספה מתייחסת למשאב מחרוזת חדש שצריך ליצור.
- ב-
app/src/main/res/values/strings.xml
, מוסיפים את משאב המחרוזת הבא.
res/values/strings.xml
<string name="showing_most_likely_place">Showing most likely place</string>
נוספו תצוגות נוספות למפה, ולכן צריך להגדיר את הגובה של TextView
שמציג את רשימת המקומות כדי שהתצוגות האלה יישארו גלויות.
- מוסיפים את מאפיין
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
הפעלת האפליקציה
- מפעילים את האפליקציה.
- מקישים על השורה 'המקום הנוכחי'. אמור להופיע לחצן במסך.
- מקישים על הלחצן. אם לא הענקתם לאפליקציה הזו הרשאת גישה למיקום בעבר, אמורה להופיע בקשת הרשאה.
- נותנים לאפליקציה הרשאה לגשת למיקום המכשיר.
- מקישים שוב על הלחצן. המפה תוצג.
איור 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"
הפעלת האפליקציה
- מפעילים את האפליקציה.
- מקישים על השורה 'המקום הנוכחי'. אמור להופיע לחצן במסך.
- מקישים על הלחצן. אם לא הענקתם לאפליקציה הזו הרשאת גישה למיקום בעבר, אמורה להופיע בקשת הרשאה.
- נותנים לאפליקציה הרשאה לגשת למיקום המכשיר.
- מקישים שוב על הלחצן.
- מקישים על מקום כדי לבחור אותו. התצוגה של המפה תגדל והיא תתמרכז עם סמן שיוצב במיקום שנבחר.
איור 7. מפה עם סמן במיקום שנבחר.
12. מזל טוב
יצרתם בהצלחה אפליקציית Android באמצעות Places SDK ל-Android.
מה למדתם
- התקנה והגדרה של Places SDK ל-Android.
- התקנה של Kotlin Extensions ל-Places SDK ל-Android.
- הפרטים של המקום בטעינה.
- הוספת השלמה אוטומטית למקומות.
- קבלת המיקום הנוכחי.
מה השלב הבא?
- כדי לקבל עוד השראה, אפשר לעיין במאגר הדוגמאות וההדגמות ב-GitHub
android-places-demos
או ליצור ממנו עותק (fork). - בסדנאות התכנות הנוספות של Kotlin אפשר ללמוד איך ליצור אפליקציות ל-Android באמצעות הפלטפורמה של מפות Google.
- כדי לעזור לנו ליצור תוכן שיהיה לך הכי שימושי, נשמח לקבל ממך תשובה לשאלה הבאה:
אילו codelabs נוספים היית רוצה לראות?
האם ה-codelab שרציתם לא מופיע ברשימה? כאן אפשר לשלוח בקשה בנושא בעיה חדשה.