# Android筆記--Activity Result API
>[!Note]前置知識:
>- [Android筆記–startActivityForResult()(已廢棄,方法)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_startActivityForResult)
>- [Kotlin筆記--Intent類別(意圖)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_intent)
Activity Result API 是 Android 現代化的 Activity 間通訊解決方案,用來處理 Activity 之間的啟動與結果回傳。它的主要==目的是取代並簡化舊的 <code>startActivityForResult()</code> 與 <code>onActivityResult()</code>,讓程式碼更清晰、可維護==。
<b><u>相較於傳統作法,Activity Result API的優點</u></b>:
- 結構更直觀 —— 結果處理與啟動邏輯綁定在一起。
- 更安全 —— 避免因 requestCode 錯誤造成混亂。
- 更符合現代架構 —— 與 Fragment、Lifecycle、Jetpack 相容性更佳。
過去必須透過「發送請求 → 等回傳 → 在 onActivityResult() 判斷」的方式來處理結果;
而使用 Activity Result API 時,只要先登記一個「結果接收器」,系統就會自動把結果送回該接收器,流程更簡潔、不易出錯。
Activity Result API 的 callback 註冊和觸發機制,實質上就是一種 觀察者模式的應用,Activity/Fragment 註冊一個「觀察者」,等待結果(狀態改變)通知它
---
Activity Result API基本使用方法(跳轉並在頁面間傳遞資料):
---
<details><summary><font size=4>步驟1.設計一個<code>ActivityResultLauncher</code>物件,註冊他並決定要執行的行為</font></summary>
```kotlin=
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// registerForActivityResult() 必須在 onCreate/onViewCreated 期間呼叫。
activityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result ->
// 處理回傳(CallBack)的結果
if(result.resultCode == RESULT_OK){
// 我們使用的合約是StartActivityForResult(),因此回傳的會是一個intent,所以我們的Callback處理可以使用intent的相關操作。
val resultCode = result.data?.getIntExtra("resultCode", -1)
Toast.makeText(this, "ResultCode: $resultCode", Toast.LENGTH_SHORT).show()
}
}
setContentView(binding.root)
}
```
- <span id="registerForActivityResult()"><b><u><code>registerForActivityResult()</code></u></b>: 註冊一個啟動器(Launcher),並處理回傳結果,需要填入一個「合約」作為參數,會返回一個<code>ActivityResultLauncher<T> Class</code>的物件。</span>
- T的類型根據不同的合約而改變。[參見](#kind_of_contracts)。
- 同時具有回傳值跟CallBack。(延伸: [甚麼是CallBack?](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/kotlin_callBack))
- ==<code>registerForActivityResult()</code>必須在 onCreate/onViewCreated 期間呼叫==。
- 所有的 registerForActivityResult() 回傳的 ActivityResultLauncher<T> 都自帶一個 launch()方法,==我們透過這個 launch() 啟動目標操作並觸發回呼來處理回傳結果==。
- <span id="ActivityResultContracts"><b><u>ActivityResultContracts</u></b></span>: 定義不同操作的「合約(Contract)」,告訴 Launcher 「我這次要做什麼操作」以及「結果會長什麼樣子」,有各種不同的合約。
<div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;">
<details><summary>各種的ActivityResultContracts</summary>
<span id="kind_of_contracts">
| Contract | 輸入類型 `T` | 使用情境 |
| -------------------------- | ------------- | --------------------- |
| StartActivityForResult | Intent | 啟動另一個 Activity,傳遞資料 |
| RequestPermission。[[參考](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_DangerousPermissions#in_activity_result_api_way)] | String | 單一權限請求(如 CAMERA) |
| RequestMultiplePermissions。[[參考](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/android_bluetooth_requestPermission#permission_request)] | Array<String> | 同時請求多個權限 |
| GetContent | String | 選擇檔案或圖片,傳入 MIME 類型 |
| TakePicturePreview | Unit | 直接拍照並回傳 Bitmap,不需額外參數 |
</span>
</details>
</div>
- <b><u><code>StartActivityForResult()</code></u></b>: ActivityResultContracts的其中一種合約,用來向<code>registerForActivityResult()</code>表示:「這次我要啟動另一個 Activity,並在結束後接收回傳結果」。
- <span id="callBack"><b><u><code>if(result.resultCode == RESULT_OK){...}</code></u></b></span>: 在這裡處理[回呼(callBack)](https://hackmd.io/@9YAtszqXS2OLNZOrLY_-Jg/kotlin_callBack)結果。
- <code id="result_resultCode">result.resultCode</code>: 由我們自己在目標Activity設計的回傳值
- <div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><details><summary>關於ResultCode</summary>
<span id="aboutResultCode">
ResultCode是Activity自帶的常量,代表各種回呼結果。
- RESULT_OK: 代表成功,代表值為<code>-1</code>。
- RESULT_CANCELED: 表示操作取消或失敗,代表值為<code>0</code>
- RESULT_FIRST_USER: 官方保留的自訂ResultCode的起始值,代表值為<code>1</code>
```kotlin=
// 範例,當我們需要更多種類的自訂起始值的話,會這樣寫
val RESULT_ADD = RESULT_FIRST_USER + 1 // 代表值為2
val RESULT_DELETE = RESULT_FIRST_USER + 2 // 代表值為3
val RESULT_EDIT = RESULT_FIRST_USER + 3 // 代表值為4
```
</span>
</details>
</div>
- <div style="border-left: 4px solid #1565C0;background-color: #0D1B2A;padding: 12px 16px;margin: 10px 0;border-radius: 4px;font-family: Arial, sans-serif;"><details><summary>更貼近實作的回乎範例</summary>
```kotlin=
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// registerForActivityResult() 必須在 onCreate/onViewCreated 期間呼叫。
activityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result ->
// 處理回傳的結果
when(result.resultCode){
RESULT_OK -> {
if(result.data != null){
// 處理成功且有夾帶資料的回乎結果
Toast.makeText(this, "ResultCode: ${result.resultCode}", Toast.LENGTH_SHORT).show()
}else{
// 處理成功但沒有夾帶資料的回乎結果
Toast.makeText(this, "ResultCode: ${result.resultCode}", Toast.LENGTH_SHORT).show()
}
}
RESULT_CANCELED -> {
// 處理失敗
Toast.makeText(this, "ResultCode: ${result.resultCode}", Toast.LENGTH_SHORT).show() }
else -> { Toast.makeText(this, "ResultCode: ${result.resultCode}", Toast.LENGTH_SHORT).show() }
}
}
binding.btnJump.setOnClickListener {
val intent = Intent(this, Activity2::class.java)
activityResultLauncher.launch(intent)
}
setContentView(binding.root)
}
```
</details>
</div>
</details>
<details><summary><font size=4>步驟2.設計一個intent物件,然後使用ActivityResultLauncher中的<code>launcher()</code>來執行註冊時所定義的行為</font></summary>
```kotlin=
binding.btnJump.setOnClickListener {
val intent = Intent(this, Activity2::class.java)
activityResultLauncher.launch(intent)
}
```
- <b><u><code>activityResultLauncher.launch()</code></u></b>: 屬於[registerForActivityResult()](#registerForActivityResult())回傳的<code>ActivityResultLauncher<T> Class</code>物件的方法。
- <code>ActivityResultLauncher<T> Class</code>的物件==自帶一個<code>launch()</code>方法,用來啟動註冊時指定的合約==
- <code>launch()</code>內部==需要填入一個和ActivityResultContractsc合約相對應的參數==。例如:
- 選 StartActivityForResult() → 填入 Intent
- 選 RequestPermission() → 填入要申請的權限名稱(String)
- 其他 Contract 也會有對應輸入類型([參考](#kind_of_contracts))
- 呼叫<code id="launch()">launch()</code>後,系統會執行對應合約的操作;若合約是有回傳結果的類型(例如: <code>StartActivityForResult()</code>),會在[先前註冊的 callback 中](#callBack)處理結果。
</details>
<details><summary><font size=4>步驟3.跳轉到步驟2中的intent物件所設定的目標activity,最後執行<code>finish()</code>返回原本的activity</font></summary>
```kotlin=
fun jumpAndCallBack_success_withIntent(){
// 用apply{]來設計,方便擴充
val intent = Intent().apply {
putExtra("data", "成功並回傳資料")
putExtra("dataA", "ResultCodeA")
putExtra("dataB", "ResultCodeB")
}
setResult(RESULT_OK, intent) // ResultCode = RESULT_OK,帶資料
finish() // 結束 ActivityB
}
fun jumpAndCallBack_success_withoutIntent(){
setResult(RESULT_OK) // ResultCode = RESULT_OK,不帶資料
finish() // 結束 ActivityB
}
```
- <span id="setResult()"><b><u><code>setResult()</code></u></b></span>: 告訴原始 Activity「我完成了什麼事情,<span id="forResult">結果如何([resultCode](#aboutResultCode))</span>」,可決定是否要帶資料(intent)回去。要填兩個參數,resultCode跟intent,這邊作為參數填入的resultCode會變為[callBack中的result的resultCode屬性](#result_resultCode)
- <span id="finish()"><b><u><code>finish()</code></u></b>: 關閉當前 Activity,返回到上一個 Activity,此方法隸屬於Activity</span>
在執行了<code>finish()</code>之後,ActivityA 會收到我們用<code>setResult()</code>回傳的resultCode和資料,並執行我們在 [ActivityResultLauncher 的 callback](#callBack) 裡寫好的對應動作。
</details>
---
總結
---
<b><u>如果將Activity Result API比喻成送信: </u></b>
- [registerForActivityResult()](#registerForActivityResult()) → 郵局:先註冊一個信箱,告訴系統我想收到誰寄來的信,以及要怎麼處理。
- [ActivityResultContracts](#ActivityResultContracts) → 郵局提供的服務類型:普通掛號、快遞、包裹,每種服務決定信件怎麼寄送。
- [launch()](#launch()) → 按下「發送確認鍵」:把信(Intent)寄出去,郵局開始運作。
- [Callback](#callBack) → 預先約定的回信的收件方式:郵局收到回信後,按照你事先設定的流程處理(更新 UI、顯示 Toast 等)。
- [setResult()](#setResult()) → 收件方寫回信:放入結果(ResultCode)和資料(Intent)。
- [resultCode](#forResult) → 顧客滿意度:OK / CANCELED 或自訂,告訴寄信者回信的狀態。
- [finish()](#finish()) → 收件方按下「寄出回信鍵」:結束自己的工作,把回信送回給寄件方的郵局(觸發 callback)。
---
GitHub專案
---
[MyPratice_Activity_result_api](https://github.com/PudCheetah/MyPratice_Activity_result_api)
---
參考資料:
---
- [什麼!startActivityForResult 被標記棄用?](https://ithelp.ithome.com.tw/articles/10265581?sc=iThomeR)
- [110/02 - 只有 StartActivityForResult 可以用嗎?](https://ithelp.ithome.com.tw/articles/10266045)
- [Android Developers取得活動結果](https://developer.android.com/training/basics/intents/result?hl=zh-tw)
- [Activity Result API详解,是时候放弃startActivityForResult了](https://blog.csdn.net/guolin_blog/article/details/121063078)
- [碼農日常-『Android studio』啟動Activity並獲取返回的結果-registerForActivityResult](https://thumbb13555.pixnet.net/blog/post/337551269-registerforactivityr)
- [再见!onActivityResult!你好,Activity Results API!](https://juejin.cn/post/6887743061309587463)
- [优雅地封装 Activity Result API,完美地替代 startActivityForResult()](https://juejin.cn/post/6987575150283587592)
- [Activity Result API 詳解](https://www.cnblogs.com/imnotGemini/p/16029465.html)
- [在kotlin中探索Activity Result API極簡的解決方案](https://blog.csdn.net/qq_39312146/article/details/129218541)