关于此 Codelab
1. 准备工作
此 Codelab 会教您如何将 Places SDK for Android 与您的应用集成并使用 Places SDK 的各项功能。
前提条件
- 具备 Kotlin 和 Android 开发方面的基础知识
学习内容
- 如何使用 Kotlin 扩展程序安装 Places SDK for Android。
- 如何加载特定地点的地点详情。
- 如何为应用添加地点自动补全微件。
- 如何根据当前报告的设备位置加载当前地点。
所需条件
若要完成此 Codelab,您需要以下帐号、服务和工具:
- 启用了结算功能的 Google 帐号。
- Android Studio Bumblebee 或更高版本。
- 已安装在 Android Studio 中的 Google Play 服务。
- Android 设备或 Android 模拟器,搭载的是基于 Android 8 或更高版本的 Google API 平台(如需了解具体安装步骤,请参阅在 Android 模拟器上运行应用)。
2. 进行设置
若要完成下面的启用步骤,请启用 Places API。
设置 Google Maps Platform
如果您还没有已启用结算功能的 Google Cloud Platform 帐号和项目,请参阅 Google Maps Platform 使用入门指南,创建结算帐号和项目。
- 在 Cloud Console 中,点击项目下拉菜单,选择要用于此 Codelab 的项目。
3. 快速入门
为了让您能尽快上手,请下载起始代码,以便顺利完成此 Codelab。您可以跳到解决方案部分,但如果您想要按照所有步骤自行构建,请继续阅读。
- 克隆代码库(如果您已安装
git
)。
git clone https://github.com/googlemaps/codelab-places-101-android.git
或者,点击下方按钮以下载源代码。
- 下载代码后,请在 Android Studio 中打开
/starter
目录下的项目。此项目包含完成此 Codelab 所需的基本文件结构。您需要的所有内容均位于/starter
目录下。
如果您想查看正在运行的完整解决方案代码,可以在 /solution
目录下查看完整代码。
4. 安装 Places SDK for Android
在本部分中,您需要将 Places SDK for Android 添加到应用的依赖项中。
添加您的 API 密钥
向应用提供您之前创建的 API 密钥,以便 Places SDK for Android 将您的密钥与应用相关联。
- 打开项目根目录下名为
local.properties
的文件(与gradle.properties
和settings.gradle
所在级别相同)。 - 定义一个新密钥
GOOGLE_MAPS_API_KEY
,将其值设为您创建的 API 密钥。
local.properties
GOOGLE_MAPS_API_KEY=YOUR_KEY_HERE
请注意,local.properties
已列在 Git 代码库中的 .gitignore
文件内。这是因为您的 API 密钥被视为敏感信息,不得签入源代码控制(如果可能的话)。
- 接下来,公开您的 API 密钥以便在整个应用中使用,为此,请在您应用的
build.gradle
文件(位于app/
目录下)中添加 Android 版 Secrets Gradle 插件,并在plugins
块中添加下面一行代码:
应用级 build.gradle
plugins {
// ...
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
- 修改项目级
build.gradle
文件,以包含以下类路径:
项目级 build.gradle
buildscript {
dependencies {
// ...
classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
}
}
构建时,此插件会将您在 local.properties
文件中定义的密钥以 build 变量的形式在 Android 清单文件中提供,并以变量的形式在 Gradle 生成的 BuildConfig
类中提供。使用此插件可移除样板代码,否则需要使用该代码从 local.properties
中读取属性,以便在整个应用中对其进行访问。
添加 Places SDK for Android 依赖项
- 现在您的 API 密钥可在应用内进行访问,接下来要将 Places SDK for Android 依赖项添加到您应用的
build.gradle
文件中。
在此 Codelab 附带的入门级项目中,已为您添加此依赖项。
应用级 build.gradle
dependencies {
// Dependency to include Places SDK for Android
implementation 'com.google.android.libraries.places:places:2.6.0'
}
- 运行应用。
现在,您应该会看到一个显示空白屏幕的应用。请继续完成后面三个演示,填充此屏幕。
5. 安装 Places Android KTX
对于使用一个或多个 Google Maps Platform Android SDK 的 Kotlin 应用,您可以通过 Kotlin 扩展程序 (KTX) 库来充分利用 Kotlin 语言功能(例如协程、扩展属性/函数等)。每个 Google 地图 SDK 都有一个对应的 KTX 库,如下所示:
在此任务中,您需要使用 Places Android KTX 库,以便在您的应用中使用 Kotlin 特有的语言功能。
添加 Places Android KTX 依赖项
为了充分利用 Kotlin 特有的功能,请在应用级 build.gradle
文件中添加此 SDK 对应的 KTX 库。
build.gradle
dependencies {
// ...
// Places SDK for Android KTX Library
implementation 'com.google.maps.android:places-ktx:2.0.0'
}
6. 初始化 Places Client
在应用范围内初始化 Places SDK
在 app/src/main/java/com/google/codelabs/maps/placesdemo
文件夹的 DemoApplication.kt
文件中,初始化 Places SDK for Android。将以下几行代码粘贴到 onCreate
函数末尾:
// Initialize the SDK with the Google Maps Platform API key
Places.initialize(this, BuildConfig.GOOGLE_MAPS_API_KEY)
当您构建应用时,Android 版 Secrets Gradle 插件会以 BuildConfig.GOOGLE_MAPS_API_KEY
的形式提供 local.properties
文件中的 API 密钥。
将应用文件添加到清单中
由于您使用 DemoApplication
扩展了 Application
,因此需要更新清单。将 android:name
属性添加到 AndroidManifest.xml
文件(位于 app/src/main
)的 application
元素中:
<application
android:name=".DemoApplication"
...
</application>
上面的代码会将应用清单指向 src/main/java/com/google/codelabs/maps/placesdemo/
文件夹中的 DemoApplication
类。
7. 提取地点详情
创建“详情”屏幕
app/src/main/res/layout/
文件夹中提供了 LinearLayout
为空的 activity_details.xml
布局。将以下代码添加到 <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" />
上面的代码会添加一个文本输入字段(在该字段中,用户可以输入任意地点 ID 或使用提供的默认值)、一个用于发起“地点详情”请求的按钮,以及一个用于显示响应中的信息的 TextView。关联的字符串已在 src/main/res/values/strings.xml
文件中为您定义。
创建详情 activity
- 在
src/main/java/com/google/codelabs/maps/placesdemo/
文件夹中创建一个DetailsActivity.kt
文件,并将其与您刚刚创建的布局相关联。将以下代码粘贴到该文件中:
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)
}
}
- 创建用于此 activity 的 Places Client。将以下代码粘贴到
onCreate
函数中设置的视图对象之后。
// Retrieve a PlacesClient (previously initialized - see DemoApplication)
placesClient = Places.createClient(this)
- Places Client 设置完毕后,将点击监听器附加到按钮。将以下代码粘贴到
onCreate
函数中的 Places Client 创建代码之后。
// Upon button click, fetch and display the Place Details
detailsButton.setOnClickListener {
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
}
}
}
上面的代码会检索在输入字段中输入的地点 ID、定义要针对地点请求的字段、创建 FetchPlaceRequest
、启动任务以及监听请求是否成功。如果请求成功,该函数会使用请求的详情填充 TextView。
- 通过定义扩展函数,将
FetchPlaceResponse
转换为文本。我们提供了一个StringUtil.kt
文件,用于简化将 Places SDK 响应转换为直观易读的字符串的流程。
在 DetailsActivity.kt
文件末尾,定义一个用于将 Fetch Place 响应对象转换为字符串的函数。
fun FetchPlaceResponse.prettyPrint(): String {
return StringUtil.stringify(this, false)
}
将详情 activity 添加到清单中
添加 DetailsActivity
的 <activity>
元素作为 AndroidManifest.xml
文件(位于 app/src/main
)中 <application>
元素的子元素:
<activity android:name=".DetailsActivity" />
将详情 activity 添加到演示菜单中
我们提供了一个空 Demo
模块,用于列出主屏幕上的可用演示。现在,您已经创建了地点详情 activity,可以使用以下代码将其添加到 src/main/java/com/google/codelabs/maps/placesdemo/
文件夹中的 Demo.kt
文件中了:
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
模块的内容进行填充。如果用户点按列表中的某一项,点击监听器便会打开关联的 activity。
运行应用
- 运行应用。这次您应该会在列表中看到一项呈现地点详情演示的内容。
- 点按“地点详情”文本。您应该会看到已创建的视图,其中包含一个输入字段和一个按钮。
- 点按“获取详情”按钮。如果您使用的是默认地点 ID,则应该会看到显示的地点名称、地址和地图坐标,如图 1 所示。
图 1. 显示响应的地点详情 activity。
8. 添加地点自动补全功能
创建“自动补全”屏幕
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
文件中进行定义。
创建自动补全 activity
- 在
src/main/java/com/google/codelabs/maps/placesdemo/
文件夹中创建一个AutocompleteActivity.kt
文件,并使用以下代码对其进行定义:
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
}
}
上面的代码会将 activity 与您在布局文件中定义的视图和 AutocompleteSupportFramgent
相关联。
- 接下来,定义在用户选择地点自动补全功能提供的某个预测结果后出现的情况。将以下代码添加到
onCreate
函数末尾:
// Specify the types of place data to return.
autocompleteFragment.setPlaceFields(listOf(Place.Field.NAME, Place.Field.ID, Place.Field.LAT_LNG, Place.Field.ADDRESS))
// Listen to place selection events
lifecycleScope.launchWhenCreated {
autocompleteFragment.placeSelectionEvents().collect { event ->
when (event) {
is PlaceSelectionSuccess -> {
val place = event.place
responseView.text = StringUtil.stringifyAutocompleteWidget(place, false)
}
is PlaceSelectionError -> Toast.makeText(
this@AutocompleteActivity,
"Failed to get place '${event.status.statusMessage}'",
Toast.LENGTH_SHORT
).show()
}
}
上面的代码定义了要针对地点请求的字段,还会监听地点选择事件以及请求是否成功。如果请求成功,该函数会使用地点详情填充 TextView。请注意,地点自动补全功能会返回 Place 对象;使用地点自动补全微件时,无需单独发出“地点详情”请求。
将自动补全 activity 添加到清单中
添加 AutocompleteActivity
的 <activity>
元素作为 AndroidManifest.xml
文件(位于 app/src/main
)中 <application>
元素的子元素:
<activity android:name=".AutocompleteActivity" />
将自动补全 activity 添加到演示菜单中
与之前一样,将地点自动补全演示附加到 Demo
模块的列表中,以将其添加到主屏幕。现在,您已经创建了地点自动补全 activity,可以将其添加到 src/main/java/com/google/codelabs/maps/placesdemo/
文件夹的 Demo.kt
文件中了。粘贴以下代码,使其紧跟在 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. 在用户点按输入字段后显示的自动补全 activity。
图 3. 在用户输入并选择“Niagara Falls”后显示地点详情的自动补全 activity。
9. 获取设备的当前地点
创建“当前地点”屏幕
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" />
创建当前地点 activity
- 在
src/main/java/com/google/codelabs/maps/placesdemo/
文件夹中创建一个CurrentActivity.kt
文件,并使用以下代码对其进行定义:
class CurrentPlaceActivity : AppCompatActivity() {
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()
}
}
}
上面的代码会将 activity 与您在布局文件中定义的视图相关联,还会将点击监听器添加到按钮,以在用户点击按钮时调用函数 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(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
),
PERMISSION_REQUEST_CODE
)
}
}
}
companion object {
private val TAG = "CurrentPlaceActivity"
private const val PERMISSION_REQUEST_CODE = 9
}
- 当
checkPermissionThenFindCurrentPlace
函数的else
分支调用requestPermissions
时,应用会向用户显示权限请求对话框。如果用户运行的设备所搭载的操作系统 (OS) 低于 Android 12,则用户只能授予确切(精确)位置权限。如果用户使用的是搭载 Android 12 或更高版本的设备,他们可以选择提供大致(粗略)位置信息,而不提供确切(精确)位置信息,如图 4 所示。
图 4. 在搭载 Android 12 或更高版本的设备上请求用户权限时,用户可以选择授予确切位置权限,也可以选择授予大致位置权限。
当用户响应系统权限对话框后,系统就会调用应用的 onRequestPermissionsResult
实现。系统会传入用户对权限对话框的响应以及您定义的请求代码。通过将以下代码粘贴到 checkPermissionThenFindCurrentPlace
下方,替换 onRequestPermissionResult
以处理与此当前地点 activity 相关的位置权限的请求代码。
@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)
// Use the builder to create a FindCurrentPlaceRequest.
val request: FindCurrentPlaceRequest = FindCurrentPlaceRequest.newInstance(placeFields)
// 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
lifecycleScope.launch {
try {
val response = placesClient.awaitFindCurrentPlace(placeFields)
responseView.text = response.prettyPrint()
// Enable scrolling on the long list of likely places
val movementMethod = ScrollingMovementMethod()
responseView.movementMethod = movementMethod
} catch (e: Exception) {
e.printStackTrace()
responseView.text = e.message
}
}
} else {
Log.d(TAG, "LOCATION permission not granted")
checkPermissionThenFindCurrentPlace()
}
}
上面的代码可定义要针对可能地点请求的字段、创建 FindCurrentPlaceRequest
、启动任务以及使用请求的详情填充 TextView。
- 通过定义扩展函数,将
FindCurrentPlaceResponse
转换为文本。我们提供了一个StringUtil.kt
文件,用于简化将 Places SDK 响应转换为直观易读的字符串的流程。
在 CurrentPlaceActivity.kt
文件末尾,定义一个用于将 Current Place 响应对象转换为字符串的函数。
fun FindCurrentPlaceResponse.prettyPrint(): String {
return StringUtil.stringify(this, false)
}
将当前地点 activity 添加到清单中
添加 CurrentPlaceActivity
的 <activity>
元素作为 AndroidManifest.xml
文件(位于 app/src/main
)中 <application>
元素的子元素:
<activity android:name=".CurrentPlaceActivity" />
将当前地点 activity 添加到演示菜单中
与之前一样,将当前地点演示附加到 Demo
模块的列表中,以将其添加到主屏幕。现在,您已经创建了当前地点 activity,可以将其添加到 src/main/java/com/google/codelabs/maps/placesdemo/
文件夹的 Demo.kt
文件中了。粘贴以下代码,使其紧跟在 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. 显示与报告的设备位置可能相符的当前地点。
10. 恭喜
您已成功使用 Places SDK for Android 构建了一个 Android 应用。
所学内容
- 安装和配置 Places SDK for Android。
- 安装适用于 Places SDK for Android 的 Kotlin 扩展程序。
- 加载地点详情。
- 添加地点自动补全功能。
- 获取当前地点。
后续步骤
- 探索包含各种示例和演示的
android-places-demos
GitHub 代码库或创建分支,以便汲取更多灵感。 - 通过更多 Kotlin Codelab 进行学习,了解如何使用 Google Maps Platform 构建 Android 应用。
- 请回答下面的问题,帮助我们为您制作最为有用的内容:
您还想学习哪些 Codelab?
没有列出您希望了解的 Codelab?没关系,请在此处通过创建新问题的方式申请 Codelab。