Goodbye! onActivityResult! Hello, Activity Results API!

background

In Android application development, starting an activity is not necessarily a single operation. Obtaining data from the started activity is a common scenario. The most traditional way is to Intentcarry the data, and then use the startActivityForResultmethod to start the next activity, and then onActivityResultreceive the return through The data, the code is as follows:

  1. Call startActivityForResultmethod start
 startActivityForResult(intent,1)
  1. Realization onActivityResultmethod
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    
    
        super.onActivityResult(requestCode, resultCode, data)
        if(requestCode == 1 && resultCode == Activity.RESULT_OK){
    
    
            // 处理第二个页面带回的数据
        }
}

The above method onActivityResultcan obtain the data returned from the previous interface. This method is very useful, not only in the same application, but also from other applications. For example, we often call the system camera and photo album to get photos and get System address book, etc.

But there are some problems...

With the expansion of the application, the onActivityResultcallback methods are nested, coupled seriously, and difficult to maintain. The most common scenario is 调用系统相机相册获取照片了. The code might look like this:

class MainActivity : AppCompatActivity() {
    
    

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    
    
        if (resultCode == Activity.RESULT_OK) {
    
    
            when (requestCode) {
    
    
                REQUEST_PERMISSION -> {
    
    
                    // 处理权限
                }
                REQUEST_CAMERA -> {
    
    
                    // 相机获取图片结果
                }
                REQUEST_ALBUM -> {
    
    
                    // 相册获取图片结果
                }
                REQUEST_CROP -> {
    
    
                    // 系统裁剪
                }
            }
        }

        super.onActivityResult(requestCode, resultCode, data)
    }

    companion object {
    
    
        const val REQUEST_PERMISSION = 1001
        const val REQUEST_CAMERA = 1002
        const val REQUEST_ALBUM = 1003
        const val REQUEST_CROP = 1004
    }
}

Various processing results are coupled in the onActivityResultcallback, and a bunch of extra constants have to be defined REQUEST_CODEto determine which request is the callback result.

onActivityResult status?

Google may also be aware onActivityResultof these problems, androidx.activity:activity:1.2.0-alpha02
and androidx.fragment:fragment:1.3.0-alpha02has been abandoned startActivityForResultand onActivityResultmethods.

 /**
    * {@inheritDoc}
    *
    * @deprecated use
    * {@link #registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
    * passing in a {@link StartActivityForResult} object for the {@link ActivityResultContract}.
    */
   @Override
   @Deprecated
   public void startActivityForResult(@SuppressLint("UnknownNullness") Intent intent,
           int requestCode) {
    
    
       super.startActivityForResult(intent, requestCode);
   }
 /**
    * {@inheritDoc}
    *
    * @deprecated use
    * {@link #registerForActivityResult(ActivityResultContract, ActivityResultCallback)}
    * with the appropriate {@link ActivityResultContract} and handling the result in the
    * {@link ActivityResultCallback#onActivityResult(Object) callback}.
    */
   @CallSuper
   @Override
   @Deprecated
   protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    
    
       if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {
    
    
           super.onActivityResult(requestCode, resultCode, data);
       }
   }

As you can see, these two methods are marked as Deprecated, then these two methods are not recommended. What method does Google recommend to better obtain data from Activity? The answer isActivity Results API

Activity Results API

Activity Results API is Google’s officially recommended method for obtaining data from Activity and Fragment.

How to use Activity Results API? Compared onActivityResultwith what are the advantages? Next, I will answer for you one by one.

Before introducing how to use it, let me introduce two important components in the Activity Results API: ActivityResultContractand ActivityResultLauncher.

  • ActivityResultContract: Protocol, which defines how to transfer data and how to process the returned data. ActivityResultContractIs an abstract class, you need to extend it to create its own protocol, each ActivityResultContractneeds to define the input and output class, if you do not need any input, use the Void (in Kotlin, a Void? Or Unit) as the input type.

  • ActivityResultLauncher: Launcher, call ActivityResultLauncherthe launchmethod to start the page jump, the function is equivalent to the originalstartActivity()

Use the Activity Results API to pass data between activities
1.First, build.gradleadd dependencies under the app :
implementation 'androidx.activity:activity:1.2.0-beta01'
implementation 'androidx.fragment:fragment:1.3.0-beta01'
2. Define agreement

