Why should I lose onActivityResult?
How to start a new Activity and get the return value?
Your answer is definitely startActivityForResult
and 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-alpha02
and Fragment 1.3.0-alpha02
in, 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, MainActivity
jump to SecondActivity
, the SecondActivity
middle 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 MainActivity
in, 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, ActivityResultContract
and the ActivityResultCallback
return value is ActivityResultLauncher
. These few names are very good, see the name and understanding.
ActivityResultContract
ActivityResultContract
Can be understood as a protocol, it is an abstract class that provides two capabilities, createIntent
and 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 ActivityResultContracts
static inner class class. In addition to StartActivityForResult
addition, the official also offers default RequestPermissions
, Dial
, RequestPermission
, TakePicture
, they are ActivityResultContract
implementation 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_DESTROY
and 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 ActivityResultContract
it.
Let's take the TakePicture
example, 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, ComponentActivity
contains a ActivityResultRegistry
subject:
@NonNull
public ActivityResultRegistry getActivityResultRegistry() {
return mActivityResultRegistry;
}
Activity from now to complete the operation, it is necessary to provide an external ActivityResultRegistry
objects to the registration of the results of the callback. At the same time, we generally by implementing LifecycleObserver
an interface, a binding LifecycleOwner
for 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