Why drop onActivityResult?

How do I start a new Activity and get the return value?

Your answers must be startActivityForResult and onActivityResult. Yes, all the while, in certain situations, such as launching the system camera to take a photo and returning to the current page to retrieve the photo data, we have no other choice but to process it in onActivityResult.

In the latest Activity 1.2.0-Alpha02 and Fragment 1.3.0-Alpha02, Google provides a new ActivityResult API that allows us to handle onActivityResult more gracefully. Before introducing the new API, let’s think about why Google dropped onActivityResult.

Less boilerplate code, decoupled, easier to test.

In the simplest scenario, MainActivity jumps to SecondActivity, where the button triggers the return and passes the value back. The code in SecondActivity is simple:

class SecondActivity : AppCompatActivity(R.layout.activity_second){

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

        back.setOnClickListener {
            setResult(Activity.RESULT_OK, Intent().putExtra("value"."I am back !"))
            finish()
        }
    }
}
Copy the code

Passing layoutId directly in the AppCompatActivity() constructor is now supported, without the need for additional setContentView().

Back in MainActivity, in the traditional way, it looks like this:

class MainActivity : AppCompatActivity(R.layout.activity_main) {

    private val REQUEST_CODE = 1

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        jump.setOnClickListener { jump() }
    }

    private fun jump(a) {
        startActivityForResult(Intent(this, SecondActivity::class.java), REQUEST_CODE)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int.data: Intent?). {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE) {
            toast(data? .getStringExtra("value") ?: "")
        }
    }API
}
Copy the code
  • Define a REQUEST_CODE to ensure that there are multiple request_codes on the same page
  • Calls startActivityForResult
  • Receive callback in onActivityResult and determine requestCode, resultCode

The above logic contains repetitive boilerplate code and is mostly coupled to the view controller (Activity/Fragment), which makes it difficult to test. Fine taste, really is not so reasonable.

It’s probably the only option we’ve ever had, so it’s rare to see anyone complaining about onActivityResult. The Google engineers, who have perfected this, have fixed it for us.

Let’s look at how to use the latest Activity Result API.

Activity Result API

    private valstartActivity = prepareCall(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult? -> toast(result? .data? .getStringExtra("value") ?: "")}override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        jump.setOnClickListener { jump() }
    }

    private fun jump(a) {
        startActivity.launch(Intent(this,SecondActivity::class.java))
    }
Copy the code

Well, it’s as simple as that. There are two main methods, prepareCall() and launch(). Break them down and analyze them one by one.

    public <I, O> ActivityResultLauncher<I> prepareCall(
            @NonNull ActivityResultContract<I, O> contract,
            @NonNull ActivityResultCallback<O> callback) {
        return prepareCall(contraguanxict, mActivityResultRegistry, callback);
    }
Copy the code

The prepare() method takes two parameters, ActivityResultContract and ActivityResultCallback, and returns ActivityResultLauncher. These names are all very well named.

ActivityResultContract

ActivityResultContract can be understood as a protocol. It is an abstract class that provides two capabilities, createIntent and parseResult. These two capabilities make sense when you start an Activity. CreateIntent provides the Intent for startActivityForResult, and parseResult handles the result fetched in onActivityResult.

In the example above, the protocol implementation class passed in by the prepare() method is StartActivityForResult. It is a static inner class in the ActivityResultContracts class. In addition to StartActivityForResult, RequestPermissions, Dial, RequestPermission, TakePicture, They’re all implementation classes of ActivityResultContract.

So, in addition to simplifying startActivityForResult, permission requests, making calls, and taking photos, all of these can be simplified through the Activity Result API. In addition to using these provided by the official default, you can also implement your own ActivityResultContract, which will be demonstrated later in the code.

ActivityResultCallback

public interface ActivityResultCallback<O> {

    /** * Called when result is available */
    void onActivityResult(@SuppressLint("UnknownNullness") O result);
}
Copy the code

This is the easy one. Notification through this interface when the callback result is available. It is important to note that the return value result must be type safe because of the generic limitations of the prepare() method. The following table shows the mapping between the built-in protocols and their return value types.

Github

Protocol type Return value type
StartActivityForResult ActivityResult
TakePicture Bitmap
Dial Boolean
RequestPermission Boolean
RequestPermissions Map<String,Boolean>

ActivityResultLauncher

The return value from the prepare() method.