Create a new Contract class, inherit from ActivityResultContract<I,O>, where Iis the type of input and the type Oof output. Two methods need to be implemented, createIntentand the parseResultinput type is Iused as createIntentthe parameter, and the output type is Oused as parseResultthe return value of the method. In the following example, the input and output types are both String:

 class MyActivityResultContract: ActivityResultContract<String,String>(){
    
    
        override fun createIntent(context: Context, input: String?): Intent {
    
    
            return Intent(context,SecondActivity::class.java).apply {
    
    
                putExtra("name",input)
            }
        }

        override fun parseResult(resultCode: Int, intent: Intent?): String? {
    
    
            val data = intent?.getStringExtra("result")
            return if (resultCode == Activity.RESULT_OK && data != null) data
            else null
        }

    }

As in the above code, we createIntentcreated an Intent in the method and carried parameters name, and in the parseResultmethod, we obtained the returned data result.

3. Register the agreement, get the launcher-ActivityResultLauncher

Registration agreement, usage registerForActivityResultmethod, the method is provided by ComponentActivityor Fragment, accepts 2 parameters, the first parameter is the Contract protocol we defined, the second parameter is a callback ActivityResultCallback<O>, which Ois the output type of the previous Contract. code show as below:

private val myActivityLauncher = registerForActivityResult(MyActivityResultContract()){
    
    result ->
   Toast.makeText(applicationContext,result,Toast.LENGTH_SHORT).show()
   textView.text = "回传数据:$result"
}

The above code, registered MyActivityResultContract, registerForActivityResultthe return value of the method is ActivityResultLauncher, so we define a myActivityLaunchercallback method, which resultis the value returned from the previous interface. Here we simply use Toast to display.

4. Finally, call the launch method of the launcher to start the interface jump

MainActivityAdd a Button, when the Button is clicked, call the launchmethod to jump:

 button.setOnClickListener {
    
    
      // 开启页面跳转
      myActivityLauncher.launch("Hello,技术最TOP")
 }

SecondActivityThe code is simple:

class SecondActivity : AppCompatActivity(){
    
    

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.second_layout)

        val name = intent.getStringExtra("name")
        textView3.text = "接收到的数据为:$name"

        button2.setOnClickListener {
    
    
            val intent = Intent().apply {
    
    
                putExtra("result","Hello,依然范特西稀,我是回传的数据!")
            }
            setResult(Activity.RESULT_OK,intent)
            finish()
        }
    }
}

The above 3 steps realize the use of the new Activity Results APIto complete the data transfer between Activities and obtain the data returned by the Activity

Take a look at the effect:

Is this over?

You may have questions. Although it does reduce code coupling, it is not easy to use.

Indeed, this is not over! ! !

Pre-defined Contract

Everyone can see that the new Activity Results APIone seems a bit cumbersome to use, so you have to define Contract every time. Google must have considered this problem, so Google has pre-defined a lot of contracts, basically think of the use scenarios you can think of, they are all defined in the class ActivityResultContracts, there are the following contracts:

StartActivityForResult() 
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
CreateDocument()
OpenDocumentTree()
OpenMultipleDocuments()
OpenDocument()
GetMultipleContents()
GetContent()

Here are some of these Contracts:

  • StartActivityForResult: General Contract, without any conversion, Intent as input, ActivityResult as output, this is also the most commonly used contract.

  • RequestMultiplePermissions: Used to request a set of permissions

  • RequestPermission: Used to request a single permission

  • TakePicturePreview: Call to MediaStore.ACTION_IMAGE_CAPTUREtake a picture, the return value is Bitmap picture

  • TakePicture: Call to MediaStore.ACTION_IMAGE_CAPTUREtake a picture and save the picture to the given Uri address, return true to indicate that the save is successful.

  • TakeVideo: Call to MediaStore.ACTION_VIDEO_CAPTUREtake a video, save it to the given Uri address, and return a thumbnail.

  • PickContact: Get contacts from the address book app

  • GetContent: Prompt to select a piece of content and return a ContentResolver#openInputStream(Uri)Uri address (content:// form) by accessing native data. By default, it is added Intent#CATEGORY_OPENABLE, returning content that can represent the stream.

  • CreateDocument: Prompt the user to select a document and return a Uri starting with (file:/http:/content:).

  • OpenMultipleDocuments: Prompt the user to select documents (multiple selections are possible), and return their Uri respectively in the form of List.

  • OpenDocumentTree: Prompt the user to select a directory, and return the one selected by the user as a Uri. The application can fully manage the documents in the returned directory.

Among the above predefined Contracts, in addition to StartActivityForResultand RequestMultiplePermissions, they basically deal with scenarios that interact with other apps and return data, such as taking pictures, selecting pictures, selecting contacts, opening documents, and so on. Use the most is the StartActivityForResultand RequestMultiplePermissionsthe.

