AndroidX fromActivity: 1.2.0 - alpha02Fragments: 1.3.0 - alpha02The addedResult API, the use ofActivityResultContractInstead of startActivityForResult, typesafe handles cross-activity communication more efficiently. Currently, Result API has been upgraded to RC version, which has some changes compared with alpha version. The content of this article is based on1.2.0 - rc - 01

Reference: Alpha version of the Result API

How to use

In AppCompatActivity or fragments through registerForActivityResult create ActivityResultLauncher (), then calls the launch (…). Start the target Activity instead of startActivityForResult.

//MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
      
		fab.setOnClickListener { view ->
		    val intent = Intent(this, MainActivity::class.java)
			launcher.launch(intent)
		}
    }
  
    // Create ActivityResultLauncher
    private val launcher : ActivityResultLauncher =
    	registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
    		activityResult ->
           		// activityResult will return this as ActivityResult                                        		
                	Log.d("MainActivity", activityResult.toString())
    			// D/MainActivity: ActivityResult{resultCode=RESULT_OK, data=Intent { (has extras) }}}}Copy the code
//SecondActivity.kt
class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setResult(Activity.RESULT_OK, Intent().putExtra("my-data"."data"))
        finish()
    }
}
Copy the code

Source code analysis

The Result API replaces startActivityResult as a callback, and the core is the ActivityResultContract protocol class. StartActivityForResul is one of several implementations of ActivityResultContracts that are preset in ActivityResultContracts.

StartActivityForResult

//ActivityResultContracts.java
public static final class StartActivityForResult
            extends ActivityResultContract<Intent.ActivityResult> {

        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, @NonNull Intent input) {
            return input;
        }

        @NonNull
        @Override
        public ActivityResult parseResult(
                int resultCode, @Nullable Intent intent) {
            return newActivityResult(resultCode, intent); }}Copy the code

The ActivityResultContract accepts two generics representing the type of launch parameter for ActivityResultLauncher and the type of result returned by onActivityResult. For StartActivityForResult, it is started with the Intent and waits for the target Activity to return ActivityResult.

Custom ActivityResultContract

Of course, you can customize ActivityResultContract to accept any type of startup parameter. Then construct the Intent in createIntent, for example:

//PostActivityContract.kt
class PostActivityContract : ActivityResultContract<Int, String?>() {
    override fun createIntent(context: Context, input: Int): Intent {
        return Intent(context, PostActivity::class.java).apply {
            putExtra("PostActivity.ID", input)
        }
    }

    override fun parseResult(resultCode: Int, intent: Intent?).: String? {
        val data= intent? .getStringExtra(PostActivity.TITLE)return if (resultCode == Activity.RESULT_OK && data! =null) data
        else null}}Copy the code
// MainActivity.kt
launcher.launch(100089) //launch with Int

private val launcher =
    registerForActivityResult(PostActivityContract()) { result ->
        // Result will return this as string?                                              
        if(result ! =null) toast("Result : $result")
        else toast("No Result")}Copy the code

ActivityResultRegistry

RegisterForActivityResult calling ActivityResultRegistry register () method

