AndroidX fromActivity: 1.2.0 - alpha02
和 Fragments: 1.3.0 - alpha02
The addedResult API, the use ofActivityResultContract
Instead 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!