概述

早期我們會用 startActivityForResult() 配合 onActivityResult() 來取得其他 Activity 的結果,但是這種方式有很多的缺點,例如:耦合嚴重、要定義 RequestCode 常數、難以維護…等,所以 Google 推出了新的 Activity Result API 來改善這狀況。

Activity Result API 的主要元件

ActivityResultContract

這是一個抽象類別,我們必須繼承它來定義如何傳遞資料及處理回傳資料 (也就是輸入和輸出),但是 Google 已經幫我們定義好一些常用的類別,通常我們可以直接拿來使用,例如:

ActivityResultContracts.TakePicture 使用相機取得影像

從原始碼可以知道輸入型別為 Uri,輸出(回傳) 的型別為 Boolean

    open class TakePicture : ActivityResultContract<Uri, Boolean>() {
        @CallSuper
        override fun createIntent(context: Context, input: Uri): Intent {
            return Intent(MediaStore.ACTION_IMAGE_CAPTURE)
                .putExtra(MediaStore.EXTRA_OUTPUT, input)
        }

        final override fun getSynchronousResult(
            context: Context,
            input: Uri
        ): SynchronousResult<Boolean>? = null

        @Suppress("AutoBoxing")
        final override fun parseResult(resultCode: Int, intent: Intent?): Boolean {
            return resultCode == Activity.RESULT_OK
        }
    }

ActivityResultContracts.RequestPermission 運行時請求權限

從原始碼可以知道輸入型別為 String,輸出(回傳) 的型別為 Boolean

    public static final class RequestPermission extends ActivityResultContract<String, Boolean> {

        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, @NonNull String input) {
            return RequestMultiplePermissions.createIntent(new String[] { input });
        }

        @NonNull
        @Override
        public Boolean parseResult(int resultCode, @Nullable Intent intent) {
            if (intent == null || resultCode != Activity.RESULT_OK) return false;
            int[] grantResults = intent.getIntArrayExtra(EXTRA_PERMISSION_GRANT_RESULTS);
            if (grantResults == null || grantResults.length == 0) return false;
            return grantResults[0] == PackageManager.PERMISSION_GRANTED;
        }

        @Override
        public @Nullable SynchronousResult<Boolean> getSynchronousResult(
                @NonNull Context context, @Nullable String input) {
            if (input == null) {
                return new SynchronousResult<>(false);
            } else if (ContextCompat.checkSelfPermission(context, input)
                    == PackageManager.PERMISSION_GRANTED) {
                return new SynchronousResult<>(true);
            } else {
                // proceed with permission request
                return null;
            }
        }
    }

ActivityResultLauncher

這是啟動器類別,其 launch() 方法可以用來啟動頁面的轉換,相當於本來的 startActivityForResult()。這裏有一點要注意,啟動器的初始化必須在 onCreate() 時處理,否則會丟出錯誤。

程式碼範例

以要求讀取聯絡人的權限為例

在 AndroidManifest.xml 中加入權限宣告

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.kirin.android.resultapidemo">

    <uses-permission android:name="android.permission.READ_CONTACTS" />

    ...

MainActivity.kt 完整程式碼

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val permissionLauncher = registerForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) { result->
            if(result) {
                Toast.makeText(this, "授權成功", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "您必須同意授權才能繼續使用", Toast.LENGTH_SHORT).show()
            }
        }
        
        permissionLauncher.launch(android.Manifest.permission.READ_CONTACTS)
    }
}

執行 app 就可以看到請求權限的提示。

參考資料

Google developers: Getting a result from an activity

再見!onActivityResult!你好,Activity Results API!

Last modified: 2022 年 10 月 15 日

Author

Comments

Write a Reply or Comment

Your email address will not be published.