Activity Result API detailed explanation, it's time to give up startActivityForResult

This article is simultaneously published on my WeChat official account. You can follow by scanning the QR code at the bottom of the article or searching for Guo Lin on WeChat. Articles are updated every working day.

If you upgrade the appcompat library in your project to version 1.3.0 or higher, you will find that the startActivityForResult() method has been deprecated.

This method is believed to be used by all Android developers. It is mainly used to exchange data between two Activities.

So why is this so commonly used method deprecated? The official statement is that it is now more recommended to use the Activity Result API to implement the function of exchanging data between two Activities.

My personal opinion is that there is no fatal problem with the startActivityForResult() method, but the Activity Result API is better in terms of ease of use and interface uniformity. Since there is a better API, it is no longer recommended to use the old API in the past, so the startActivityForResult() method is marked as obsolete.

In fact, in addition to the startActivityForResult() method, methods like requestPermissions() are also marked as obsolete. It seems that there is no relationship between them, but in the Activity Result API, they are assigned to a unified API template. Therefore, we can use very similar code to exchange data between two Activities and request runtime permissions.

In addition, the usage of the Activity Result API is very simple, you can learn it as soon as you learn it. I believe that after reading this article, you can upgrade all relevant codes in your project to use the Activity Result API.

So let's get started.


Exchange data between two Activities

If you want to exchange data between two Activities, let's review the traditional way of writing first:

class FirstActivity : AppCompatActivity() {
    
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        firstButton.setOnClickListener {
    
    
            val intent = Intent(this, SecondActivity::class.java)
            startActivityForResult(intent, 1)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    
    
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
    
    
            1 -> {
    
    
                if (resultCode == RESULT_OK) {
    
    
                    val data = data?.getStringExtra("data")
                    // Handle data from SecondActivity
                }
            }
        }
    }
    
}

Here the startActivityForResult() method is called to request data from SecondActivity, and then the result returned by SecondActivity is parsed in the onActivityResult() method.

So what does the code in SecondActivity look like? Here we simply simulate it, just return a piece of data:

class SecondActivity : AppCompatActivity() {
    
    

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        val secondButton = findViewById<Button>(R.id.second_button)
        secondButton.setOnClickListener {
    
    
            val intent = Intent()
            intent.putExtra("data", "data from SecondActivity")
            setResult(RESULT_OK, intent)
            finish()
        }
    }

}

In this way, the function of FirstActivity requesting data from SecondActivity will work. Does it feel quite simple? So as I said just now, there is no fatal problem with the startActivityForResult() method.

Then let's learn how to use the Activity Result API to achieve the same function.

First of all, the code in SecondActivity does not need to be modified. This part of the code is not deprecated and the Activity Result API has nothing to do with it.

For the code in FirstActivity, we need to use the Activity Result API instead of startActivityForResult(), as shown below:

class FirstActivity : AppCompatActivity() {
    
    

    private val requestDataLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
    
     result ->
        if (result.resultCode == RESULT_OK) {
    
    
            val data = result.data?.getStringExtra("data")
            // Handle data from SecondActivity
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        firstButton.setOnClickListener {
    
    
            val intent = Intent(this, SecondActivity::class.java)
            requestDataLauncher.launch(intent)
        }
    }
    
}

Note the code changes here. We completely removed the override of the onActivityResult() method, and instead called the registerForActivityResult() method to register a listener for the Activity result.

The registerForActivityResult() method receives two parameters. The first parameter is a contract type. Since we want to request data from another Activity, we use the Contract StartActivityForResult here. The second parameter is a Lambda expression. When a result is returned, it will be called back here, and then we can get and process the data here.

The return value of the registerForActivityResult() method is an ActivityResultLauncher object, which has a launch() method that can be used to enable the Intent. In this way, we no longer need to call the startActivityForResult() method, but directly call the launch() method and pass in the Intent.

Which of these two writing styles is better? I personally feel that the writing method of the Activity Result API is simpler, but the overall advantage is not that great. The real strength of the Activity Result API lies in what we'll cover next.


Request runtime permission

In addition to the startActivityForResult() method, the requestPermissions() method is also deprecated. As for the reasons are the same, it is recommended to use the Activity Result API.

So how do you use the Activity Result API to request runtime permissions? Don't be surprised, it's surprisingly simple:

class FirstActivity : AppCompatActivity() {
    
    
    
    private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
    
