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

Almost every developer used this API , it is mainly used to exchange data between two Activities.
Then why this API 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 nothing fatal about the startActivityForResult() API, 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 one, so the startActivityForResult()
API is marked as obsolete.
In fact, other APIs like requestPermissions()
are also marked as deprecated. It seems that there is no relationship between the two, but in the Activity Result API, they are attributed to a unified API template. Therefore, we can use very similar code to exchange data between two activities and request runtime permissions.
Also, the usage of the Activity Result API is very easy to use
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:
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
}
}
}
}
}
Code language: HTML, XML (xml)
Here, the startActivityForResult()
API is called to request data from SecondActivity
, and then the result returned by SecondActivity
is parsed in the onActivityResult()
API.
So what does the code in SecondActivity looks like? Here we simply just return a 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()
}
}
}
Code language: HTML, XML (xml)
Then let’s learn how to use the Activity Result API to achieve the same function.
First, 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 to replace the startActivityForResult()
, as follows:
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)
}
}
}
Code language: HTML, XML (xml)
Note the code changes here: We completely removed the onActivityResult()
API , instead we called registerForActivityResult()
to register a listener for the Activity result.
The registerForActivityResult()
API receives two parameters. The first parameter is a Contract type. Since we want to request data from another Activity, the Contract called StartActivityForResult
is used 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()
API is an ActivityResultLauncher
object, which has a launch()
API can be used to enable the Intent. In this way, we don’t need to call the startActivityForResult()
API, but directly call the launch() API and pass the Intent in.
Request runtime permissions
In addition to the startActivityForResult()
API, the requestPermissions()
API has also been deprecated. The reason is 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 will be 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)
}
}
}
Code language: HTML, XML (xml)
We only need to focus on the part where the code changes.
Since this time the runtime permission is requested, the StartActivityForResult
can no longer be used as the Contract, but the Contract called RequestPermission
.
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 that tells us whether the user has allowed the permission we requested.
Finally, the parameters of the launch() API have also changed, and now you only need 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 were barely related before. This is the benefit of the Activity Result API, which unifies the interfaces of some APIs, making it very simple for us to implement specific functions.
Built-in Contract
Just now we have experienced the two contracts StartActivityForResult
and RequestPermission
, which are used to exchange data between two activities and request runtime permissions respectively. They are all contracts built into the Activity Result API.
So in addition to this, what other built-in Contracts can we use?
Below is a list of all built-in contracts supported by appcompat version 1.3.0, and new contracts may be added in the future:
StartActivityForResult() StartIntentSenderForResult() RequestMultiplePermissions() RequestPermission() TakePicturePreview() TakePicture() TakeVideo() PickContact() GetContent() GetMultipleContents() OpenDocument() OpenMultipleDocuments() OpenDocumentTree() CreateDocument()
The name of each Contract has clearly indicated what their feature is
For example, if I want to call the mobile phone camera to take a picture and get the Bitmap object of this picture, I can use the TakePicturePreview Contract.
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)
}
}
}
Code language: HTML, XML (xml)
The code is very simple, just change the Contract type, and then the parameters of the Lambda expression will become bitmap objects. In addition, since the contract of TakePicturePreview does not require input parameters, we can directly pass in null when calling the launch() API.
Seeing this, some readers and friends may be more curious. How do I know what input parameters each Contract requires, 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> {
...
}
Code language: PHP (php)
No need to care about the specific implementation inside TakePicturePreview
for the time being, 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 each Contract
Custom Contract
In addition to the above built-in Contracts, we can indeed define our own Contract types.
Just now we roughly saw the source 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 APIs internally, 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);
...
}
Code language: PHP (php)
Any Contract that inherits from ActivityResultContract needs to override the two methods createIntent()
and parseResult()
.
The effect of these two APIs is also very obvious. createIntent()
is used to create an Intent, which will be used later to initiate actions, 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 as an output parameter to the Lambda expression.
Each built-in Contract uses this rule to encapsulate its own logic.
So what kind of Contract do we want to customize to demonstrate?
When writing the data exchange between the two Activity, we need to start SecondActivity explicitly, and manually parse the data returned by SecondActivity from the 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
}
}
Code language: HTML, XML (xml)
We specify through generics that the input parameter of this Contract is Void and the output parameter is a string.
Then in the createIntent() API, we manually create an Intent and set its purpose to open SecondActivity.
Finally, in the parseResult() API, 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 implement the original function of exchanging data between two Activities, which will become simple:
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)
}
}
}
Code language: HTML, XML (xml)
It can be seen that with the contract of GetDataFromSecondActivity, we do not need to explicitly declare to start SecondActivity, the launch() API 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 results of the parsing.
So how is requestCode
As you know, the Activity Result API can completely replace the startActivityForResult()
API. But when we call the startActivityForResult()
API, 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
}
}
}
}
Code language: HTML, XML (xml)
Here we have called the startActivityForResult() API in two places, and they are used to handle different tasks, so we need to set different requestCodes for them.
In the onActivityResult() API, in order to distinguish which task the result came from, we need to judge the requestCode here.
This is the traditional way to use the startActivityForResult() API.
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 confused by this problem due to the inertia of my thinking. What should I do if there is no place to pass in the requestCode?
Later, I turned around and found that the Activity Result API does not need such a thing as requestCode at all :
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)
}
}
}
Code language: HTML, XML (xml)
You can see 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.
One Reply to “Time to say good bye to startActivityForResult?”