Activity result contract android

ActivityResultContract, ActivityResultAPIs in AndroidX

I was going through the ActivityResultAPIs introduced in AndroidX and found them very useful so I decided to share my knowledge here.

Use case: The use case is pretty simple, Start a new activity and get some result back from the newly started activity. I know what you are thinking….. startActivityForResult() and onActivityResult() right. You are absolutely right that we have been using these methods for this use case.

Android has introduced ActivityResultAPIs to handle the same use case in a better and easy way plus you get a lot of other features, will discuss these features later :).

Why should one use it: Many of us will have this question that why should we use this API when we already have startActivityForResult(), yes? Let me give you one use case where you may find this API extremely useful.

“ When starting an activity for a result, it is possible (and, in cases of memory-intensive operations such as camera usage, almost certain) that your process and your activity will be destroyed due to low memory.”

For this reason, the Activity Result APIs decouples the result callback from the place in your code where you launch the other activity. Since the result callback needs to be available when your process and activity are recreated, the callback must be unconditionally registered every time your activity is created, even if the logic of launching the other activity only happens based on user input or other business logic. (You will see this in the code)” source: developer.android.com

Solution: Let’s discuss the solution to the above-mentioned problem where our process may get killed and we may lose the result.

Let’s discuss each component that we have used in the above code. There are mainly three components while using ActivityResultAPI. Contract, ActivityResultLauncher, ResultCallback.

A Contract defines

An intent which will be used to start an activity and

Receives the result intent from the started activity and parses it.

An activity can be called with an input of type I and produce an output of type O.

In the above example ActivityResultContracts.TakePicture() is a pre built contract provided by Android.

2. ActivityResultLauncher: A launcher that is used to start the process of executing an ActivityResultContract. capturePhoto is nothing but our ActivityResultLauncher which is returned by registerForActivityResult(). In simple words, if we have to start an activity for result using ActivityResultAPI then we will simply use ActivityResultLauncher like capturePhoto.invoke(). This will launch ActivityResultContract which in turn launch the intent to startTheActivity by itself.

3. registerForActivityResult: is used to register a contract and this is the place where you get the callback for the result, in the above example callback is passed as lambda. This method returns an object of ActivityResultLauncher.

TakePicture() Contract that we used in the earlier example was a pre-built contract which has a predefined Intent with it. We use Custom Contract to start an activity for result from one activity or fragment to another. Yes, you heard it right. We can use this API for fragments also. Noticed fragment-ktx:1.3.0-alpha04?

To define a custom contract we need to create

  1. A class that extends ActivityResultContract class. This is how we decouple the startActivity() and onActivityResult code from the main activity. Let’s look at the code.

2. Register for activity result.

3. Use the launcher returned by registerForActivityResult which is fetchDataFromSecondActivity. Let’s say we want to launch the second activity on click of a button.

“Test Input” is the input that will be passed to createIntent() of the SecondActivityContract.

As usual in the SecondActivity all we have to do is setResult(Activity. RESULT_OK, intent)

That was all about the Custom Contract.

Android provides some really useful Contracts which are pre-built by Android itself. To use these contracts you can use ActivityResultContract class.

For e.g. ActivityResultContracts.TakePicture()

I personally found permission contracts very useful as we do not have to handle boilerplate code to request Android runtime permissions. This is how we can use permission contracts

  1. Get a launcher to trigger the permission request

2. Trigger the permission request

Start activity for result from fragment

Читайте также:  Recent file cache что это за папка android

With ActivityResultAPI it is fairly simple and same as did in activity.

You can find an example on fragment using this API in the following GitHub project
https://github.com/goyalish/ActivityResultApi.

Don’t forget to clap if you found this post useful. Cheers…

Источник

Introducing the Activity Result APIs

Google has finally released the new Activity Result APIs, which are something I’ve been looking forward to for a very long time. In this post I’ll run through the basics, how you can create your own contracts, and how this allows us to abstract away even more responsibilities from your UI.

The basics

As I’m sure you’re aware, when you want to request data from another activity (say for instance, requesting an image from a camera), you would override onActivityResult . This works absolutely fine but has a couple of downsides:

Repetitive logic

Actually receiving the result requires some slightly tedious code. You need to check the request code to see if it was your activity that requested what’s being returned, then you need to check to see whether or not the request was successful. After that, you pull the data out of the Intent object. It’s become commonplace to see code like this littered around the UI:

Wouldn’t it be nice if we could abstract this away?

Tight coupling

The only place to get these onActivityResult callbacks is in an Activity or Fragment and there’s simply no getting around that.

The Activity Result API

Starting with Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 , we now have a nice abstraction which allows us to handle onActivityResult callbacks in a neat and reusable way, and Google were kind enough to add a few commonly used contracts so that we don’t need to manage them ourselves.

