程式碼研究室簡介
1. 事前準備
本程式碼研究室會教導您如何將 Places SDK for Android 與您的應用程式整合,以及如何使用各個 Places SDK 功能。
必要條件
- Kotlin 和 Android 開發的基本知識
課程內容
- 如何安裝 Places SDK for Android 搭配 Kotlin 擴充功能。
- 如何載入特定地點的地點詳細資料。
- 如何在應用程式中加入「地點自動完成」小工具。
- 如何根據裝置目前回報的位置載入目前地點。
軟硬體需求
要完成此程式碼研究室,您需要下列帳戶、服務和工具:
- 已啟用帳單功能的 Google 帳戶。
- Android Studio Bumblebee 以上版本。
- 在 Android Studio 上安裝的 Google Play 服務。
- 搭載 Android 8 以上版本的 Google API 平台的 Android 裝置或 Android Emulator (如需安裝步驟說明,請參閱在 Android Emulator 上執行應用程式)。
2. 做好準備
請在下方啟用啟用步驟,以啟用 Places API。
設定 Google 地圖平台
如果您還沒有 Google Cloud Platform 帳戶和已啟用計費功能的專案,請參閱開始使用 Google 地圖平台指南,建立帳單帳戶和專案。
- 在 Cloud Console 中按一下專案下拉式選單,然後選取您要用於這個程式碼研究室的專案。
3. 快速入門
建議您下載範例程式碼,以便快速上手這個程式碼研究室。你可以直接跳到解決方案頁面。不過,如果你想確實完成所有步驟,請繼續閱讀本文。
- 如果您已安裝
git
,請複製存放區。
git clone https://github.com/googlemaps/codelab-places-101-android.git
或者,按一下此按鈕即可下載原始碼。
- 下載程式碼後,開啟 Android Studio 中
/starter
目錄內的專案。此專案包含完成程式碼研究室所需的基本檔案結構。您所需的一切都位於/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
檔案中加入 Secrets Gradle Plugin for Android 外掛程式 (位於app/
目錄中),並在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
檔案內定義自己在 Android 資訊清單檔案中做為建構變數的鍵,也可在 Gradle 產生的 BuildConfig
類別做為建構期間的變數使用。使用這個外掛程式,即可移除從 local.properties
讀取屬性時需要的樣板程式碼,以便在整個應用程式中存取。
新增 Places SDK for Android 依附元件
- 現在,您可以在應用程式內存取 API 金鑰,請將 Places SDK for Android 依附元件加入應用程式的
build.gradle
檔案。
在這個程式碼研究室的入門專案中,您已新增這個依附元件。
應用程式層級的 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 地圖平台 Android SDK 的 Kotlin 應用程式,Kotlin 擴充功能 (KTX) 程式庫可讓您使用 Kotlin 語言功能,例如協同程式、擴充功能屬性/函式等等。每個 Google Maps 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)
當您建構應用程式時,Secrets Gradle Plugin for Android 可讓 local.properties
檔案中的 API 金鑰設為 BuildConfig.GOOGLE_MAPS_API_KEY
。
將應用程式檔案新增至資訊清單
由於您將「Application
」擴充至「DemoApplication
」,因此您必須更新資訊清單。在 AndroidManifest.xml
檔案的 application
元素中加入 android:name
屬性 (位於 app/src/main
):
<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
」檔案中為您定義相關字串。
建立詳細資料活動
- 在
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)
}
}
- 建立要用於此活動的 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
檔案的結尾定義函式,以便將「擷取地點」回應物件轉換成字串。
fun FetchPlaceResponse.prettyPrint(): String {
return StringUtil.stringify(this, false)
}
將詳細資料活動新增至資訊清單
將 DetailsActivity
的 <activity>
元素新增為 AndroidManifest.xml
檔案中的 <application>
元素子項,位於 app/src/main
:
<activity android:name=".DetailsActivity" />
在 [示範] 選單中新增「活動」活動
系統會提供空白的 Demo
模組,在主畫面列出可用的示範。現在您已建立 Place Details 活動,請將活動新增到 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
模組的內容疊代。如果使用者輕觸清單中的項目,點擊接聽器就會開啟相關活動。
執行應用程式
- 執行應用程式。這次您應該在清單中看到一個項目,其中有 Place Details 示範。
- 輕觸 [地點詳細資訊] 文字。您應該會看到含有輸入欄位和按鈕的視圖。
- 輕觸 [取得詳細資料] 按鈕。如果您使用了預設的地點 ID,應該就會看到地點名稱、地址和地圖座標,如圖 1 所示。
圖 1. 「地點詳細資料」活動會顯示回應。
8. 新增 Place Autocomplete
建立自動完成畫面
「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
檔案中定義。
建立自動完成活動
- 在
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
}
}
這段程式碼會將活動與您在版面配置檔案中定義的資料檢視和 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 中填入地點詳細資訊。請注意,「地點自動完成」會傳回「地點」物件;使用「地點自動完成」小工具時,無需另外發出「地點詳細資料」要求。
將「自動完成」活動加入資訊清單
將 AutocompleteActivity
的 <activity>
元素新增為 AndroidManifest.xml
檔案中的 <application>
元素子項,位於 app/src/main
:
<activity android:name=".AutocompleteActivity" />
將「自動完成」活動新增至示範選單
和先前一樣,只要將「地點自動完成」示範影片加到「Demo
」模組的清單中,即可將其新增至主畫面。您已建立「地點自動完成」活動,現在可以將該活動新增至「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 所示。
- 開始輸入地點名稱。可以是建築物名稱、地址或地理區域。系統會在你輸入的同時顯示預測查詢字串。
- 請選取一項預測。現在,系統應會停止預測,且「文字檢視」現在會顯示所選位置的詳細資訊,如圖 3 所示。
圖 2:使用者輕觸輸入欄位後自動完成活動。
圖 3:在使用者輸入和選取「尼加拉瀑布」時顯示「地點詳細資料」的自動完成活動。
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" />
建立目前地點活動
- 在
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()
}
}
}
這段程式碼會將活動與您在版面配置檔案中定義的資料檢視建立關聯。它也會在按鈕中加入點擊事件監聽器,以便在使用者點擊按鈕時呼叫 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
時,應用程式會向使用者顯示權限要求對話方塊。如果使用者的裝置搭載的作業系統低於 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)
// 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
檔案的結尾定義一個函式,將「目前位置」回應物件轉換成字串。
fun FindCurrentPlaceResponse.prettyPrint(): String {
return StringUtil.stringify(this, false)
}
在資訊清單中新增「目前位置」活動
將 CurrentPlaceActivity
的 <activity>
元素新增為 AndroidManifest.xml
檔案中的 <application>
元素子項,位於 app/src/main
:
<activity android:name=".CurrentPlaceActivity" />
將「目前位置」活動新增至示範選單
和先前一樣,只要將目前的「地點」示範加入至 Demo
模組的清單中,即可將其新增至主畫面。現在您已經建立「目前地點」活動,請將其新增至「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 擴充功能。
- 正在載入地點詳細資料。
- 新增 Place Autocomplete。
- 取得目前地點。
後續步驟
- 探索或建立
android-places-demos
GitHub 存放區範例和示範,從中汲取更多靈感。 - 學習更多 Kotlin 程式碼研究室,瞭解如何透過 Google 地圖平台建構 Android 應用程式。
- 請回答下列問題,協助我們製作您覺得最實用的內容:
您還想查看其他程式碼研究室嗎?
您想要查看的程式碼研究室中並未列出嗎?請在這裡提出新的問題。