//ComponentActivity.java

  	@NonNull
    @Override
    public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultRegistry registry,
            @NonNull final ActivityResultCallback<O> callback) {
        return registry.register(
                "activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
    }

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

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

The ActivityResultContract is stored internally in a HashMap by Register (). You can also use custom ActivityResultRegistry instead of using mActivityResultRegistry, for example

val launcher: ActivityResultLauncher<Intent> = activityResultRegistry
        .register(ActivityResultContracts.StartActivityForResult(),
                "activity_rq#0".this.//LifecyleOwner: ComponentActivity
        ) { activityResult: ActivityResult ->
            Log.d("MainActivity", activityResult.toString())
            // D/MainActivity: ActivityResult{resultCode=RESULT_OK, data=Intent { (has extras) }}
        }
Copy the code

The Register accepts a LifecycleOwner (ComponentActivity itself) and stores/removes callbacks to the Map at the appropriate lifecycle, ensuring that the timing of the callback response is correct.

LifecycleEventObserver observer = new LifecycleEventObserver() {
            @Override
            public void onStateChanged(
                    @NonNull LifecycleOwner lifecycleOwner,
                    @NonNull Lifecycle.Event event) {
                if (Lifecycle.Event.ON_START.equals(event)) {
                    mKeyToCallback.put(key, newCallbackAndContract<>(callback, contract)); . }else if (Lifecycle.Event.ON_STOP.equals(event)) {
                    mKeyToCallback.remove(key);
                } else if(Lifecycle.Event.ON_DESTROY.equals(event)) { unregister(key); }}};Copy the code

ComponentActivity related implementation

ComponentActivity is simpler, hold ActivityResultRegistry and provide registerForActivityResult method. ActivityResultRegistry stores callbacks through a HashMap, whose key is incremented.

 "activity_rq#" + mNextLocalRequestCode.getAndIncrement()
Copy the code

Do YOU still need a requestCode

OnActivityResult, which used to require requestCode to identify which startActivityForResult return, can now be managed by AutoIncrement. OnSaveInstanceState will automatically save the key pair of the requestCode and ActivityResultRegistry when the process is killed. When onActivityResult returns a requestCode, You can find the key through the correspondence, and then find the ActivityResultCallback.

//ActivityResultRegistry.java
private int registerKey(String key) {
        Integer existing = mKeyToRc.get(key);
        if(existing ! =null) {
            return existing;
        }
        int rc = mNextRc.getAndIncrement();
        bindRcKey(rc, key);
        return rc;
    }
Copy the code

Fragment-dependent implementation

Internal fragments. RegisterForActivityResult (), when onAttach invokes getActivity (). GetActivityResultRegistry () to register

//Fragment.java
	public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultCallback<O> callback) {
        return prepareCallInternal(contract, new Function<Void, ActivityResultRegistry>() {
            @Override
            public ActivityResultRegistry apply(Void input) {
                if (mHost instanceof ActivityResultRegistryOwner) {
                    return ((ActivityResultRegistryOwner) mHost).getActivityResultRegistry();
                }
                return requireActivity().getActivityResultRegistry();
            }
        }, callback);
	}


    @NonNull
    private <I, O> ActivityResultLauncher<I> prepareCallInternal(
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final Function<Void, ActivityResultRegistry> registryProvider,
            @NonNull final ActivityResultCallback<O> callback) {... registerOnPreAttachListener(new OnPreAttachedListener() {
            @Override
            void onPreAttached(a) {
                final String key = generateActivityResultKey();
                ActivityResultRegistry registry = registryProvider.apply(null);
                ref.set(registry.register(key, Fragment.this, contract, callback)); }});return new ActivityResultLauncher<I>() {
            @Override
            public void launch(I input) {... }}; }private void registerOnPreAttachListener(@NonNull final OnPreAttachedListener callback) {
        //If we are already attached, we can register immediately
        if (mState >= ATTACHED) {
            callback.onPreAttached();
        } else {
            // else we need to wait until we are attachedmOnPreAttachedListeners.add(callback); }}Copy the code

Register inserts framgent instances into a HashMap held by the parent, but LifecycleOwner is the Fragment itself. As we can see from the previous analysis, post-processing is performed on ON_DESTROY, so there is no need to worry about memory leaks.

More preset ActivityResultContract

In addition to StartActivityFroResult, there are a number of anticipated ActivityResultContracts:

For example, open the file manager to select the image with GetContent and return the URI:

class MainActivity : AppCompatActivity() {

    private val launcher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
        Log.d("MainActivity"."uri: $uri")}override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button_get_content.setOnClickListener {
            launcher.launch("image/*")}}}Copy the code

Through the RequestPermission and RequestMultiplePermission permissions application will also be easier:

request_permission.setOnClickListener {
    requestPermission.launch(permission.BLUETOOTH)
}

request_multiple_permission.setOnClickListener {
    requestMultiplePermissions.launch(
        arrayOf(
            permission.BLUETOOTH,
            permission.NFC,
            permission.ACCESS_FINE_LOCATION
        )
    )
}

// Request permission contract
private val requestPermission =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        // Do something if permission granted
        if (isGranted) toast("Permission is granted")
        else toast("Permission is denied")}// Request multiple permissions contract
private val requestMultiplePermissions =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions : Map<String, Boolean> ->
        // Do something if some permissions granted or denied
        permissions.entries.forEach {
            // Do checking here}}Copy the code

The last

The benefits of the Result API are clear: The ActivityResultContract eliminates awareness of requestCode and onActivityResult and reduces template code. More importantly, a variety of preset ActivityResultContract greatly reduces the communication cost of system apis and facilitates daily development. It’s time to say goodbye to startActivityForResult!