     granted ->
        if (granted) {
    
    
            // User allow the permission.
        } else {
    
    
            // User deny the permission.
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        firstButton.setOnClickListener {
    
    
            requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
        }
    }
    
}

We only need to focus on the part of the code that changes.

Since this is a request for runtime permissions, we can no longer use the StartActivityForResult just now as the Contract, but use the RequestPermission Contract.

In addition, because different contracts are specified, the parameters of the Lambda expression will also change. Now the Lambda expression will pass in a Boolean parameter to tell us whether the user has allowed the permission we requested.

Finally, the parameters of the launch() method have also changed, and now it is only necessary to pass in the name of the permission to be requested.

Have you noticed that the templates of these two pieces of code are surprisingly consistent. We used two similar pieces of code to implement two functions that had little connection before. This is the benefit of the Activity Result API, which unifies the interfaces of some APIs and makes it very simple for us to implement specific functions.


Built-in Contract

Just now we experienced the two Contracts of StartActivityForResult and RequestPermission, which are used to exchange data between two Activities and request runtime permissions respectively. They are all built-in contracts in the Activity Result API.

So besides that, what other built-in contracts can we use?

The following are all the built-in contracts supported by the appcompat 1.3.0 version I listed, and new contracts may continue to be added in the future:

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

The naming of each Contract has clearly indicated what their functions are. That is to say, when we want to implement the functions contained in the above Contract, we don’t need to write it manually. The Activity Result API has already supported us. up.

For example, if I want to call a mobile phone camera to take a picture and get the Bitmap object of this picture, then I can use the Contract TakePicturePreview.

The implementation code is as follows:

class FirstActivity : AppCompatActivity() {
    
    

    private val takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicturePreview()) {
    
     bitmap ->
        // bitmap from camera
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        firstButton.setOnClickListener {
    
    
            takePictureLauncher.launch(null)
        }
    }

}

The code is very simple, just change the Contract type, and then the parameter of the Lambda expression will become a bitmap object. In addition, since the TakePicturePreview contract does not require input parameters, we can directly pass in null when we call the launch() method.

Seeing this, some readers may be more curious. How do I know what input parameters are required by each Contract, and what are the parameters returned in the Lambda expression?

This is very simple, just look at the source code of this Contract. For example, the source code of TakePicturePreview is as follows:

/**
 * An {@link ActivityResultContract} to
 * {@link MediaStore#ACTION_IMAGE_CAPTURE take small a picture} preview, returning it as a
 * {@link Bitmap}.
 * <p>
 * This can be extended to override {@link #createIntent} if you wish to pass additional
 * extras to the Intent created by {@code super.createIntent()}.
 */
public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap> {
    
    
    ...
}

We don't need to care about the specific implementation inside TakePicturePreview for now, just look at the generic type it specifies when inheriting the parent class. The first parameter is the required input parameter, and the second parameter is the output parameter returned by the Lambda expression.

As long as you master this little skill, you can easily use every kind of Contract. Then I won't do more demonstrations, and the usage of these contracts is waiting for you to explore by yourself.


Custom Contract

In addition to the above built-in contracts, we can indeed define our own contract types.

Although I don't think this necessity is very strong, because the built-in Contract can already help us deal with most scenarios.

However, custom Contract is not a complicated thing. Instead, it's pretty simple, so let's briefly mention it here.

Just now we probably saw the source code implementation of TakePicturePreview, which must inherit from the ActivityResultContract class, and specify the input parameters and output parameters of the current contract type through generics.

ActivityResultContract is an abstract class that defines two abstract methods inside it, as follows:

public abstract class ActivityResultContract<I, O> {
    
    

    public abstract @NonNull Intent createIntent(@NonNull Context context, I input);

    public abstract O parseResult(int resultCode, @Nullable Intent intent);
    ...
}

In other words, any Contract that inherits from ActivityResultContract needs to rewrite the createIntent() and parseResult() methods.

And the effect of these two methods is also very obvious. createIntent() is used to create an Intent, which will be used to initiate actions later, such as starting another Activity to obtain data, or turning on the camera to take pictures, etc. And parseResult() is used to parse the result of the response, and return the parsed result to the Lambda expression as an output parameter.

Each built-in Contract uses this rule to encapsulate its own logic.

So what kind of Contract do we want to customize for demonstration?