This is the new callback which we can register at any point in our Activity . Making the actual request is as simple as invoking takePicture :

So what’s going on here? Let’s break it down slightly. takePicture is just a callback which returns a nullable Bitmap — whether or not it’s null depends on whether or not the onActivityResult process was successful. prepareCall then registers this call into a new feature on ComponentActivity called the ActivityResultRegistry — we’ll come back to this later. ActivityResultContracts.TakePicture() is one of the built-in helpers which Google have created for us, and finally invoking takePicture actually triggers the Intent in the same way that you would previously with Activity.startActivityForResult(intent, REQUEST_CODE) .

The built in ActivityResultContracts

The built-in ActivityResultContracts currently include a few common operations:

  • Requesting multiple permissions with RequestPermissions
  • Requesting just one permission with RequestPermission
  • Making a phone call with Dial
  • And taking a picture with TakePicture

So what do these actually do under the hood? Let’s take a closer look at TakePicture :

ActivityResultContract takes two type parameters which correspond to data that’s required to make this request (in this case, nothing or Void but quite often a String such as a URI), and the data type to be returned — a Bitmap . As we can see, the contract has two functions, createIntent and parseResult :

In these built-in contracts, you can see we’ve moved the logic which previously tended to live in our Activity and moved them to pretty trivial helper classes. It’s also incredibly easy for us to write our own reusable contracts: we simply specify the inputs and outputs, and handle the Intent creation and Intent + resultCode parsing ourselves.

For a concrete example: we often launch one of our own Activities to send custom data types back. Here’s a ActivityResultContract which would allow us to handle that:

Lovely. However, this is still coupled to the Activity or Fragment . Is there a way around this?

The ActivityResultRegistry

Happily, there is, and it allows us to do all sorts of clever things. The ActivityResultRegistry is a new feature of the ComponentActivity class, and ultimately it contains a list of callbacks to be invoked when onActivityResult is triggered. All we require is a reference to one to be able to register our listeners from any class:

It’s worth pointing out that the resultLauncher() invocation here is an activity-ktx extension for invoking an ActivityResultLauncher of type Void , which is actually just:

Thanks to Ian Lake for letting me know about this one. The TakePictureHandler class allows our Activities to be more composable, and we can test this functionality fairly easily as a small, single-responsibility class, abstracted away from our view:

One slight downside of this is you have to remember to add the handler to the lifecycle observer in your Activity or Fragment . We can improve this a little so that it manages this task itself:

Читайте также:  Обучение андроид разработке с нуля

Here, we pass both an ActivityResultRegistry and a LifecycleOwner to the underlying implementation — this is because otherwise it wouldn’t be possible to test the implementation; we need to be able to pass our own result registry with a dispatching callback AND we need to be able to trigger the onCreate lifecycle callback. By keeping the TakePictureImpl internal , we can test this class thoroughly but expose a simpler API for the caller where we only pass the ComponentActivity . There’s a bit of Kotlin delegation magic here too, and if you’re unsure what this does you can check out one of my previous articles for some more info.

Back to the Activity Result API itself: there’s plenty of situations where this would be useful. We have a class which abstracts away Google’s in-app update API and emits a sealed class of results through a Kotlin Flow type. However the API requires implementing onActivityResult , and we haven’t been able to get around that, so we’ve had to pass the Intent returned through the callback manually to the class. Now we don’t have to do that, the class can be entirely self-contained. It’s a pretty complex example so I haven’t posted it here, but I might put it up separately once I’ve refactored it to use the new registry.

I’m also looking forward to seeing what abstractions people come up with for requesting permissions with this new API.

Gotchas

After some discussion, there does appear to be a small gotcha whilst using this API. Google are pretty explicit in their docs that you must re-register your callback in onCreate :

To clarify, these callbacks are not persisted on a configuration change — but any results returned are in-fact queued, so that when you re-register you’ll instantly receive the awaiting result. Basically to avoid weird behaviour, always make sure these callbacks are registered in onCreate ; don’t do anything weird like registering them ad-hoc in click listeners. Thanks to Gabor Varadi and Vasiliy Zukanov for spotting this and a good discussion on the underlying issue on Twitter. There’s also a great point made here:

The issue isn’t the number of pending requests, it’s that the ID for a result contract is dynamic based on an AtomicInteger.getAndIncrement instead of something stable. So if you misuse the API and call prepareCall each time you want to launch a result request, then you wouldn’t get the result if the camera low-memory-kills you. You only get it if you initialize the ResultCaller once in onCreate and re-use it each time.

So be aware of this behaviour, and that this may change in future. A bug has been filed with Google and this is an Alpha release, so perhaps this post will require updating in a few weeks.

Last thoughts

