1. Trước khi bắt đầu
Lớp học lập trình này hướng dẫn bạn cách tích hợp Places SDK for Android với ứng dụng của mình và sử dụng từng tính năng của Places SDK.
Điều kiện tiên quyết
- Có kiến thức cơ bản về Kotlin và phát triển Android
Kiến thức bạn sẽ học được
- Cách cài đặt Places SDK for Android bằng Tiện ích Kotlin.
- Cách tải Thông tin chi tiết về địa điểm cho một địa điểm cụ thể.
- Cách thêm tiện ích Tự động hoàn thành địa điểm vào ứng dụng.
- Cách tải Địa điểm hiện tại dựa trên vị trí hiện được báo cáo của thiết bị.
Bạn cần có
Để hoàn tất lớp học lập trình này, bạn cần có các tài khoản, dịch vụ và công cụ sau:
- Tài khoản Google có bật tính năng thanh toán.
- Android Studio Bumblebee trở lên.
- Dịch vụ Google Play được cài đặt trong Android Studio.
- Một thiết bị Android hoặc trình mô phỏng Android chạy nền tảng API của Google dựa trên Android 8 trở lên (xem phần Chạy ứng dụng trên Trình mô phỏng Android để biết các bước cài đặt).
2. Bắt đầu thiết lập
Đối với bước bật bên dưới, hãy bật Places API và Maps SDK for Android.
Thiết lập Nền tảng Google Maps
Nếu bạn chưa có tài khoản Google Cloud Platform và dự án có bật tính năng thanh toán, vui lòng xem hướng dẫn Bắt đầu sử dụng Google Maps Platform để tạo tài khoản thanh toán và dự án.
- Trong Cloud Console, hãy nhấp vào trình đơn thả xuống dự án rồi chọn dự án mà bạn muốn sử dụng cho lớp học lập trình này.
- Bật các API và SDK của Google Maps Platform cần thiết cho lớp học lập trình này trong Google Cloud Marketplace. Để làm như vậy, hãy làm theo các bước trong video này hoặc tài liệu này.
- Tạo khoá API trong trang Thông tin xác thực của Cloud Console. Bạn có thể làm theo các bước trong video này hoặc tài liệu này. Tất cả các yêu cầu gửi đến Nền tảng Google Maps đều cần có khoá API.
3. Bắt đầu nhanh
Để bạn có thể bắt đầu nhanh chóng, hãy tải mã khởi đầu xuống để giúp bạn theo dõi lớp học lập trình này. Bạn có thể chuyển ngay đến giải pháp, nhưng nếu muốn làm theo tất cả các bước để tự xây dựng, hãy tiếp tục đọc.
- Sao chép kho lưu trữ nếu bạn đã cài đặt
git
.
git clone https://github.com/googlemaps/codelab-places-101-android-kotlin.git
Ngoài ra, bạn có thể nhấp vào nút này để tải mã nguồn xuống.
- Sau khi tải mã xuống, hãy mở dự án trong thư mục
/starter
trong Android Studio. Dự án này bao gồm cấu trúc tệp cơ bản mà bạn cần để hoàn tất lớp học lập trình. Mọi thứ bạn cần để làm việc đều nằm trong thư mục/starter
.
Nếu muốn xem mã giải pháp đầy đủ đang chạy, bạn có thể xem mã hoàn chỉnh trong thư mục /solution
.
4. Thêm khoá API vào dự án
Phần này mô tả cách lưu trữ khoá API để ứng dụng của bạn có thể tham chiếu một cách an toàn. Bạn không nên kiểm tra khoá API trong hệ thống kiểm soát phiên bản, vì vậy, bạn nên lưu trữ khoá này trong tệp secrets.properties
. Tệp này sẽ được đặt trong bản sao cục bộ của thư mục gốc của dự án. Để biết thêm thông tin về tệp secrets.properties
, hãy xem phần Tệp thuộc tính Gradle.
Để đơn giản hoá tác vụ này, bạn nên sử dụng Trình bổ trợ Secrets Gradle cho Android.
Cách cài đặt Trình bổ trợ Secrets Gradle cho Android trong dự án Google Maps:
- Trong Android Studio, hãy mở tệp
build.gradle.kts
hoặcbuild.gradle
cấp cao nhất rồi thêm mã sau vào phần tửdependencies
trongbuildscript
.
Nếu bạn sử dụng build.gradle.kts
, hãy thêm:
buildscript {
dependencies {
classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
}
}
Nếu bạn sử dụng build.gradle
, hãy thêm:
buildscript {
dependencies {
classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
}
}
- Mở tệp
build.gradle.kts
hoặcbuild.gradle
ở cấp mô-đun rồi thêm đoạn mã sau vào phần tửplugins
.
Nếu bạn sử dụng build.gradle.kts
, hãy thêm:
plugins {
// ...
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
}
Nếu bạn sử dụng build.gradle
, hãy thêm:
plugins {
// ...
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
- Trong tệp
build.gradle.kts
hoặcbuild.gradle
ở cấp mô-đun, hãy đảm bảo rằngtargetSdk
vàcompileSdk
được đặt thành 34. - Lưu tệp và đồng bộ hoá dự án với Gradle.
- Mở tệp
secrets.properties
trong thư mục cấp cao nhất, sau đó thêm đoạn mã sau. Thay thếYOUR_API_KEY
bằng khoá API của bạn. Lưu trữ khoá của bạn trong tệp này vìsecrets.properties
không được đưa vào hệ thống quản lý phiên bản.
PLACES_API_KEY=YOUR_API_KEY
- Lưu tệp.
- Tạo tệp
local.defaults.properties
trong thư mục cấp cao nhất (cùng thư mục với tệpsecrets.properties
), rồi thêm mã sau.
PLACES_API_KEY=DEFAULT_API_KEY
Mục đích của tệp này là cung cấp một vị trí dự phòng cho khoá API nếu không tìm thấy tệp secrets.properties
để các bản dựng không bị lỗi. Điều này có thể xảy ra nếu bạn sao chép ứng dụng từ một hệ thống kiểm soát phiên bản bỏ qua secrets.properties
và bạn chưa tạo tệp secrets.properties
cục bộ để cung cấp khoá API.
- Lưu tệp.
- Trong Android Studio, hãy mở tệp
build.gradle.kts
hoặcbuild.gradle
ở cấp mô-đun rồi chỉnh sửa thuộc tínhsecrets
. Nếu thuộc tínhsecrets
không tồn tại, hãy thêm thuộc tính đó.
Chỉnh sửa các thuộc tính của trình bổ trợ để đặt propertiesFileName
thành secrets.properties
, đặt defaultPropertiesFileName
thành local.defaults.properties
và đặt mọi thuộc tính khác.
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. Cài đặt Places SDK for Android
Trong phần này, bạn sẽ thêm Places SDK for Android vào các phần phụ thuộc của ứng dụng.
- Giờ đây, khi khoá API của bạn có thể truy cập được bên trong ứng dụng, hãy thêm phần phụ thuộc Places SDK for Android vào tệp
build.gradle
của ứng dụng.
Sửa đổi tệp build.gradle
ở cấp ứng dụng để thêm phần phụ thuộc cho Places SDK cho Android:
build.gradle ở cấp ứng dụng
dependencies {
// Dependency to include Places SDK for Android
implementation 'com.google.android.libraries.places:places:3.4.0'
}
- Chạy ứng dụng.
Lúc này, bạn sẽ thấy một ứng dụng có màn hình trống. Tiếp tục điền thông tin vào màn hình này bằng 3 bản minh hoạ.
6. Cài đặt Places Android KTX
Đối với các ứng dụng Kotlin sử dụng một hoặc nhiều SDK Android của Nền tảng Google Maps, các thư viện tiện ích Kotlin (KTX) cho phép bạn tận dụng các tính năng của ngôn ngữ Kotlin, chẳng hạn như coroutine, thuộc tính/hàm mở rộng và nhiều tính năng khác. Mỗi Google Maps SDK đều có một thư viện KTX tương ứng như minh hoạ bên dưới:
Trong nhiệm vụ này, hãy sử dụng thư viện Places Android KTX để dùng các tính năng ngôn ngữ dành riêng cho Kotlin trong ứng dụng của bạn.
Thêm phần phụ thuộc Places Android KTX
Để tận dụng các tính năng dành riêng cho Kotlin, hãy thêm thư viện KTX tương ứng cho SDK này vào tệp build.gradle
ở cấp ứng dụng.
build.gradle
dependencies {
// ...
// Places SDK for Android KTX Library
implementation 'com.google.maps.android:places-ktx:3.1.1'
}
7. Khởi chạy Places Client
Khởi chạy Places SDK cho phạm vi ứng dụng
Trong tệp DemoApplication.kt
của thư mục app/src/main/java/com/google/codelabs/maps/placesdemo
, hãy khởi chạy Places SDK for Android. Dán các dòng bên dưới vào cuối hàm onCreate
:
// Initialize the SDK with the Google Maps Platform API key
Places.initialize(this, BuildConfig.PLACES_API_KEY)
Khi bạn tạo ứng dụng, Trình bổ trợ Secrets Gradle cho Android sẽ cung cấp khoá API trong tệp secrets.properties
dưới dạng BuildConfig.PLACES_API_KEY
.
Thêm tệp ứng dụng vào tệp kê khai
Vì bạn đã mở rộng Application
bằng DemoApplication
, nên bạn cần cập nhật tệp kê khai. Thêm thuộc tính android:name
vào phần tử application
trong tệp AndroidManifest.xml
, nằm trong app/src/main
:
<application
android:name=".DemoApplication"
...
</application>
Mã này trỏ tệp kê khai ứng dụng đến lớp DemoApplication
trong thư mục src/main/java/com/google/codelabs/maps/placesdemo/
.
8. Tìm nạp thông tin chi tiết về địa điểm
Tạo màn hình Chi tiết
Bố cục activity_details.xml
có LinearLayout
trống có trong thư mục app/src/main/res/layout/
. Điền vào bố cục tuyến tính bằng cách thêm mã sau đây giữa các dấu ngoặc <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" />
Đoạn mã này thêm một trường nhập văn bản để người dùng có thể nhập Mã địa điểm bất kỳ hoặc sử dụng giá trị mặc định được cung cấp, một nút để bắt đầu yêu cầu Chi tiết về địa điểm và một TextView để hiển thị thông tin từ phản hồi. Các chuỗi được liên kết sẽ được xác định cho bạn trong tệp src/main/res/values/strings.xml
.
Tạo một hoạt động Chi tiết
- Tạo một tệp
DetailsActivity.kt
trong thư mụcsrc/main/java/com/google/codelabs/maps/placesdemo/
và liên kết tệp đó với bố cục bạn vừa tạo. Dán mã này vào tệp:
@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
}
}
}
- Tạo một Places Client để sử dụng với hoạt động này. Dán mã này sau mã để kiểm tra khoá API trong hàm
onCreate
.
// Retrieve a PlacesClient (previously initialized - see DemoApplication)
placesClient = Places.createClient(this)
- Sau khi thiết lập Places Client, hãy đính kèm một trình nghe lượt nhấp vào nút. Dán mã này sau khi tạo Places Client trong hàm
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
}
}
Mã này truy xuất Mã địa điểm đã được nhập vào trường nhập, xác định những trường cần yêu cầu cho địa điểm, tạo FetchPlaceRequest
, bắt đầu tác vụ và theo dõi xem có thành công hay không. Nếu yêu cầu thành công, hàm sẽ điền thông tin chi tiết được yêu cầu vào TextView.
Thêm hoạt động Chi tiết vào tệp kê khai
Thêm phần tử <activity>
cho DetailsActivity
dưới dạng phần tử con của phần tử <application>
trong tệp AndroidManifest.xml
, nằm trong app/src/main
:
<activity android:name=".DetailsActivity" android:label="@string/details_demo_title" />
Thêm hoạt động Chi tiết vào trình đơn minh hoạ
Một mô-đun Demo
trống được cung cấp để liệt kê các bản minh hoạ có sẵn trên màn hình chính. Sau khi tạo một hoạt động Chi tiết về địa điểm, hãy thêm hoạt động đó vào tệp Demo.kt
trong thư mục src/main/java/com/google/codelabs/maps/placesdemo/
bằng mã sau:
DETAILS_FRAGMENT_DEMO(
R.string.details_demo_title,
R.string.details_demo_description,
DetailsActivity::class.java
),
Các chuỗi liên kết được xác định trong tệp src/main/res/values/strings.xml
.
Kiểm tra MainActivity.kt
và quan sát thấy rằng nó tạo ra một ListView được điền bằng cách lặp lại nội dung của mô-đun Demo
. Nếu người dùng nhấn vào một mục trong danh sách, trình nghe sự kiện nhấp chuột sẽ mở hoạt động được liên kết.
Chạy ứng dụng
- Chạy ứng dụng. Lần này, bạn sẽ thấy một mục trong danh sách trình bày bản minh hoạ Chi tiết về địa điểm.
- Nhấn vào văn bản Chi tiết về địa điểm. Bạn sẽ thấy khung hiển thị mà bạn đã tạo cùng với một trường nhập liệu và nút.
- Nhấn vào nút "XEM CHI TIẾT". Nếu đã sử dụng Place ID mặc định, bạn sẽ thấy tên địa điểm, địa chỉ và toạ độ trên bản đồ xuất hiện, như minh hoạ trong Hình 1.
Hình 1. Hoạt động Chi tiết về địa điểm có phản hồi hiển thị.
9. Thêm tính năng Place Autocomplete
Tạo màn hình Tự động hoàn thành
Bố cục activity_autocomplete.xml
có LinearLayout
trống được cung cấp trong thư mục app/src/main/res/layout/
. Điền vào bố cục tuyến tính bằng cách thêm mã này vào giữa dấu ngoặc <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" />
Đoạn mã này thêm một tiện ích AutocompleteSupportFragment
và một TextView để hiển thị thông tin từ phản hồi. Các chuỗi liên kết được xác định trong tệp src/main/res/values/strings.xml
.
Tạo một hoạt động Tự động hoàn thành
- Tạo tệp
AutocompleteActivity.kt
trong thư mụcsrc/main/java/com/google/codelabs/maps/placesdemo/
rồi xác định tệp đó bằng đoạn mã sau:
@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
}
}
Mã này liên kết hoạt động với các khung hiển thị và AutocompleteSupportFramgent
mà bạn đã xác định trong tệp bố cục.
- Tiếp theo, hãy xác định điều gì sẽ xảy ra khi người dùng chọn một trong các kết quả dự đoán do tính năng Tự động hoàn thành địa điểm cung cấp. Thêm mã này vào cuối hàm
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()
}
}
}
Mã này xác định những trường cần yêu cầu cho địa điểm, theo dõi sự kiện chọn địa điểm và theo dõi trạng thái thành công hoặc thất bại. Nếu yêu cầu thành công, hàm sẽ điền thông tin chi tiết về địa điểm vào TextView. Xin lưu ý rằng tính năng Tự động hoàn thành địa điểm trả về một đối tượng Địa điểm; bạn không cần đưa ra một yêu cầu riêng về Chi tiết địa điểm khi sử dụng tiện ích Tự động hoàn thành địa điểm.
Thêm hoạt động Tự động hoàn thành vào tệp kê khai
Thêm phần tử <activity>
cho AutocompleteActivity
dưới dạng phần tử con của phần tử <application>
trong tệp AndroidManifest.xml
, nằm trong app/src/main
:
<activity android:name=".AutocompleteActivity" android:label="@string/autocomplete_fragment_demo_title" />
Thêm hoạt động Tự động hoàn thành vào trình đơn minh hoạ
Giống như trước đây, hãy thêm bản minh hoạ tính năng Tự động hoàn thành địa điểm vào màn hình chính bằng cách thêm bản minh hoạ đó vào danh sách trong mô-đun Demo
. Giờ đây, bạn đã tạo một hoạt động Tự động hoàn thành địa điểm, hãy thêm hoạt động đó vào tệp Demo.kt
trong thư mục src/main/java/com/google/codelabs/maps/placesdemo/
. Dán đoạn mã này ngay sau mục DETAILS_FRAGMENT_DEMO
:
AUTOCOMPLETE_FRAGMENT_DEMO(
R.string.autocomplete_fragment_demo_title,
R.string.autocomplete_fragment_demo_description,
AutocompleteActivity::class.java
),
Các chuỗi liên kết được xác định trong tệp src/main/res/values/strings.xml
.
Chạy ứng dụng
- Chạy ứng dụng. Lần này, bạn sẽ thấy 2 mục trong danh sách trên màn hình chính.
- Nhấn vào hàng Place Autocomplete (Tự động hoàn thành địa điểm). Bạn sẽ thấy một cửa sổ bật lên đầu vào của tính năng Tự động hoàn thành địa điểm như trong Hình 2.
- Bắt đầu nhập tên của một địa điểm. Đây có thể là tên cơ sở, địa chỉ hoặc khu vực địa lý. Các từ dự đoán sẽ xuất hiện khi bạn nhập.
- Chọn một trong các cụm từ dự đoán. Các đề xuất sẽ biến mất và TextView hiện sẽ hiển thị thông tin chi tiết về địa điểm đã chọn như trong Hình 3.
Hình 2. Hoạt động tự động hoàn thành sau khi người dùng nhấn vào trường nhập.
Hình 3. Hoạt động tự động hoàn thành hiển thị Chi tiết về địa điểm sau khi người dùng nhập và chọn "Thác Niagara".
10. Nhận Địa điểm hiện tại của thiết bị
Tạo màn hình Địa điểm hiện tại
Bố cục activity_current.xml
có LinearLayout
trống đã được cung cấp trong thư mục app/src/main/res/layout/
. Điền vào bố cục tuyến tính bằng cách thêm mã sau vào giữa các dấu ngoặc <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" />
Tạo một hoạt động Địa điểm hiện tại
- Tạo tệp
CurrentPlaceActivity.kt
trong thư mụcsrc/main/java/com/google/codelabs/maps/placesdemo/
rồi xác định tệp đó bằng mã sau:
@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()
}
}
}
Đoạn mã này liên kết hoạt động với các khung hiển thị mà bạn đã xác định trong tệp bố cục. Thao tác này cũng thêm một trình nghe lượt nhấp vào nút để gọi hàm checkPermissionThenFindCurrentPlace
khi người dùng nhấp vào nút.
- Xác định
checkPermissionThenFindCurrentPlace()
để kiểm tra quyền truy cập thông tin vị trí chính xác và yêu cầu cấp quyền nếu quyền này chưa được cấp. Dán mã này sau hàmonCreate
.
/**
* 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
}
- Khi nhánh
else
của hàmcheckPermissionThenFindCurrentPlace
gọirequestPermissions
, ứng dụng sẽ hiển thị hộp thoại yêu cầu cấp quyền cho người dùng. Nếu đang dùng thiết bị chạy hệ điều hành thấp hơn Android 12, thì người dùng chỉ có thể cấp quyền truy cập vị trí chính xác (chi tiết). Nếu đang dùng thiết bị chạy Android 12 trở lên, người dùng sẽ có lựa chọn cung cấp vị trí ước chừng (không chính xác) thay vì vị trí chính xác, như minh hoạ trong Hình 4.
Hình 4. Khi yêu cầu người dùng cấp quyền trên thiết bị chạy Android 12 trở lên, bạn có thể chọn cấp quyền truy cập vào vị trí chính xác hoặc vị trí ước chừng.
Sau khi người dùng phản hồi hộp thoại cấp quyền của hệ thống, hệ thống sẽ gọi chế độ triển khai onRequestPermissionsResult
của ứng dụng. Hệ thống sẽ chuyển phản hồi người dùng tới hộp thoại cấp quyền, cũng như mã yêu cầu mà bạn đã xác định. Ghi đè onRequestPermissionResult
để xử lý mã yêu cầu cho quyền truy cập thông tin vị trí liên quan đến hoạt động Current Place này bằng cách dán mã sau đây bên dưới 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()
}
- Sau khi được cấp quyền, hàm
findCurrentPlace
sẽ chạy. Xác định hàm bằng mã này sau hàmonRequestPermissionsResult
.
/**
* 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()
}
}
Đoạn mã này xác định những trường cần yêu cầu cho các địa điểm có khả năng xuất hiện, tạo một FindCurrentPlaceRequest
, bắt đầu tác vụ và điền thông tin chi tiết được yêu cầu vào TextView.
Thêm hoạt động Địa điểm hiện tại vào tệp kê khai
Thêm phần tử <activity>
cho CurrentPlaceActivity
dưới dạng phần tử con của phần tử <application>
trong tệp AndroidManifest.xml
, nằm trong app/src/main
:
<activity android:name=".CurrentPlaceActivity" android:label="@string/current_demo_title" />
Thêm hoạt động Địa điểm hiện tại vào trình đơn minh hoạ
Giống như trước đây, hãy thêm bản minh hoạ Current Place vào màn hình chính bằng cách thêm bản minh hoạ đó vào danh sách trong mô-đun Demo
. Bây giờ, sau khi bạn đã tạo một hoạt động Current Place, hãy thêm hoạt động đó vào tệp Demo.kt
trong thư mục src/main/java/com/google/codelabs/maps/placesdemo/
. Dán đoạn mã này ngay sau mục AUTOCOMPLETE_FRAGMENT_DEMO
:
CURRENT_FRAGMENT_DEMO(
R.string.current_demo_title,
R.string.current_demo_description,
CurrentPlaceActivity::class.java
),
Các chuỗi liên kết được xác định trong tệp src/main/res/values/strings.xml
.
Chạy ứng dụng
- Chạy ứng dụng. Lần này, bạn sẽ thấy 3 mục trong danh sách trên màn hình chính.
- Nhấn vào hàng Địa điểm hiện tại. Bạn sẽ thấy một nút trên màn hình.
- Nhấn vào nút. Nếu bạn chưa cấp quyền truy cập thông tin vị trí cho ứng dụng này trước đây, thì một yêu cầu cấp quyền sẽ xuất hiện.
- Cấp quyền cho ứng dụng truy cập vào thông tin vị trí của thiết bị.
- Nhấn vào nút đó một lần nữa. Lần này, một danh sách gồm tối đa 20 địa điểm lân cận và xác suất của các địa điểm đó sẽ xuất hiện, như minh hoạ trong Hình 5.
Hình 5. Đưa ra các kết quả phù hợp có khả năng là Địa điểm hiện tại cho vị trí được báo cáo của thiết bị.
11. Hiển thị Địa điểm hiện tại trên bản đồ
Thêm phần phụ thuộc Map
Trong tệp build.gradle
ở cấp mô-đun, hãy thêm phần phụ thuộc Dịch vụ Google Play cho Maps SDK cho Android.
app/build.gradle
dependencies {
// ...
implementation 'com.google.android.gms:play-services-maps:18.2.0'
}
Cập nhật tệp kê khai Android để tính đến các bản đồ
Thêm các phần tử meta-data
sau đây vào phần tử application
.
Các thư viện này nhúng phiên bản Dịch vụ Google Play mà ứng dụng được biên dịch và chỉ định khoá API của bạn.
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}" />
Thêm khoá API vào secrets.properties
Mở tệp secrets.properties
trong thư mục cấp cao nhất, sau đó thêm đoạn mã sau. Thay thế YOUR_API_KEY
bằng khoá API của bạn.
MAPS_API_KEY=YOUR_API_KEY
Mở tệp local.defaults.properties
trong thư mục cấp cao nhất (cùng thư mục với tệp secrets.properties
), sau đó thêm đoạn mã sau.
MAPS_API_KEY=DEFAULT_API_KEY
Kiểm tra khoá API
Trong onCreate()
, ứng dụng sẽ kiểm tra khoá API Maps và khởi động mảnh hỗ trợ Maps. getMapAsync()
dùng để đăng ký lệnh gọi lại bản đồ.
Thêm mã sau để thực hiện việc này.
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)
// ...
}
Tạo bố cục bản đồ
- Trong thư mục
app/src/main/res/layout/
, hãy tạo tệp bố cụcfragment_map.xml
rồi điền đoạn mã sau vào bố cục.
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" />
Thao tác này xác định một SupportMapFragment
để đóng vai trò là vùng chứa cho bản đồ và cung cấp quyền truy cập vào đối tượng GoogleMap
.
- Trong bố cục
activity_current.xml
có trong thư mụcapp/src/main/res/layout/
, hãy thêm đoạn mã sau vào cuối bố cục tuyến tính.
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
được thêm vào sẽ tham chiếu đến một tài nguyên chuỗi mới cần được tạo.
- Trong
app/src/main/res/values/strings.xml
, hãy thêm tài nguyên chuỗi sau.
res/values/strings.xml
<string name="showing_most_likely_place">Showing most likely place</string>
Vì các khung hiển thị bổ sung đã được thêm vào bản đồ, nên TextView
hiển thị danh sách địa điểm cần được đặt chiều cao để các khung hiển thị này vẫn xuất hiện.
- Thêm thuộc tính
maxHeight
vàoTextView
bằng mã nhận dạngcurrent_response_content
res/layout/activity_current.xml
android:maxHeight="200dp"
Triển khai OnMapReadyCallback
Triển khai giao diện OnMapReadyCallback
bằng cách thêm giao diện này vào nội dung khai báo lớp và ghi đè phương thức onMapReady()
để thiết lập bản đồ khi đối tượng GoogleMap
có sẵn:
CurrentPlaceActivity.kt
class CurrentPlaceActivity : AppCompatActivity(), OnMapReadyCallback {
Thêm mã sau vào cuối lớp:
CurrentPlaceActivity.kt
override fun onMapReady(map: GoogleMap) {
this.map = map
lastKnownLocation?.let { location ->
map.moveCamera(
CameraUpdateFactory.newLatLngZoom(
location,
DEFAULT_ZOOM
)
)
}
}
Lệnh gọi lại yêu cầu một số biến lớp để hoạt động đúng cách. Ngay sau tiêu đề lớp, hãy thêm nội dung sau:
CurrentPlaceActivity.kt
private var map: GoogleMap? = null
private var lastKnownLocation: LatLng? = null
Thêm mã sau vào đối tượng đồng hành của lớp:
CurrentPlaceActivity.kt
private const val DEFAULT_ZOOM = 15f
Chạy ứng dụng
- Chạy ứng dụng.
- Nhấn vào hàng Địa điểm hiện tại. Bạn sẽ thấy một nút trên màn hình.
- Nhấn vào nút. Nếu bạn chưa cấp quyền truy cập thông tin vị trí cho ứng dụng này trước đây, thì một yêu cầu cấp quyền sẽ xuất hiện.
- Cấp quyền cho ứng dụng truy cập vào thông tin vị trí của thiết bị.
- Nhấn vào nút đó một lần nữa. Bản đồ sẽ xuất hiện.
Hình 6. Hoạt động Hiện tại cho thấy bản đồ.
Cập nhật bản đồ bằng một địa điểm
Thêm mã sau vào cuối lớp:
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
}
}
Các đối tượng này được dùng để lưu trữ dữ liệu của Địa điểm và định dạng dữ liệu đó.
Ở đầu lớp, hãy thêm đoạn mã sau để tạo một biến dùng để lưu trữ dữ liệu của Địa điểm được trả về.
CurrentPlaceActivity.kt
private val likelyPlaces = mutableListOf<LikelyPlace>()
Trong bước này , mã sẽ được thay đổi để người dùng thấy danh sách Địa điểm và họ sẽ chọn một địa điểm để hiển thị trên bản đồ. Tất cả dữ liệu về Địa điểm đều xuất hiện trong một danh sách trên màn hình.
Trong hàm findCurrentPlace
, trong khối lifecycleScope.launch
trước dòng mã này
CurrentPlaceActivity.kt
responseView.text = response.prettyPrint()
thêm mã sau:
CurrentPlaceActivity.kt
likelyPlaces.clear()
likelyPlaces.addAll(
response.placeLikelihoods.take(M_MAX_ENTRIES).mapNotNull { placeLikelihood ->
placeLikelihood.toLikelyPlace()
}
)
openPlacesDialog()
Mã này yêu cầu một hằng số cho số lượng địa điểm tối đa cần hiển thị.
Trong đối tượng đồng hành, hãy thêm mã cho hằng số đó.
CurrentPlaceActivity.kt
private const val M_MAX_ENTRIES = 5
Thêm mã sau đây để tạo hộp thoại cho phép người dùng chọn một địa điểm.
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()
}
Theo các phương pháp hay nhất của Android, hộp thoại này tham chiếu đến một tài nguyên chuỗi cần được thêm vào tệp tài nguyên strings.xml
nằm trong thư mục app/src/main/res/values/
.
Thêm nội dung sau vào strings.xml
:
res/values/strings.xml
<string name="pick_place">Choose a place</string>
Sau đó, các hàm này sẽ gọi hàm setPlaceOnMap
để di chuyển camera và đặt một điểm đánh dấu tại vị trí đã chọn.
Thêm mã sau:
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)
)
}
Bạn cũng nên lưu và khôi phục trạng thái của bản đồ.
Để lưu trạng thái của thành phần này, hãy ghi đè hàm onSaveInstanceState
và thêm mã sau:
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)
}
Để khôi phục trạng thái của thành phần này, trong onCreate
, hãy thêm mã sau sau lệnh gọi đến setContentView
:
CurrentPlaceActivity.kt
if (savedInstanceState != null) {
lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION)
}
Để lưu và khôi phục, bạn cần có một khoá. Đây là một hằng số từ đối tượng đồng hành.
Trong khối đối tượng đồng hành, hãy thêm nội dung sau:
CurrentPlaceActivity.kt
// Key for storing activity state.
private const val KEY_LOCATION = "location"
Chạy ứng dụng
- Chạy ứng dụng.
- Nhấn vào hàng Địa điểm hiện tại. Bạn sẽ thấy một nút trên màn hình.
- Nhấn vào nút. Nếu bạn chưa cấp quyền truy cập thông tin vị trí cho ứng dụng này trước đây, thì một yêu cầu cấp quyền sẽ xuất hiện.
- Cấp quyền cho ứng dụng truy cập vào thông tin vị trí của thiết bị.
- Nhấn vào nút đó một lần nữa.
- Chọn một địa điểm bằng cách nhấn vào địa điểm đó. Bản đồ sẽ được phóng to và căn giữa với một điểm đánh dấu được đặt tại vị trí đã chọn.
Hình 7. Bản đồ có điểm đánh dấu tại vị trí đã chọn.
12. Xin chúc mừng
Bạn đã tạo thành công một ứng dụng Android bằng Places SDK for Android.
Kiến thức bạn học được
- Cài đặt và định cấu hình Places SDK cho Android.
- Cài đặt Tiện ích Kotlin cho Places SDK for Android.
- Đang tải Thông tin chi tiết về địa điểm.
- Thêm Place Autocomplete.
- Lấy Địa điểm hiện tại.
Tiếp theo là gì?
- Khám phá hoặc phân nhánh kho lưu trữ
android-places-demos
GitHub gồm các mẫu và bản minh hoạ để có thêm ý tưởng. - Tìm hiểu qua các lớp học lập trình khác về Kotlin để tạo ứng dụng Android bằng Nền tảng Google Maps.
- Hãy giúp chúng tôi tạo nội dung hữu ích nhất cho bạn bằng cách trả lời câu hỏi sau:
Bạn muốn xem những lớp học lập trình nào khác?
Lớp học lập trình bạn muốn không có trong danh sách? Yêu cầu cấp lại bằng cách báo lỗi mới tại đây.