It's time to lose onActivityResult!

Why should I lose onActivityResult?

How to start a new Activity and get the return value?
Your answer is definitely startActivityForResultand onActivityResult. That's right, in some scenarios, such as starting the system camera to take pictures and returning to the current page to get photo data, we have no other choice but to process in onActivityResult.

In the latest Activity 1.2.0-alpha02and Fragment 1.3.0-alpha02in, Google provides a new Activity Result API, so that we can be more elegant process onActivityResult. Before introducing the new API, we might as well think about why Google lost onActivityResult?
Reduce boilerplate code, decoupling, and easier to test.
For the simplest scenario, MainActivityjump to SecondActivity, the SecondActivitymiddle button triggers the return and returns the value. 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()
        }
    }
}

It is now supported to pass in the layoutId directly in the AppCompatActivity () constructor without the need for additional setContentView ().

Back MainActivityin, according to the traditional wording, it is 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() {
        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
}
  • Define a REQUEST_CODE, when there are multiple on the same page, make sure not to repeat
  • Call startActivityForResult
  • Receive callback in onActivityResult, and judge requestCode, resultCode

There is no lack of duplicate boilerplate code in the above logic, and most of them are coupled to the view controller (Activity / Fragment), which makes it difficult to test. A closer look is indeed not so reasonable.

We may only have this one option for a long time, so we rarely see anyone complaining about onActivityResult. The Google engineers who keep improving improve this problem for us.

Let's take a look at how to use the latest Activity Result API.

Activity Result API

    private val startActivity =
        prepareCall(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult? ->
            toast(result?.data?.getStringExtra("value") ?: "")
        }

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

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

Well, it's that simple. There are two main methods, prepareCall()and launch(). Disassemble to analyze one by one.

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

The prepare () method receives two parameters, ActivityResultContractand the ActivityResultCallbackreturn value is ActivityResultLauncher. These few names are very good, see the name and understanding.

ActivityResultContract

ActivityResultContractCan be understood as a protocol, it is an abstract class that provides two capabilities, createIntentand parseResult. These two capabilities are well understood in the start Activity, createIntent is responsible for providing Intent for startActivityForResult, and parseResult is responsible for processing the results obtained in onActivityResult.

In the above example, the protocol implementation class passed in the prepare () method is StartActivityForResult. It is a ActivityResultContractsstatic inner class class. In addition to StartActivityForResultaddition, the official also offers default RequestPermissions, Dial, RequestPermission, TakePicture, they are ActivityResultContractimplementation class.

Therefore, in addition to simplification startActivityForResult, permission requests, phone calls, and photos can be simplified through the Activity Result API. In addition to using the official default, we can also implement ActivityResultContract ourselves, which will be demonstrated in the code behind.

ActivityResultCallback

public interface ActivityResultCallback<O> {

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

This is relatively simple. When the callback result is available, notify through this interface. One thing to note is that due to the generic limitation of the prepare () method, the return value here must be type-safe. The following table shows the correspondence between the system's built-in protocols and their return value types.

Github

ActivityResultLauncher

prepare()The return value of the method.

prepare()In fact, the method calls the ActivityResultRegistry.registerActivityResultCallback()method, the specific source is not analyzed here, the back will write a separate source resolution. The general process is to automatically generate the requestCode, register the callback and store it, bind the life cycle, Lifecycle.Event.ON_DESTROYand automatically unbind the registration when an event is received .

        @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);
            }
        }

I cut off the middle part that handles request permissions. This looks clear. I originally prepared a separate source code analysis, and the core source code was finished.

As shown earlier startActivityForResult(), let's show the permission request again.

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

    requestPermission.launch(Manifest.permission.READ_PHONE_STATE)

Make a phone call and take a picture will not be shown here. All the sample code has been uploaded to my Github.

How to customize the return value?

The aforementioned protocols are preset by the system, and the return values ​​are also fixed. So, how to return the value of a custom type? In fact, very simple, customize ActivityResultContractit.

Let's take the TakePictureexample, the default return value is Bitmap, now we let 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)
        }
    }

use:

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

pictureCustomBt.setOnClickListener {  takePictureCustom()}

In this way, you can call the system camera to take a picture and get the Drawable object in the result callback.

What about decoupling?

Sometimes we may perform some complex processing operations in the result callback, whether it is the previous onActivityResult()or the above writing, are directly coupled in the view controller. Through the new Activity Result API, we can also handle result callbacks in separate classes, truly achieving a single responsibility.
In fact Activity Result API's core operations are done by ActivityResultRegistry, ComponentActivitycontains a ActivityResultRegistrysubject:

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

Activity from now to complete the operation, it is necessary to provide an external ActivityResultRegistryobjects to the registration of the results of the callback. At the same time, we generally by implementing LifecycleObserveran interface, a binding LifecycleOwnerfor automatic registration unbundling. 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(){
        takePhotoLauncher()
    }
}

Play more flowers?

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

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

    private lateinit var takePhotoLauncher : ActivityResultLauncher<Intent>

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

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

}

Automatically register and unbind by binding LiveData.

At last

All sample code has been uploaded to my Github at
github.com/lulululbj/A  ...

Specially share the analysis of the real questions of the byte beating interview, add VX: q1607947758 to get it for free

  • Android Advanced Architecture Video

image

Published 488 original articles · praised 85 · 230,000 views +

Guess you like

Origin blog.csdn.net/Coo123_/article/details/105203861