This is a useful abstraction and I’m super pleased that Google have released this. One minor thing to note is that the the current documentation on the activity result page appears to be incorrect in a couple of places (how to register a contract, how to dispatch a result in a test), but hopefully that’ll get resolved shortly.

Hopefully this has given you some good ideas for some neat abstractions and I’d love to see them, so please share anything you come up with. I’ll likely update this post in a few weeks with some bits and pieces that I come up with too.

Thanks for reading!

Edit: Thanks to Ian Lake for the tips on activity-ktx and pointing me in the direction of the documentation bug tracker.

Источник

Android Activity Result Contracts

Recently Google introduced Android ActivityResultContracts which can be used to pass data between activities. These contracts are a huge improvement compared to onActivityResult() as they allow for the decoupling of unrelated data transfers and can be easily reused across multiple activities. ActivityResultContracts remove the need for conditional statements that were all too common a pattern with the legacy of onActivityResult() when deciding “what data is this / where is it from.»

Before Activity Result Contracts

Previously, passing data from an exiting activity to the launching activity or handling permission request results would require creating Intents and overriding onActivityResult() and using some if / else or switch statements (or when statements if you prefer Kotlin). While this is easy enough to use, if your activity may need to handle the result of a variety of exiting intents / permission results. The onActivityResult() method can quickly become a long and complex mess that isn’t the easiest to read.

Читайте также:  Очистка памяти android studio

Suppose our MainActivity needs to handle requesting microphone, camera, and location permissions in addition to a string message received from another activity. That might look something like this.

Built-In Contracts

Google has already provided some commonly used ActivityResultContracts that can be directly passed into registerForActivityResult() along with a callback that will be executed when that activity result is found.

Take a Picture / Video

Easily capture images / videos from the Camera without having to deal with Media Intent. For photos use TakePicture() and for videos use TakeVideo() .

Request Permission(s)

Ask the user to grant Runtime Permission(s). To request one permission use RequestPermission() which will return a single Boolean indicating if the permission was granted.

To request multiple permissions simultaneously use RequestMultiplePermissions() which would return a map of Booleans indicating which permissions were granted.

And More!

Custom Contracts

Developers also have the ability to define and use their own custom Activity Result Contracts to handle Intents passed between activities. This allows developers to specify expected input and output types and easily reuse across multiple activities.

Getting Started

Activity Result APIs were introduced in AndroidX Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 and they provide components for registering for a result, launching the result, and handling the result once it is dispatched by the system.

Add the following dependencies to the app’s build.gradle file. (At time of writing these were the latest versions of activity-ktx and fragment-ktx . Feel free to use any version later than this.)

Example Contract

Note: We will be using View Binding for code simplicity.

For this example, we want a simple MessageContract that will return a String from one activity to another.

Let’s start by defining a MessageContract which will create an Intent launching MessageActivity .

And a simple MessageActivity layout could contain an EditText and Button defining an onClick method called onSubmitMessage() . Here’s the MessageActivity layout XML from our code.

Let’s define how MessageActivity should handle the button click in onSubmitMessage() — by packaging the String inputted by the user in the EditText in an Intent, setting the Activity result to RESULT_OK , and finishing the activity with finish() .

When the MessageActivity is closed, the resulting String can be parsed from the Intent and returned in the MessageContract’s parseResult() .

Note that in the above example we pass in Unit as the input and a String as the output. If you wanted to provide arguments to the next activity, you would do so in the input portion. We just use Unit when you want to specify the input as nothing.

In our receiving activity, we need to specify a variable for the ActivityResultLauncher returned by the registerForActivityResult() where we pass in our custom MessageContract() .

Here we will define a lambda that is to be executed when the contract parses the result. In this example, we are just displaying the String message in a Toast to the user. The result it is expected to be a String as was specified to be the output type by our contract — class MessageContract : ActivityResultContract () .

To launch the messageLauncher in our receiving activity, we simply need to call messageLauncher.launch() . This will call the createIntent() method we overrode in our custom MessageContract and create and launch the MessageActivity Intent.

“Gotchas”

Be sure your contracts are available when the Activities process is created or recreated. It is possible that your process gets destroyed while the other Activity that you are awaiting your result from is working. If this happens and your result contract isn’t prepared by the time your Activity is recreated than the result will be missed. Do NOT make these local registrations that happen when a specific function is hit or button is pressed, which is also called out in Google’s documentation here.

Takeaways

ActivityResultContracts can be used to explicitly specify input and output types when handling the different data transfer cases between activities. You no longer need to override onActivityResult() and use a plethora of conditional statements to determine how to handle the exiting activity’s Intent. The contracts allow for the (1) decoupling of unrelated data transfers (2) easy reuse across multiple activities and (3) increased type-safety. They are definitely a clean, long-needed improvement for Android development.

Feel free to check out the sample application provided with this post.

Источник

Оцените статью