I thought for a while, when writing data exchange between two Activities just now, we need to start SecondActivity explicitly, and manually parse the data returned by SecondActivity from Intent, which is a little troublesome. This can be optimized with the help of a custom contract.

Create a new Contract called GetDataFromSecondActivity, the code is as follows:

class GetDataFromSecondActivity : ActivityResultContract<Void, String?>() {
    
    

    override fun createIntent(context: Context, input: Void?): Intent {
    
    
        return Intent(context, SecondActivity::class.java)
    }

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

We specify through generics that the input parameter of this Contract is Void, and the output parameter is a string.

Then in the createIntent() method, we manually created an Intent and set its purpose to open SecondActivity.

Finally, in the parseResult() method, we parse the result returned by SecondActivity and return the parsed string as an output parameter.

Such a custom contract is completed, and we use this contract to realize the initial function of exchanging data between two activities, which will become easier:

class FirstActivity : AppCompatActivity() {
    
    

    private val getDataLauncher = registerForActivityResult(GetDataFromSecondActivity()) {
    
     data ->
        // Handle data from SecondActivity
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        firstButton.setOnClickListener {
    
    
            getDataLauncher.launch(null)
        }
    }

}

It can be seen that with the help of the GetDataFromSecondActivity contract, we don't need to explicitly declare to start SecondActivity, and the launch() method can directly pass in null. In addition, we don't need to manually parse the data returned by SecondActivity, the parameters on the lambda expression are the parsed results.


last little question

At this point, we have basically learned all the content of the Activity Result API.

At the end of this article, I would like to answer one more small question. Because I had such doubts when I used the Activity Result API, I guess that some friends may have the same question, so I will answer it here.

Now you know that the Activity Result API can completely replace the startActivityForResult() method. But when we call the startActivityForResult() method, in addition to passing in the Intent, we also need to pass in a requestCode to distinguish between multiple tasks. For example, the following code:

class FirstActivity : AppCompatActivity() {
    
    

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        val secondButton = findViewById<Button>(R.id.second_button)
        firstButton.setOnClickListener {
    
    
            val intent = Intent(Intent.ACTION_VIEW)
            startActivityForResult(intent, 1)
        }
        secondButton.setOnClickListener {
    
    
            val intent = Intent(Intent.ACTION_DIAL)
            startActivityForResult(intent, 2)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    
    
        super.onActivityResult(requestCode, resultCode, data)
        when (requestCode) {
    
    
            1 -> {
    
    
                // Handle result for ACTION_VIEW
            }
            2 -> {
    
    
                // Handle result for ACTION_DIAL
            }
        }
    }

}

Here we call the startActivityForResult() method in two places, each of which is used to handle different tasks, so we need to set different requestCode for them.

In the onActivityResult() method, in order to distinguish which task the result came from, we need to judge the requestCode here.

This is the traditional way of writing when using the startActivityForResult() method before.

And the Activity Result API has no place for you to pass in the requestCode.

When I first came into contact with the Activity Result API, I was troubled by this problem due to the influence of thinking inertia. What should I do if there is no place to pass in the requestCode?

Later, after thinking about it, I found that the Activity Result API does not need such a thing as requestCode at all. We can use the following writing method to achieve exactly the same function as before:

class FirstActivity : AppCompatActivity() {
    
    

    private val actionViewLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
    
     result ->
        // Handle result for ACTION_VIEW
    }

    private val actionDialLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
    
     result ->
        // Handle result for ACTION_DIAL
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_first)
        val firstButton = findViewById<Button>(R.id.first_button)
        val secondButton = findViewById<Button>(R.id.second_button)
        firstButton.setOnClickListener {
    
    
            val intent = Intent(Intent.ACTION_VIEW)
            actionViewLauncher.launch(intent)
        }
        secondButton.setOnClickListener {
    
    
            val intent = Intent(Intent.ACTION_DIAL)
            actionDialLauncher.launch(intent)
        }
    }

}

It can also be seen from this that the design of the Activity Result API is more reasonable, and multiple tasks can be distinguished without the use of magic numbers such as requestCode.

Everything is simpler and clearer.


If you want to learn Kotlin and the latest Android knowledge, you can refer to my new book "The First Line of Code 3rd Edition" , click here to view details .


Pay attention to my technical public account, and high-quality technical articles will be pushed every day.

Scan the QR code below on WeChat to follow:

Guess you like

Origin blog.csdn.net/sinyu890807/article/details/121063078