بدء استخدام Places SDK for Android (Kotlin)

1. قبل البدء

يعلّمك هذا الدرس العملي كيفية دمج حزمة تطوير البرامج Places SDK for Android مع تطبيقك واستخدام كل ميزات هذه الحزمة.

تطبيق Places التجريبي

المتطلبات الأساسية

  • معرفة أساسية بلغة Kotlin وتطوير تطبيقات Android

أهداف الدورة التعليمية

  • كيفية تثبيت Places SDK for Android باستخدام إضافات Kotlin
  • كيفية تحميل تفاصيل المكان لمكان محدّد
  • كيفية إضافة أداة Place Autocomplete إلى تطبيقك
  • كيفية تحميل "المكان الحالي" استنادًا إلى الموقع الجغرافي الذي يتم الإبلاغ عنه حاليًا للجهاز

المتطلبات

لإكمال هذا الدرس العملي، ستحتاج إلى الحسابات والخدمات والأدوات التالية:

2. طريقة الإعداد

في خطوة التفعيل أدناه، فعِّل Places API وحزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات Android.

إعداد Google Maps Platform

إذا لم يكن لديك حساب على Google Cloud Platform ومشروع مفعَّل فيه نظام الفوترة، يُرجى الاطّلاع على دليل البدء باستخدام Google Maps Platform لإنشاء حساب فوترة ومشروع.

  1. في Cloud Console، انقر على القائمة المنسدلة الخاصة بالمشروع واختَر المشروع الذي تريد استخدامه في هذا الدرس العملي.

  1. فعِّل واجهات برمجة التطبيقات وحِزم تطوير البرامج (SDK) في Google Maps Platform المطلوبة لهذا الدرس العملي في Google Cloud Marketplace. لإجراء ذلك، اتّبِع الخطوات الواردة في هذا الفيديو أو هذه المستندات.
  2. أنشئ مفتاح واجهة برمجة التطبيقات في صفحة بيانات الاعتماد في Cloud Console. يمكنك اتّباع الخطوات الواردة في هذا الفيديو أو هذه المستندات. تتطلّب جميع الطلبات إلى "منصة خرائط Google" مفتاح واجهة برمجة تطبيقات.

3- البدء بسرعة

للبدء بأسرع ما يمكن، نزِّل رمز البداية لمساعدتك في متابعة هذا الدرس التطبيقي حول الترميز. يمكنك الانتقال إلى الحلّ مباشرةً، ولكن إذا أردت اتّباع جميع الخطوات لإنشائه بنفسك، يمكنك مواصلة القراءة.

  1. استنسِخ المستودع إذا كان لديك git مثبَّتًا.
git clone https://github.com/googlemaps/codelab-places-101-android-kotlin.git

بدلاً من ذلك، انقر على هذا الزر لتنزيل الرمز المصدر.

  1. بعد تنزيل الرمز، افتح المشروع الموجود داخل الدليل /starter في "استوديو Android". يتضمّن هذا المشروع بنية الملف الأساسية التي ستحتاج إليها لإكمال الدرس العملي. يمكنك العثور على كل ما تحتاج إليه للعمل في الدليل /starter.

إذا أردت الاطّلاع على رمز الحل الكامل قيد التشغيل، يمكنك عرض الرمز المكتمل في الدليل /solution.

4. إضافة مفتاح واجهة برمجة التطبيقات إلى المشروع

يوضّح هذا القسم كيفية تخزين مفتاح واجهة برمجة التطبيقات حتى يتمكّن تطبيقك من الرجوع إليه بأمان. يجب عدم إدخال مفتاح واجهة برمجة التطبيقات في نظام التحكّم في الإصدارات، لذا ننصح بتخزينه في الملف 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 بمفتاح واجهة برمجة التطبيقات. خزِّن مفتاحك في هذا الملف لأنّ secrets.properties مستبعد من إمكانية التحقّق من نظام التحكّم بالإصدارات.
PLACES_API_KEY=YOUR_API_KEY
  1. احفظ الملف.
  2. أنشئ ملف local.defaults.properties في الدليل على المستوى الأعلى، أي المجلد نفسه الذي يحتوي على ملف secrets.properties، ثم أضِف الرمز التالي.
PLACES_API_KEY=DEFAULT_API_KEY

الغرض من هذا الملف هو توفير موقع احتياطي لمفتاح واجهة برمجة التطبيقات في حال تعذّر العثور على الملف secrets.properties، وذلك لضمان عدم تعذّر إنشاء الإصدارات. يمكن أن يحدث ذلك إذا استنسخت التطبيق من نظام التحكّم في الإصدارات الذي يحذف secrets.properties ولم تنشئ بعد ملف secrets.properties محليًا لتوفير مفتاح واجهة برمجة التطبيقات.

  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 for Android

في هذا القسم، يمكنك إضافة حزمة تطوير البرامج Places SDK for Android إلى التبعيات في تطبيقك.

  1. بعد أن أصبح من الممكن الوصول إلى مفتاح واجهة برمجة التطبيقات داخل التطبيق، أضِف مصدر الاعتمادية الخاص بحزمة 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'
}
  1. شغِّل التطبيق.

من المفترض أن يظهر لك الآن تطبيق بشاشة فارغة. واصِل ملء هذه الشاشة بثلاثة عروض توضيحية.

