background

In Android application development, starting an Activity is not necessarily a single operation. It is common to retrieve data from an Activity that is started. The most traditional way is to carry data with an Intent. Then use the startActivityForResult method to start the next Activity, and use onActivityResult to receive the returned result as follows:

  1. callstartActivityForResultMethods to start the
 startActivityForResult(intent,1)
Copy the code
  1. implementationonActivityResultmethods
override fun onActivityResult(requestCode: Int, resultCode: Int.data: Intent?). {
        super.onActivityResult(requestCode, resultCode, data)
        if(resultCode == 1 && resultCode == Activity.RESULT_OK){
            // Process the data from the second page}}Copy the code

In this way, onActivityResult can retrieve data from the previous interface. This method is very useful, not only in the same application, but also from other applications. For example, we commonly call the system camera, album to obtain photos, obtain the system address book, etc.

But there are some problems…

As the application grew, the onActivityResult callback methods became heavily nested, coupled, and difficult to maintain. The most common scenario is to call the system camera album to get photos. The code might look something 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 -> {
                    // Process permission
                }
                REQUEST_CAMERA -> {
                    // The camera gets the result of the image
                }
                REQUEST_ALBUM -> {
                    // get the result of the photo
                }
                REQUEST_CROP -> {
                    // System clipping}}}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}}Copy the code

The various processing results are coupled to the onActivityResult callback, and you have to define a bunch of extra constants in REQUEST_CODE to determine which request callback results.

OnActivityResult status?

Google is probably aware of these issues with onActivityResult, In androidx. Activity: the activity: 1.2.0 – alpha02 and androidx fragments, fragments: 1.3.0 – alpha02, The startActivityForResult and onActivityResult methods have been deprecated.

 / * * * {@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);
   }
Copy the code
 / * * * {@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); }}Copy the code

As you can see, both methods are marked as Deprecated, so these two methods are not recommended. What is a better way to retrieve data from an Activity? The answer is the Activity Results API

Activity Results API

The Activity Results API is officially recommended by Google for activities and fragments to obtain data.

How to use the Activity Results API? What are the advantages over onActivityResult? Next, will answer for you one by one.

Before I show you how to use it, I’ll introduce you to two important components of the Activity Results API: ActivityResultContract and ActivityResultLauncher.

  • ActivityResultContract: A protocol that defines how data is passed and how returned data is processed. ActivityResultContract is an abstract class that you need to inherit to create your own protocol. Each ActivityResultContract needs to define input and output classes. If you don’t need any input, use Void (in Kotlin, Use the Void? Or Unit) as the input type.

  • ActivityResultLauncher: a launcher that calls the launch method of ActivityResultLauncher to launch a page jump, acting as the original startActivity()

Use the Activity Results API to pass data between activities
1.First of all, under appbuild.gradleAdd a dependency to:
Implementation 'androidx. Activity: the activity: 1.2.0 - beta01' implementation 'androidx. Fragments: fragments: 1.3.0 - beta01'Copy the code
2. Define the agreement

Create a New Contract class that inherits from ActivityResultContract

, where I is the input type and O is the output type. You need to implement two methods: createIntent and parseResult. The input type is “I” as the parameter of createIntent and the output type is “O” as the return value of parseResult. In the following example, the input and output types are “String” :
,o>

 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}}Copy the code

In the above code, we create the Intent in the createIntent method with the parameter name. In the parseResult method, we get the returned data result.

3. Register the protocol and get the initiator -ActivityResultLauncher

The registration agreement, the use of registerForActivityResult method, the method provided by the ComponentActivity or fragments, accepts two parameters, the first parameter is the Contract agreement, we define The second argument is a callback ActivityResultCallback

, where O is the output type of the previous Contract. The code is as follows:

private val myActivityLauncher = registerForActivityResult(MyActivityResultContract()){result ->
   Toast.makeText(applicationContext,result,Toast.LENGTH_SHORT).show()
   textView.text = "Return data:$result"
}
Copy the code

In the code above, RegisterForActivityResult method is the return value is registered the MyActivityResultContract ActivityResultLauncher, so we defined a myActivityLauncher, correction method, Result is the value returned from the previous screen. Here we simply use Toast.

4.Finally, call the launch method of the initiator to initiate the interface jump

Add a Button to the MainActivity. When clicking the Button, call the launch method to jump:

 button.setOnClickListener {
      // The page is displayed
      myActivityLauncher.launch("Hello, TOP technology")}Copy the code

The code for SecondActivity 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 = "The data received is:$name"

        button2.setOnClickListener {
            val intent = Intent().apply {
                putExtra("result"."Hello, still fantasy, this is the returned data!")
            }
            setResult(Activity.RESULT_OK,intent)
            finish()
        }
    }
}
Copy the code

In the three steps above, you can use the new Activity Results API to pass data between activities and retrieve the data returned by activities