With these predefined Contracts, it is much easier to transfer data between activities. For example, the previous example can be simplified to this:

1. 注册协议,获取ActivityResultLauncher:

 private val myActivityLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
    
     activityResult ->  
        if(activityResult.resultCode == Activity.RESULT_OK){
    
    
            val result = activityResult.data?.getStringExtra("result")
            Toast.makeText(applicationContext,result,Toast.LENGTH_SHORT).show()
            textView.text = "回传数据:$result"
        }
    }

2. 构造需要传递的数据,启动页面跳转

 button.setOnClickListener {
    
    
        val  intent = Intent(this,SecondActivity::class.java).apply {
    
    
             putExtra("name","Hello,技术最TOP")
        }
        myActivityLauncher.launch(intent)
}

OK, it's that simple! ! !

For example, our permission, application, please see the code:

request_permission.setOnClickListener {
    
    
    requestPermission.launch(permission.BLUETOOTH)
}

request_multiple_permission.setOnClickListener {
    
    
    requestMultiplePermissions.launch(
        arrayOf(
            permission.BLUETOOTH,
            permission.NFC,
            permission.ACCESS_FINE_LOCATION
        )
    )
}

// 请求单个权限
private val requestPermission =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) {
    
     isGranted ->
        // Do something if permission granted
        if (isGranted) toast("Permission is granted")
        else toast("Permission is denied")
    }

// 请求一组权限
private val requestMultiplePermissions =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
    
     permissions : Map<String, Boolean> ->
        // Do something if some permissions granted or denied
        permissions.entries.forEach {
    
    
            // Do checking here
        }                                                                             
}

With this, we can abandon all third-party permission request frameworks, just put these two Contracts in BaseActivity, or extract them into a separate class, and then apply for permissions anytime, anywhere. Is it very convenient! ! !

Receive the result of Activity in a class other than Activity/Fragment

In the Activity and Fragment, we can directly use the registerForActivityResultAPI, it is because ConponentActivityand Fragmentbase class implements ActivityResultCalleran interface, non-Activity / Fragment, if we want to receive Activity return data can be used directly ActivityResultRegistryto achieve.

For example, use a separate class to implement protocol registration and starter startup:

    class MyLifecycleObserver(private val registry : ActivityResultRegistry)
            : DefaultLifecycleObserver {
    
    
        lateinit var getContent : ActivityResultLauncher<String>

        fun onCreate(owner: LifecycleOwner) {
    
    
            getContent = registry.register("key", owner, GetContent()) {
    
     uri ->
                // Handle the returned Uri
            }
        }

        fun selectImage() {
    
    
            getContent("image/*")
        }
    }

    class MyFragment : Fragment() {
    
    
        lateinit var observer : MyLifecycleObserver

        override fun onCreate(savedInstanceState: Bundle?) {
    
    
            // ...

            observer = MyLifecycleObserver(requireActivity().activityResultRegistry)
            lifecycle.addObserver(observer)
        }

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    
    
            val selectButton = view.findViewById<Button>(R.id.select_button)

            selectButton.setOnClickListener {
    
    
                // Open the activity to select an image
                observer.selectImage()
            }
        }
    }

In the example, we MyLifecycleObserverimplement protocol registration and launcher startup in, why do we need to implement LifecycleObserverit? Because the life cycle components, LifecycleOwnerwill Lifecycleautomatically remove the registered launcher is destroyed. However, if LifecycleOwner does not exist, each ActivityResultLauncherclass allows you to manually invoke unregister()instead. But when using ActivityResultRegistry, Google officially strongly recommends that we use LifecycleOwnerAPIs that are acceptable as parameters.

Why don't you need to manually call them in Activity and Fragment unregister()? , Because ComponentActivityand Fragment have been implemented LifecycleObserver.

ComponentActivityThe source code is here:

The source code in Fragment is here:

to sum up

The new Activity Result API provides an easy way to perform many common tasks, such as calling third-party apps to obtain data, request permissions, take photos, select pictures, obtain contacts, and so on. In addition, it reduces the coupling of the code and reduces the boilerplate code (for example, defining the requestCode constant). In addition, startActivityForResultand onActivityResulthas been abandoned, the official also strongly recommends using this method to transfer data and obtain data returned by Activity.

If you haven't used it yet, use it as soon as possible, thief incense! ! ! Happy coding everyone! ! !

Guess you like

Origin blog.csdn.net/zwluoyuxi/article/details/109285162