Prepare () method is invoked ActivityResultRegistry. RegisterActivityResultCallback () method, the specific source is not analyzed here, later to write a source code parsing alone. Basically, the requestCode is automatically generated, the call-back is registered and stored, the Lifecycle is bound, and the registration is automatically unbound when the Lifecycle.event.on_Destroy Event is received.

Instead of startActivityForResult () is ActivityResultLauncher. Launch () method, finally will be called to ActivityResultRegistry. The invoke () method, as shown below:

        @Override
        public <I, O> void invoke(
                final int requestCode,
                @NonNull ActivityResultContract<I, O> contract,
                I input) {
            Intent intent = contract.createIntent(input);
            if (ACTION_REQUEST_PERMISSIONS.equals(intent.getAction())) {
           	// handle request permissions
            } else {
                ComponentActivity.this.startActivityForResult(intent, requestCode); }}Copy the code

I snuffed out the one in the middle dealing with request Permissions. So you can see it clearly. Originally ready to separate water a source code analysis, this immediately the core source is finished.

Having shown startActivityForResult(), let’s show the permission request.

    private val requestPermission = prepareCall(ActivityResultContracts.RequestPermission()){
        result -> toast("request permission $result")
    }

    requestPermission.launch(Manifest.permission.READ_PHONE_STATE)
Copy the code

Make a phone call, take a picture, I won’t show you here. All of the sample code has been uploaded to my Github.

How do I customize return values?

The previously mentioned protocols are predefined by the system, and the return values are fixed. So, how do you return a value for a custom type? It’s actually very simple, you can just customize your ActivityResultContract.

In the case of TakePicture, the default return value is Bitmap. Now let’s have it return Drawable.

    private class TakePicDrawable : ActivityResultContract<Void,Drawable>() {override fun createIntent(input: Void?).: Intent {
            return Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        }

        override fun parseResult(resultCode: Int, intent: Intent?).: Drawable? {
            if(resultCode ! = Activity.RESULT_OK || intent ==null) return null
            val bitmap = intent.getParcelableExtra<Bitmap>("data")
            return BitmapDrawable(bitmap)
        }
    }
Copy the code

Use:

private val takePictureCustom = prepareCall(TakePicDrawable()) { result ->
    toast("take picture : $result")
}

pictureCustomBt.setOnClickListener {  takePictureCustom()}
Copy the code

This allows you to call the system camera to take a picture and get the Drawable object in the result callback.

What about decoupling?

Sometimes we might want to do some complicated processing in the result callback, either onActivityResult() or the above, which is directly coupled to the view controller. With the new Activity Result API, we can also handle Result callbacks in separate classes, truly having a single responsibility.

The ActivityResult API uses ActivityResultRegistry to perform core operations. The ActivityResultRegistry object is included in ComponentActivity:

    @NonNull
    public ActivityResultRegistry getActivityResultRegistry() {
        return mActivityResultRegistry;
    }
Copy the code

To complete the operation outside the Activity, you now need to provide an external ActivityResultRegistry object to register the result callback. At the same time, we generally implement the LifecycleObserver interface and bind a LifecycleOwner to automate unbinding registration. The complete code is as follows:

class TakePhotoObserver(
    private val registry: ActivityResultRegistry,
    private val func: (Bitmap) -> Unit
) : DefaultLifecycleObserver {

    private lateinit var takePhotoLauncher: ActivityResultLauncher<Void? >override fun onCreate(owner: LifecycleOwner) {
        takePhotoLauncher = registry.registerActivityResultCallback(
            "key",
            ActivityResultContracts.TakePicture()
        ) { bitmap ->
            func(bitmap)
        }
    }

    fun takePicture(a){
        takePhotoLauncher()
    }
}
Copy the code

Do you want to play with some flowers?

I saw some fancy writing on Github, and I want to share it with you.

class TakePhotoLiveData(private val registry: ActivityResultRegistry) : LiveData<Bitmap>() {

    private lateinit var takePhotoLauncher : ActivityResultLauncher<Intent>

    override fun onActive(a) {
        super.onActive()
        registry.registerActivityResultCallback("key",
        ActivityResultContracts.TakePicture()){
            result -> value = result 
        }
    }

    override fun onInactive(a) {
        super.onInactive()
        takePhotoLauncher.dispose()
    }

}
Copy the code

Automatically register and unbind by binding LiveData.

The last

I don’t know what you think of the latest Activity Result API, feel free to leave your opinion in the comments section.

All sample code has been uploaded to my Github address:

Github.com/lulululbj/A… .

More Android latest trends, welcome to scan the code to pay attention to “bingxin say TM”!