Take a look at the results:

Is that it?

You might wonder, although it does reduce code coupling, it’s not easy to use.

Yes, but it’s not over!!

Predefined Contract

As you can see, the new Activity Results API is a bit cumbersome to use, and you have to define a Contract every time. Google certainly has this in mind, so Google has predefined a lot of contracts, which are defined in the class ActivityResultContracts, and they are defined in the class ActivityResultContracts:

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

These contracts are introduced as follows:

  • StartActivityForResult: A generic Contract that does no conversion, with the Intent as the input and ActivityResult as the output. This is the most common Contract.

  • RequestMultiplePermissions: used to request a set of permissions

  • RequestPermission: Used to request a single permission

  • TakePicturePreview: Call mediastore. ACTION_IMAGE_CAPTURE to take a picture and return a Bitmap image

  • ACTION_IMAGE_CAPTURE: TakePicture: call mediastore.action_image_capture to take a picture and save the image to the given Uri address. Return true to save the picture successfully.

  • TakeVideo: Call mediastore. ACTION_VIDEO_CAPTURE to capture the video, save it to the given Uri address, and return a thumbnail image.

  • PickContact: Retrieves a contact from the Contacts APP

  • GetContent: prompts you to select a content and returns a Uri address (content://) that accesses the native data via ContentResolver#openInputStream(Uri). By default, it adds Intent#CATEGORY_OPENABLE, which returns the content that can represent the stream.

  • CreateDocument: Prompts the user to select a document and returns a Uri starting with file:/ HTTP :/content:.

  • OpenMultipleDocuments: Prompts the user to select documents (you can select multiple documents) and returns their URIs as a List.

  • OpenDocumentTree: Prompts the user to select a directory and returns the selected one as a Uri. The application can fully manage the documents in the returned directory.

Above the predefined Contract, besides StartActivityForResult and RequestMultiplePermissions, basic it is processed with other APP interaction, return data scenarios, for example, pictures, picture, select contacts, open the document, and so on. The most used is the StartActivityForResult and RequestMultiplePermissions.

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

1. Register the protocol and get 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 = "Return data:$result"}}Copy the code

2. Create the data to be transferred and start the page redirect

 button.setOnClickListener {
        val  intent = Intent(this,SecondActivity::class.java).apply {
             putExtra("name"."Hello, TOP technology")
        }
        myActivityLauncher.launch(intent)
}
Copy the code

OK, it’s that simple!!

For example, our permissions, apply, 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
        )
    )
}

// Request a single permission
private val requestPermission =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        // Do something if permission granted
        if (isGranted) toast("Permission is granted")
        else toast("Permission is denied")}// Request a set of permissions
private val requestMultiplePermissions =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions : Map<String, Boolean> ->
        // Do something if some permissions granted or denied
        permissions.entries.forEach {
            // Do checking here}}Copy the code

With this, we can discard all third party permission request frameworks and simply put these two contracts into BaseActivity or extract them into a separate class to apply for permissions anytime, anywhere. Isn’t it convenient!!

Receive the result of an Activity in a class that is not an Activity/Fragment

In the Activity and fragments, we can directly use registerForActivityResultAPI, That’s because the ConponentActivity and Fragment base classes implement the ActivityResultCaller interface. In non-Activities/Fragments, if we want to receive data from an Activity, You can do this directly using ActivityResultRegistry.

For example, a separate class is used to register protocols and start initiators:

    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(a) {
            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()
            }
        }
    }
Copy the code

In the example, we implemented protocol registration and launcher startup in MyLifecycleObserver. Why implement LifecycleObserver? Because, with the Lifecycle component, LifecycleOwner automatically removes the registered initiator when Lifecycle is destroyed. However, if LifecycleOwner doesn’t exist, each ActivityResultLauncher class lets you manually call unregister() instead. However, when using ActivityResultRegistry, Google officials strongly recommend that we use an API that accepts LifecycleOwner as a parameter.

Why not call unregister() manually in activities and fragments? Because ComponentActivity and Fragment already implement LifecycleObserver.

ComponentActivity source code here:

Fragment source code:

conclusion

The new Activity Result API provides an easy way to perform many common tasks, such as calling third-party apps to get data, requesting permissions, taking photos, selecting images, retrieving contacts, and more. In addition, there is less coupling of code and less boilerplate code (for example, defining requestCode constants). In addition, startActivityForResult and onActivityResult have been deprecated, and it is strongly recommended to use this method for data transfer and retrieving data returned by activities.

Have not used up, quickly use up, thief sweet!! Have fun coding!!

The article was first published on the public account: “Technology TOP”, there are dry articles updated every day, you can search wechat “technology TOP” first time to read, reply [mind map] [interview] [resume] yes, I prepare some Android advanced route, interview guidance and resume template for you