1. قبل البدء
يعلّمك هذا الدرس العملي كيفية دمج حزمة تطوير البرامج Places SDK for Android مع تطبيقك واستخدام كل ميزات هذه الحزمة.
المتطلبات الأساسية
- معرفة أساسية بلغة Kotlin وتطوير تطبيقات Android
أهداف الدورة التعليمية
- كيفية تثبيت Places SDK for Android باستخدام إضافات Kotlin
- كيفية تحميل تفاصيل المكان لمكان محدّد
- كيفية إضافة أداة Place Autocomplete إلى تطبيقك
- كيفية تحميل "المكان الحالي" استنادًا إلى الموقع الجغرافي الذي يتم الإبلاغ عنه حاليًا للجهاز
المتطلبات
لإكمال هذا الدرس العملي، ستحتاج إلى الحسابات والخدمات والأدوات التالية:
- حساب Google تم تفعيل الفوترة فيه
- Android Studio Bumblebee أو إصدار أحدث
- تثبيت خدمات Google Play في "استوديو Android"
- جهاز Android أو محاكي Android يعمل بمنصة Google APIs استنادًا إلى الإصدار 8 من نظام التشغيل Android أو الإصدارات الأحدث (راجِع تشغيل التطبيقات على "محاكي Android" لمعرفة خطوات التثبيت).
2. طريقة الإعداد
في خطوة التفعيل أدناه، فعِّل Places API وحزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات Android.
إعداد Google Maps Platform
إذا لم يكن لديك حساب على Google Cloud Platform ومشروع مفعَّل فيه نظام الفوترة، يُرجى الاطّلاع على دليل البدء باستخدام Google Maps Platform لإنشاء حساب فوترة ومشروع.
- في Cloud Console، انقر على القائمة المنسدلة الخاصة بالمشروع واختَر المشروع الذي تريد استخدامه في هذا الدرس العملي.
- فعِّل واجهات برمجة التطبيقات وحِزم تطوير البرامج (SDK) في Google Maps Platform المطلوبة لهذا الدرس العملي في Google Cloud Marketplace. لإجراء ذلك، اتّبِع الخطوات الواردة في هذا الفيديو أو هذه المستندات.
- أنشئ مفتاح واجهة برمجة التطبيقات في صفحة بيانات الاعتماد في Cloud Console. يمكنك اتّباع الخطوات الواردة في هذا الفيديو أو هذه المستندات. تتطلّب جميع الطلبات إلى "منصة خرائط Google" مفتاح واجهة برمجة تطبيقات.
3- البدء بسرعة
للبدء بأسرع ما يمكن، نزِّل رمز البداية لمساعدتك في متابعة هذا الدرس التطبيقي حول الترميز. يمكنك الانتقال إلى الحلّ مباشرةً، ولكن إذا أردت اتّباع جميع الخطوات لإنشائه بنفسك، يمكنك مواصلة القراءة.
- استنسِخ المستودع إذا كان لديك
git
مثبَّتًا.
git clone https://github.com/googlemaps/codelab-places-101-android-kotlin.git
بدلاً من ذلك، انقر على هذا الزر لتنزيل الرمز المصدر.
- بعد تنزيل الرمز، افتح المشروع الموجود داخل الدليل
/starter
في "استوديو Android". يتضمّن هذا المشروع بنية الملف الأساسية التي ستحتاج إليها لإكمال الدرس العملي. يمكنك العثور على كل ما تحتاج إليه للعمل في الدليل/starter
.
إذا أردت الاطّلاع على رمز الحل الكامل قيد التشغيل، يمكنك عرض الرمز المكتمل في الدليل /solution
.
4. إضافة مفتاح واجهة برمجة التطبيقات إلى المشروع
يوضّح هذا القسم كيفية تخزين مفتاح واجهة برمجة التطبيقات حتى يتمكّن تطبيقك من الرجوع إليه بأمان. يجب عدم إدخال مفتاح واجهة برمجة التطبيقات في نظام التحكّم في الإصدارات، لذا ننصح بتخزينه في الملف 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
بمفتاح واجهة برمجة التطبيقات. خزِّن مفتاحك في هذا الملف لأنّsecrets.properties
مستبعد من إمكانية التحقّق من نظام التحكّم بالإصدارات.
PLACES_API_KEY=YOUR_API_KEY
- احفظ الملف.
- أنشئ ملف
local.defaults.properties
في الدليل على المستوى الأعلى، أي المجلد نفسه الذي يحتوي على ملفsecrets.properties
، ثم أضِف الرمز التالي.
PLACES_API_KEY=DEFAULT_API_KEY
الغرض من هذا الملف هو توفير موقع احتياطي لمفتاح واجهة برمجة التطبيقات في حال تعذّر العثور على الملف secrets.properties
، وذلك لضمان عدم تعذّر إنشاء الإصدارات. يمكن أن يحدث ذلك إذا استنسخت التطبيق من نظام التحكّم في الإصدارات الذي يحذف secrets.properties
ولم تنشئ بعد ملف secrets.properties
محليًا لتوفير مفتاح واجهة برمجة التطبيقات.
- احفظ الملف.
- في 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 for Android
في هذا القسم، يمكنك إضافة حزمة تطوير البرامج Places SDK for Android إلى التبعيات في تطبيقك.
- بعد أن أصبح من الممكن الوصول إلى مفتاح واجهة برمجة التطبيقات داخل التطبيق، أضِف مصدر الاعتمادية الخاص بحزمة Places SDK لنظام التشغيل Android إلى ملف
build.gradle
في تطبيقك.
عدِّل ملف build.gradle
على مستوى التطبيق لإضافة مصدر الاعتمادية الخاص بحزمة تطوير برامج الأماكن لأجهزة Android:
ملف 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 التي تستخدم حزمة تطوير برامج (SDK) واحدة أو أكثر من حِزم تطوير البرامج لنظام التشغيل Android في "منصة خرائط Google"، تتيح لك مكتبات Kotlin الإضافية (KTX) الاستفادة من ميزات لغة Kotlin، مثل الروتينات الفرعية وخصائص/دوال الإضافة وغير ذلك. تحتوي كل حزمة تطوير برامج (SDK) من "خرائط Google" على مكتبة 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 for Android. الصِق الأسطر أدناه في نهاية الدالة onCreate
:
// Initialize the SDK with the Google Maps Platform API key
Places.initialize(this, BuildConfig.PLACES_API_KEY)
عند إنشاء تطبيقك، يتيح المكوّن الإضافي Secrets Gradle لأجهزة Android مفتاح واجهة برمجة التطبيقات في ملف 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 لاستخدامه مع هذا النشاط. ألصِق هذا الرمز بعد الرمز اللازم للتحقّق من مفتاح واجهة برمجة التطبيقات في الدالة
onCreate
.
// Retrieve a PlacesClient (previously initialized - see DemoApplication)
placesClient = Places.createClient(this)
- بعد إعداد Places Client، أرفِق أداة معالجة نقرات بالزر. الصِق هذا الرمز بعد إنشاء 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. نشاط الإكمال التلقائي الذي يعرض "تفاصيل المكان" بعد أن يكتب المستخدم "شلالات نياغرا" ويختارها
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، يمكنه منح إذن الوصول إلى الموقع الجغرافي الدقيق فقط. إذا كان المستخدم يستخدم جهازًا يعمل بالإصدار 12 من نظام التشغيل Android أو إصدار أحدث، سيُتاح له خيار تقديم الموقع الجغرافي التقريبي بدلاً من الموقع الجغرافي الدقيق، كما هو موضّح في الشكل 4.
الشكل 4 عند طلب إذن المستخدم على جهاز يعمل بالإصدار 12 من نظام التشغيل Android أو إصدار أحدث، سيظهر خيار منح إذن الوصول إلى الموقع الجغرافي الدقيق أو التقريبي.
بعد أن يردّ المستخدم على مربّع حوار أذونات النظام، يستدعي النظام بعد ذلك تنفيذ تطبيقك للدالة 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 بالتفاصيل المطلوبة.
إضافة نشاط "المكان الحالي" إلى ملف البيان
أضِف عنصر <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" إلى حزمة تطوير البرامج (SDK) لتطبيق "خرائط Google" لنظام التشغيل Android.
app/build.gradle
dependencies {
// ...
implementation 'com.google.android.gms:play-services-maps:18.2.0'
}
تعديل ملف البيان Android لتضمين الخرائط
أضِف عناصر meta-data
التالية ضمن العنصر application
.
تتضمّن هذه الملفات إصدار "خدمات Google Play" الذي تم تجميع التطبيق به، كما تحدّد مفتاح واجهة برمجة التطبيقات.
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}" />
إضافة مفتاح واجهة برمجة التطبيقات إلى secrets.properties
افتح ملف secrets.properties
في الدليل ذي المستوى الأعلى، ثم أضِف الرمز التالي. استبدِل YOUR_API_KEY
بمفتاح واجهة برمجة التطبيقات.
MAPS_API_KEY=YOUR_API_KEY
افتح ملف local.defaults.properties
في مجلد المستوى الأعلى، أي المجلد نفسه الذي يحتوي على ملف secrets.properties
، ثم أضِف الرمز التالي.
MAPS_API_KEY=DEFAULT_API_KEY
التحقّق من مفتاح واجهة برمجة التطبيقات
في onCreate()
، سيتحقّق التطبيق من مفتاح واجهة برمجة التطبيقات للخرائط وسيهيئ جزء دعم الخرائط. يُستخدَم 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)
}
لاستعادة حالته، أضِف الرمز التالي في onCreate
بعد طلب setContentView
:
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 for Android.
ما تعلّمته
- تثبيت حزمة تطوير البرامج (SDK) الخاصة بخدمة "أماكن Google" لنظام التشغيل Android وضبطها
- تثبيت إضافات Kotlin لحزمة تطوير البرامج Places SDK for Android
- جارٍ تحميل تفاصيل المكان.
- إضافة خدمة الإكمال التلقائي للأماكن
- الحصول على المكان الحالي
ما هي الخطوات التالية؟
- يمكنك استكشاف مستودع
android-places-demos
GitHub الذي يتضمّن عيّنات وعروض توضيحية أو إنشاء نسخة منه للحصول على المزيد من الأفكار. - يمكنك الاستفادة من المزيد من دروس الترميز التطبيقية حول Kotlin لإنشاء تطبيقات Android باستخدام "منصة خرائط Google".
- يُرجى مساعدتنا في إنشاء المحتوى الذي تراه الأكثر فائدة من خلال الإجابة عن السؤال التالي:
ما هي جلسات الترميز الأخرى التي تريد المشاركة فيها؟
هل لم يتم إدراج الدرس العملي الذي تريده؟ يمكنك طلب ذلك من خلال تقديم مشكلة جديدة هنا.