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 Intent
carry the data, and then use the startActivityForResult
method to start the next activity, and then onActivityResult
receive the return through The data, the code is as follows:
- Call
startActivityForResult
method start
startActivityForResult(intent,1)
- Realization
onActivityResult
method
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == 1 && resultCode == Activity.RESULT_OK){
// 处理第二个页面带回的数据
}
}
The above method onActivityResult
can 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 onActivityResult
callback 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 onActivityResult
callback, and a bunch of extra constants have to be defined REQUEST_CODE
to determine which request is the callback result.
onActivityResult status?
Google may also be aware onActivityResult
of these problems, androidx.activity:activity:1.2.0-alpha02
and androidx.fragment:fragment:1.3.0-alpha02
has been abandoned startActivityForResult
and onActivityResult
methods.
/**
* {@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 onActivityResult
with 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: ActivityResultContract
and ActivityResultLauncher
.
-
ActivityResultContract
: Protocol, which defines how to transfer data and how to process the returned data.ActivityResultContract
Is an abstract class, you need to extend it to create its own protocol, eachActivityResultContract
needs 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, callActivityResultLauncher
thelaunch
method 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.gradle
add 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 I
is the type of input and the type O
of output. Two methods need to be implemented, createIntent
and the parseResult
input type is I
used as createIntent
the parameter, and the output type is O
used as parseResult
the 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 createIntent
created an Intent in the method and carried parameters name
, and in the parseResult
method, we obtained the returned data result
.
3.
Register the agreement, get the launcher-ActivityResultLauncher
Registration agreement, usage registerForActivityResult
method, the method is provided by ComponentActivity
or Fragment
, accepts 2 parameters, the first parameter is the Contract protocol we defined, the second parameter is a callback ActivityResultCallback<O>
, which O
is 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
, registerForActivityResult
the return value of the method is ActivityResultLauncher
, so we define a myActivityLauncher
callback method, which result
is 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
MainActivity
Add a Button, when the Button is clicked, call the launch
method to jump:
button.setOnClickListener {
// 开启页面跳转
myActivityLauncher.launch("Hello,技术最TOP")
}
SecondActivity
The 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 API
to 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 API
one 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 toMediaStore.ACTION_IMAGE_CAPTURE
take a picture, the return value is Bitmap picture -
TakePicture
: Call toMediaStore.ACTION_IMAGE_CAPTURE
take a picture and save the picture to the given Uri address, return true to indicate that the save is successful. -
TakeVideo
: Call toMediaStore.ACTION_VIDEO_CAPTURE
take 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 aContentResolver#openInputStream(Uri)
Uri address (content:// form) by accessing native data. By default, it is addedIntent#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 StartActivityForResult
and 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 StartActivityForResult
and RequestMultiplePermissions
the.
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 registerForActivityResult
API, it is because ConponentActivity
and Fragment
base class implements ActivityResultCaller
an interface, non-Activity / Fragment, if we want to receive Activity return data can be used directly ActivityResultRegistry
to 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 MyLifecycleObserver
implement protocol registration and launcher startup in, why do we need to implement LifecycleObserver
it? Because the life cycle components, LifecycleOwner
will Lifecycle
automatically remove the registered launcher is destroyed. However, if LifecycleOwner does not exist, each ActivityResultLauncher
class allows you to manually invoke unregister()
instead. But when using ActivityResultRegistry, Google officially strongly recommends that we use LifecycleOwner
APIs that are acceptable as parameters.
Why don't you need to manually call them in Activity and Fragment unregister()
? , Because ComponentActivity
and Fragment have been implemented LifecycleObserver
.
ComponentActivity
The 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, startActivityForResult
and onActivityResult
has 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! ! !