1. Прежде чем начать
В этой лабораторной работе вы узнаете, как интегрировать Places SDK для Android с вашим приложением и использовать каждую из функций Places SDK.
Предпосылки
- Базовые знания Kotlin и разработки под Android
Чему вы научитесь
- Как установить Places SDK для Android с расширениями Kotlin.
- Как загрузить информацию о конкретном месте.
- Как добавить виджет автозаполнения мест в ваше приложение.
- Как загрузить текущее место на основе текущего местоположения устройства.
Что вам понадобится
Для выполнения этой лабораторной работы вам понадобятся следующие учетные записи, сервисы и инструменты:
- Учетная запись Google с включенной функцией выставления счетов.
- Android Studio Bumblebee или выше.
- Сервисы Google Play, установленные в Android Studio.
- Устройство Android или эмулятор Android , на котором работает платформа API Google на базе Android 8 или более поздней версии (инструкции по установке см. в разделе Запуск приложений на эмуляторе Android ).
2. Настройте
Для выполнения шага включения ниже включите Places API и Maps SDK для Android .
Настройте платформу Google Карт
Если у вас еще нет учетной записи Google Cloud Platform и проекта с включенным выставлением счетов, ознакомьтесь с руководством « Начало работы с Google Maps Platform», чтобы создать учетную запись для выставления счетов и проект.
- В Cloud Console щелкните раскрывающееся меню проектов и выберите проект, который вы хотите использовать для этой кодовой лаборатории.
- Включите API и SDK платформы Google Карт, необходимые для этой лабораторной работы, в Google Cloud Marketplace . Для этого следуйте инструкциям в этом видео или в этой документации .
- Сгенерируйте ключ API на странице «Учётные данные» в Cloud Console. Вы можете следовать инструкциям в этом видео или в этой документации . Для всех запросов к платформе Google Карт требуется ключ API.
3. Быстрый старт
Чтобы начать работу как можно быстрее, скачайте стартовый код, который поможет вам разобраться с этой практической работой. Вы можете сразу перейти к решению, но если хотите выполнить все шаги по его самостоятельной сборке, продолжайте читать.
- Клонируйте репозиторий, если у вас установлен
git
.
git clone https://github.com/googlemaps/codelab-places-101-android-kotlin.git
Или нажмите эту кнопку, чтобы загрузить исходный код.
- После загрузки кода откройте проект из каталога
/starter
в Android Studio. Этот проект содержит базовую структуру файлов, необходимую для выполнения этой лабораторной работы. Всё необходимое для работы находится в каталоге/starter
.
Если вы хотите увидеть полный работающий код решения, вы можете просмотреть готовый код в каталоге /solution
.
4. Добавьте свой API-ключ в проект.
В этом разделе описывается, как сохранить ключ API, чтобы приложение могло безопасно ссылаться на него. Не следует регистрировать ключ API в системе контроля версий, поэтому мы рекомендуем хранить его в файле secrets.properties
, который будет размещён в локальной копии корневого каталога вашего проекта. Подробнее о файле secrets.properties
см. в разделе Файлы свойств Gradle .
Чтобы упростить эту задачу, мы рекомендуем вам использовать плагин Secrets Gradle для Android .
Чтобы установить плагин Secrets Gradle для Android в вашем проекте Google Maps:
- В 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 для Android в файл
build.gradle
вашего приложения.
Измените файл build.gradle
на уровне приложения, чтобы добавить зависимость для Places SDK для 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 платформы Google Карт для Android, библиотеки расширения 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.
Инициализируйте 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 добавьте прослушиватель кликов к кнопке. Вставьте этот код после создания клиента 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 запрошенной информацией.
Добавьте действие Details в манифест
Добавьте элемент <activity>
для DetailsActivity
как дочерний элемент элемента <application>
в файле AndroidManifest.xml
, расположенном в app/src/main
:
<activity android:name=".DetailsActivity" android:label="@string/details_demo_title" />
Добавить действие «Подробности» в демонстрационное меню
Для отображения доступных демонстраций на главном экране предоставляется пустой модуль Demo
. После создания действия Place Details добавьте его в файл 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
которые вы определили в файле макета.
- Затем определите, что происходит, когда пользователь выбирает одно из подсказок, представленных Place Autocomplete. Добавьте этот код в конец функции
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; при использовании виджета автозаполнения места нет необходимости отправлять отдельный запрос на информацию о месте.
Добавьте действие Autocomplete в манифест
Добавьте элемент <activity>
для AutocompleteActivity
как дочерний элемент элемента <application>
в файле AndroidManifest.xml
, расположенном в app/src/main
:
<activity android:name=".AutocompleteActivity" android:label="@string/autocomplete_fragment_demo_title" />
Добавить действие «Автозаполнение» в демо-меню
Как и прежде, добавьте демо-версию Place Autocomplete на главный экран, добавив её в список модуля Demo
. После создания активности Place Autocomplete добавьте её в файл 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, он может предоставить только разрешение на определение точного местоположения. Если пользователь использует устройство с Android 12 или выше, ему будет предоставлена возможность указать приблизительное местоположение вместо точного, как показано на рисунке 4.
Рисунок 4. Запрос разрешения пользователя на устройстве под управлением Android 12 или выше предоставляет возможность предоставления точного или приблизительного местоположения.
После того, как пользователь ответит на диалоговое окно системных разрешений, система вызовет реализацию метода onRequestPermissionsResult
вашего приложения. Система передаёт ответ пользователя на диалоговое окно разрешений, а также определённый вами код запроса. Переопределите onRequestPermissionResult
для обработки кода запроса разрешений местоположения, связанных с этим действием Current Place, вставив следующий код ниже 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" />
Добавить действие «Текущее место» в демо-меню
Как и раньше, добавьте демо-версию Current Place на главный экран, добавив её в список модуля Demo
. После создания активности Current Place добавьте её в файл 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 для Maps SDK для Android.
app/build.gradle
dependencies {
// ...
implementation 'com.google.android.gms:play-services-maps:18.2.0'
}
Обновите манифест Android для учета карт
Добавьте следующие элементы meta-data
в элемент application
.
Они встраивают версию сервисов Google Play, с помощью которой было скомпилировано приложение, и указывают ваш ключ 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
}
}
Они используются для хранения данных места и их форматирования.
В начале класса добавьте следующий код для создания переменной, используемой для хранения возвращенных данных Place.
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 для Android.
Что вы узнали
- Установка и настройка Places SDK для Android.
- Установка расширений Kotlin для Places SDK для Android.
- Подробности места загрузки.
- Добавление автозаполнения места .
- Получение текущего места .
Что дальше?
- Для большего вдохновения изучите или создайте форк репозитория примеров и
android-places-demos
на GitHub. - Ознакомьтесь с дополнительными практическими занятиями по кодированию на Kotlin для создания приложений Android с использованием платформы Google Maps.
- Помогите нам создать контент, который будет вам наиболее полезен, ответив на следующий вопрос:
Какие еще практические занятия вы хотели бы увидеть?
Нужная вам кодлаб-программа отсутствует в списке? Запросите её в новом выпуске здесь .
1. Прежде чем начать
В этой лабораторной работе вы узнаете, как интегрировать Places SDK для Android с вашим приложением и использовать каждую из функций Places SDK.
Предпосылки
- Базовые знания Kotlin и разработки под Android
Чему вы научитесь
- Как установить Places SDK для Android с расширениями Kotlin.
- Как загрузить информацию о конкретном месте.
- Как добавить виджет автозаполнения мест в ваше приложение.
- Как загрузить текущее место на основе текущего местоположения устройства.
Что вам понадобится
Для выполнения этой лабораторной работы вам понадобятся следующие учетные записи, сервисы и инструменты:
- Учетная запись Google с включенной функцией выставления счетов.
- Android Studio Bumblebee или выше.
- Сервисы Google Play, установленные в Android Studio.
- Устройство Android или эмулятор Android , на котором работает платформа API Google на базе Android 8 или более поздней версии (инструкции по установке см. в разделе Запуск приложений на эмуляторе Android ).
2. Настройте
Для выполнения шага включения ниже включите Places API и Maps SDK для Android .
Настройте платформу Google Карт
Если у вас еще нет учетной записи Google Cloud Platform и проекта с включенным выставлением счетов, ознакомьтесь с руководством « Начало работы с Google Maps Platform», чтобы создать учетную запись для выставления счетов и проект.
- В Cloud Console щелкните раскрывающееся меню проектов и выберите проект, который вы хотите использовать для этой кодовой лаборатории.
- Включите API и SDK платформы Google Карт, необходимые для этой лабораторной работы, в Google Cloud Marketplace . Для этого следуйте инструкциям в этом видео или в этой документации .
- Сгенерируйте ключ API на странице «Учётные данные» в Cloud Console. Вы можете следовать инструкциям в этом видео или в этой документации . Для всех запросов к платформе Google Карт требуется ключ API.
3. Быстрый старт
Чтобы начать работу как можно быстрее, скачайте стартовый код, который поможет вам разобраться с этой практической работой. Вы можете сразу перейти к решению, но если хотите выполнить все шаги по его самостоятельной сборке, продолжайте читать.
- Клонируйте репозиторий, если у вас установлен
git
.
git clone https://github.com/googlemaps/codelab-places-101-android-kotlin.git
Или нажмите эту кнопку, чтобы загрузить исходный код.
- После загрузки кода откройте проект из каталога
/starter
в Android Studio. Этот проект содержит базовую структуру файлов, необходимую для выполнения этой лабораторной работы. Всё необходимое для работы находится в каталоге/starter
.
Если вы хотите увидеть полный работающий код решения, вы можете просмотреть готовый код в каталоге /solution
.
4. Добавьте свой API-ключ в проект.
В этом разделе описывается, как сохранить ключ API, чтобы приложение могло безопасно ссылаться на него. Не следует регистрировать ключ API в системе контроля версий, поэтому мы рекомендуем хранить его в файле secrets.properties
, который будет размещён в локальной копии корневого каталога вашего проекта. Подробнее о файле secrets.properties
см. в разделе Файлы свойств Gradle .
Чтобы упростить эту задачу, мы рекомендуем вам использовать плагин Secrets Gradle для Android .
Чтобы установить плагин Secrets Gradle для Android в вашем проекте Google Maps:
- В 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 для Android в файл
build.gradle
вашего приложения.
Измените файл build.gradle
на уровне приложения, чтобы добавить зависимость для Places SDK для 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 платформы Google Карт для Android, библиотеки расширения 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.
Инициализируйте 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/
Polder и свяжите его с только что созданным вами макетом. Вставьте этот код в файл:
@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)
- После того, как клиент будет настроен, прикрепите прослушитель нажмите кнопку. Вставьте этот код после создания клиентов в функции
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
и заметьте, что он создает лист обзор, который заполняется итерацией через содержимое Demo
-модуля. Если пользователь нажимает на элемент в списке, The Click Suldier открывает связанную деятельность.
Запустите приложение
- Запустить приложение. На этот раз вы должны увидеть один пункт в списке, представляющий демо -подробную информацию о месте.
- Нажмите на место с подробностями. Текст. Вы должны увидеть представление, которое вы создали с помощью поля ввода и кнопки.
- Нажмите кнопку «Получить детали». Если вы использовали идентификатор места по умолчанию, вы должны увидеть отображаемые имя места, адрес и координаты карты, как показано на рисунке 1.
Рисунок 1. Разместите детали деятельности с отображением ответа.
9. Добавьте место автозаполнения
Создать экран автозаполнения
В app/src/main/res/layout/
LinearLayout
Заполните линейную макет activity_autocomplete.xml
добавив этот код между кронштейнами <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
который вы определили в файле макета.
- Затем определите, что происходит, когда пользователь выбирает одно из предсказаний, представленных AutoCoplete Place. Добавьте этот код в конце функции
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" />
Добавьте активность автозаполнения в демо -меню
Как и прежде, добавьте Dep AutoComplete Demo на домашний экран, добавив его в список в Demo
модуле. Теперь, когда вы создали активность автозаполнения места, добавьте его в файл Demo.kt
в src/main/java/com/google/codelabs/maps/placesdemo/
Foper. Вставьте этот код сразу после элемента 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. Получите текущее место устройства
Создать текущий экран места
В app/src/main/res/layout/
LinearLayout
Заполните линейную макет activity_current.xml
добавив следующий код между кронштейнами <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
Function CallsrequestPermissions
, приложение представит диалоговое окно запроса на разрешение пользователю. Если пользователь запускает устройство, использующую ОС, ниже, чем 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 запрашиваемыми деталями.
Добавьте текущее место в манифесте
Добавьте элемент <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 для Android.
app/build.gradle
dependencies {
// ...
implementation 'com.google.android.gms:play-services-maps:18.2.0'
}
Обновите манифест 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 MAPS и инициализировать фрагмент поддержки MAPS. 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/
polder создайте фрагмент файла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
с idcurrent_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
Добавьте следующий код в объект Class Companion:
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/
folder.
Добавьте следующее в 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 с местами SDK для Android.
Что вы узнали
- Установка и настройка мест SDK для Android.
- Установка расширений Kotlin для мест SDK для Android.
- Загрузка мест детали .
- Добавление места автозаполнения .
- Получение текущего места .
Что дальше?
- Исследуйте или разветвляйте репозиторий GitHub
android-places-demos
образцов и демонстраций для большего вдохновения. - Учитесь у Cotlin CodeLabs для создания приложений Android с платформой Google Maps.
- Помогите нам создать контент, который вы найдете наиболее полезным, ответив на следующий вопрос:
Какие еще коделаб вы бы хотели увидеть?
Коделаб, который вы хотите, не перечислили? Запросите его с новой проблемой здесь .