6. تثبيت Places Android KTX

بالنسبة إلى تطبيقات Kotlin التي تستخدم حزمة تطوير برامج (SDK) واحدة أو أكثر من حِزم تطوير البرامج لنظام التشغيل Android في "منصة خرائط Google"، تتيح لك مكتبات Kotlin الإضافية (KTX) الاستفادة من ميزات لغة Kotlin، مثل الروتينات الفرعية وخصائص/دوال الإضافة وغير ذلك. تحتوي كل حزمة تطوير برامج (SDK) من "خرائط Google" على مكتبة KTX مقابلة كما هو موضّح أدناه:

مخطّط KTX في "منصة خرائط Google"

في هذه المهمة، استخدِم مكتبة 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.

إنشاء نشاط "التفاصيل"

  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 لاستخدامه مع هذا النشاط. ألصِق هذا الرمز بعد الرمز اللازم للتحقّق من مفتاح واجهة برمجة التطبيقات في الدالة onCreate.
        // Retrieve a PlacesClient (previously initialized - see DemoApplication)
        placesClient = Places.createClient(this)
  1. بعد إعداد 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. شغِّل التطبيق. من المفترض أن يظهر عنصر واحد في القائمة يعرض العرض التوضيحي لتفاصيل المكان.
  2. انقر على نص "تفاصيل المكان". من المفترض أن ترى طريقة العرض التي أنشأتها مع حقل إدخال وزر.
  3. انقر على الزر "الحصول على التفاصيل". إذا كنت قد استخدمت معرّف المكان التلقائي، من المفترض أن يظهر اسم المكان وعنوانه وإحداثيات الخريطة، كما هو موضّح في الشكل 1.

نشاط &quot;تفاصيل المكان&quot; مع الردّ

الشكل 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. نشاط الإكمال التلقائي بعد أن ينقر المستخدم على حقل الإدخال

نشاط الإكمال التلقائي بعد أن كتب المستخدم &quot;شلالات نياغرا&quot; واختارها

الشكل 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" />

إنشاء نشاط "المكان الحالي"

  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، يمكنه منح إذن الوصول إلى الموقع الجغرافي الدقيق فقط. إذا كان المستخدم يستخدم جهازًا يعمل بالإصدار 12 من نظام التشغيل Android أو إصدار أحدث، سيُتاح له خيار تقديم الموقع الجغرافي التقريبي بدلاً من الموقع الجغرافي الدقيق، كما هو موضّح في الشكل 4.

طلب إذن المستخدم على جهاز يعمل بنظام التشغيل Android 12 أو إصدار أحدث

الشكل 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()
    }
  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 بالتفاصيل المطلوبة.

إضافة نشاط "المكان الحالي" إلى ملف البيان

أضِف عنصر <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.

عرض النتائج المحتملة لـ &quot;المكان الحالي&quot; استنادًا إلى الموقع الجغرافي الذي أبلغ عنه الجهاز

الشكل 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)

        // ...
    }

إنشاء تخطيط الخريطة

  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. انقر على الزر مرة أخرى. سيتم عرض الخريطة.

نشاط &quot;المكان الحالي&quot; الذي يعرض الخريطة

الشكل 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"

تشغيل التطبيق

  1. شغِّل التطبيق.
  2. انقر على صف "الموقع الجغرافي الحالي". سيظهر لك زر على الشاشة.
  3. انقر على الزر. إذا لم يسبق لك منح إذن الوصول إلى الموقع الجغرافي لهذا التطبيق، من المفترض أن يظهر طلب الحصول على الإذن.
  4. امنح التطبيق الإذن بالوصول إلى الموقع الجغرافي للجهاز.
  5. انقر على الزر مرة أخرى.
  6. اختَر مكانًا من خلال النقر عليه. سيتم تكبير الخريطة وتوسيطها مع وضع علامة في الموقع الجغرافي المحدّد.

خريطة تتضمّن علامة في الموقع الجغرافي المحدّد

الشكل 7. خريطة تتضمّن علامة في الموقع الجغرافي المحدّد

12. تهانينا

لقد أنشأت تطبيق Android بنجاح باستخدام حزمة تطوير البرامج Places SDK for Android.

ما تعلّمته

ما هي الخطوات التالية؟

  • يمكنك استكشاف مستودع android-places-demos GitHub الذي يتضمّن عيّنات وعروض توضيحية أو إنشاء نسخة منه للحصول على المزيد من الأفكار.
  • يمكنك الاستفادة من المزيد من دروس الترميز التطبيقية حول Kotlin لإنشاء تطبيقات Android باستخدام "منصة خرائط Google".
  • يُرجى مساعدتنا في إنشاء المحتوى الذي تراه الأكثر فائدة من خلال الإجابة عن السؤال التالي:

ما هي جلسات الترميز الأخرى التي تريد المشاركة فيها؟

العرض المرئي للبيانات على الخرائط مزيد من المعلومات عن تخصيص نمط خرائطي إنشاء تفاعلات ثلاثية الأبعاد في الخرائط

هل لم يتم إدراج الدرس العملي الذي تريده؟ يمكنك طلب ذلك من خلال تقديم مشكلة جديدة هنا.