Starting Activities for Results in Android
Tags: android, tutorial, learning, androiddevelopment
Published: 2023-10-24 21:15:45

---
title: Starting Activities for Results in Android
published: true
date: 2023-10-24 20:53:42 UTC
tags: android, tutorial, learning, androiddevelopment
canonical_url: https://blog.nicholasfragiskatos.dev/starting-activities-for-results-in-android
---

There are many situations in Android where we need to start a second activity from the current activity or fragment. This second activity could either be another internal activity as defined by your application, or it could be from some external application that we can leverage to do something such as take a picture, choose from a list of contacts, or view something in Google Maps.

Furthermore, it's very likely that when we want to leverage some other application's functionality we also want that other application to provide us with a result. Luckily, Android provides us with a mechanism for receiving information back from other activities.

In this article, I want to go over the two different approaches that are available to us to solve this issue: one is the original, but deprecated approach, and the other is the modern approach.

I will provide small code snippets where needed, but the full sample project can be found [here on GitHub](https://github.com/nfragiskatos/ResultsFromActivities)

> 💡
Both solutions are available in activities _and_ fragments. However, for brevity, I'll just be referencing activities.

## Project Setup

To start, I created a brand new project using Android Studio's "_Empty Views Activity_" template. Then along with the provided `MainActivity` I added a `SecondActivity`

I added one button to the layout of `MainActivity` that when clicked will open `SecondActivity`. In the layout of `SecondActivity` I have a `TextView` to display any input passed from `MainActivity`, an `EditText` to input a result to send back to `MainActivity`, and lastly a `Button` that when clicked will finish the activity and return the result.

`MainActivity`:

![Image of the initial layout of MainActivity](https://cdn.hashnode.com/res/hashnode/image/upload/v1698091727630/16940b8c-2048-44a9-90a9-1ee226d3a243.png?height=600)

`SecondActivity`:

![Image of the initial layout of SecondActivity](https://cdn.hashnode.com/res/hashnode/image/upload/v1698092418546/aae0ea54-f09c-494f-8cd3-164f82045855.png?height=600)

## The Legacy Approach

The legacy solution is simple enough to implement, and it revolves around two key functions: `startActivityForResult(...)`, and `onActivityResult(...)`. Both are provided by the activity's parent class.

### Starting `SecondActivity` with an Input

In `MainActivity`, I implemented a click listener for the button:

```kotlin
binding.btnStartSecondActivityOld.setOnClickListener {  
    startSecondActivityForResultUsingOldWay()  
}

private fun startSecondActivityForResultUsingOldWay() {  
    val intent = Intent(this, SecondActivity::class.java).putExtra(  
        MAIN_ACTIVITY_BUNDLE_ID,  
        "Input From Main Activity Old Way"  
    )  

    startActivityForResult(  
        intent,  
        MAIN_ACTIVITY_REQUEST_CODE  
    )  
}

```

First, I create an `Intent` that specifies the current context and the name of the activity's class I wish to start. Also, I used `putExtra` to store a `String` in the `Bundle` of the `Intent`, which will be accessible to `SecondActivity` as input. Then once I have the `Intent` created, all I have to do is call `startActivityForResult(...)` passing along the `Intent` and a request code.

> 💡
The request code can be anything. It can be used to keep track of different requests if you expect to be dealing with multiple requests.

### Handling Input and Returning a Result

In `SecondActivity.onCreate(...)`, I first retrieve the input from the `Bundle` of the `Intent` and display it. Then I add a click-listener to the button that when fired will return the result. The click listener creates its own `Intent` to bundle the return data. Then it calls `setResult(...)` to send the `Intent` and a result code back to `MainActivity`. Lastly, it calls `finish()` to end the activity.

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    // ....

    val inputFromMainActivity = intent.getStringExtra(MainActivity.MAIN_ACTIVITY_BUNDLE_ID)  

    binding.tvInput.text = inputFromMainActivity

    binding.btnSendResult.setOnClickListener {  
        val result = binding.etResult.text.toString()  
        val intent = Intent().putExtra(SECOND_ACTIVITY_BUNDLE_ID, result)  
        setResult(SECOND_ACTIVITY_RESULT_CODE, intent)  
        finish()  
    }
}

```

> 💡
Much like the request code, the result code can also be any integer. For convenience, the `Activity` class already has some predefined constants, like `Activity.RESULT_OK`.

### Handling the Result

To process the result back in `MainActivity` we override the `onActivityResult(...)` function. `onActivityResult(...)` has arguments for a request code and a result code so you can uniquely handle each result based on where it came from. It also has an `Intent` argument which stores the returned result.

```kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {  
    super.onActivityResult(requestCode, resultCode, data)  

    if (requestCode == MAIN_ACTIVITY_REQUEST_CODE && resultCode == SecondActivity.SECOND_ACTIVITY_RESULT_CODE) {  
        data?.getStringExtra(SecondActivity.SECOND_ACTIVITY_BUNDLE_ID)?.let {  
            Toast.makeText(this, "Got Result: $it", Toast.LENGTH_SHORT).show()  
        }  
    }  
}

```

![gif showing the workflow of clicking the button to open the second activity, then using the second activity to return a result using the legacy way](https://cdn.hashnode.com/res/hashnode/image/upload/v1698092743770/ffbcb254-2c46-4410-a12f-3d252e6c60bf.gif?height=600)

### Limitations

This seems straightforward enough to implement, so why would this be deprecated?

One problem is that since the request code is just an integer there is a possibility of duplication between two unrelated requests, or even the wrong integer value being used by mistake which could lead to confusing behavior. A way to circumvent these issues is to define a library of request code constants for each type of request, but that is still not ideal if there are many codes to manage.

A similar issue is that `onActivityResult(...)` is a single function that has to handle _all_ requests. Depending on the number of requests, this function could just turn into one large, messy switch statement.

## The New Activity Result API

The new Activity Result API centers on three new classes: `ActivityResultContract`, `ActivityResultCallback`, and `ActivityResultLauncher`. These three classes are used in the `registerForActivityResult(...)` function, which is provided to us by the activity or fragment.

```java
@NonNull   
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(  
    @NonNull ActivityResultContract<I, O> contract,  
    @NonNull ActivityResultCallback<O> callback
)

```

As the name implies, `registerForActivityResult(...)` will register your callback, but it _will not_ launch anything. That is the responsibility of the `ActivityResultLauncher` object that `registerForActivityResult(...)` returns. We can use the `ActivityResultLauncher` wherever in our code we are ready to launch the request.

The first argument we need to pass `registerForActivityResult(...)` is an instance of an `ActivityResultContract`. This contract is essentially a way to define the input and output for the request in a type-safe way. We'll look more into the details of contracts in a later section when we implement our own. Luckily, there's an existing `ActivityResultContracts` class (notice the additional 's' on the end) that contains several prebuilt contracts to use.

Finally, the last piece is the `ActivityResultCallback` argument. This is what is called when the result is ready to be returned. The callback is given the type of result as defined by the output of the contract.

### Implementation

First, I defined a top-level `ActivityResultLauncher` property for the `MainActivity` class.

```kotlin
private val startSecondActivityForResultLauncher: ActivityResultLauncher<Intent> = 
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult: ActivityResult ->  
        if (activityResult.resultCode == SecondActivity.SECOND_ACTIVITY_RESULT_CODE) {
            activityResult.data?.getStringExtra(SecondActivity.SECOND_ACTIVITY_BUNDLE_ID)?.let {  
                Toast.makeText(this, "Got Result: $it", Toast.LENGTH_SHORT).show()  
            }  
        }  
    }

```

For the contract, I am passing the prebuilt `StartActivityForResult` contract, which defines an `Intent` as an input and an `ActivityResult` as the output.

Therefore, in the callback function, I can take the `ActivityResult`, do some code comparisons and then grab the data from the bundle just like in `onActivityResult(...)` from the legacy approach.

For the UI I add another button to my view and then define an on-click to launch the request:

```xml
<Button  
    android:id="@+id/btnStartSecondActivityNew"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:text="Start SecondActivity (new)"  
    app:layout_constraintEnd_toEndOf="parent"  
    app:layout_constraintStart_toStartOf="parent"  
    app:layout_constraintTop_toBottomOf="@+id/btnStartSecondActivityOld" 
/>
```
```kotlin
binding.btnStartSecondActivityNew.setOnClickListener {  
    startSecondActivityUsingNewApi()  
}

private fun startSecondActivityUsingNewApi() {  
    val intent = Intent(  
        this,  
        SecondActivity::class.java  
    ).putExtra(  
        MAIN_ACTIVITY_BUNDLE_ID,  
        "Input From Main Activity New API"  
    )  
    startSecondActivityForResultLauncher.launch(intent)
}

```

Since the `StartActivityForResult` contract takes an `Intent` as an input, I create one in the same way that I did when calling `startActivityForResult(...)` for the legacy approach. The `Intent` defines the context, the activity class to start, as well as some input `String` value. Then it's as simple as calling the `launch(...)` function on our `ActivityResultLauncher` object and passing in the `Intent` as input.

![gif showing the workflow of clicking the button to open the second activity, then using the second activity to return a result using the new Activity Result API](https://cdn.hashnode.com/res/hashnode/image/upload/v1698093022368/ce6a2fa3-18da-41bb-a0f8-31a018f3109f.gif?height=600)

### Benefits

This approach provides us with a few very nice benefits over the deprecated approach:

- We no longer have to worry about request code handling and maintenance. Each separate request that's launched has its own callback. We are no longer limited to one callback function (`onActivityResult(...)`) handling _all_ requests.

- Because we are using the `ActivityResultContract` class, we get the convenience of a strongly typed input and output.

- `ActivityResultContract` also wraps up some of the boilerplate code for managing the input and output of the request. We will see in the next section that if we create our own custom contract we can move even more input and output logic into the contract.

## Activity Result API with a Custom Contract

Creating our own custom contract involves implementing two functions from `ActivityResultContract`: one to define how our input is processed and one to define how the output is processed.

```kotlin
abstract class ActivityResultContract<I, O> {
    abstract fun createIntent(context: Context, input: I): Intent

    abstract fun parseResult(resultCode: Int, intent: Intent?): O
}

```

You'll notice that for the implementation, all we are doing is moving some code from the activity into these functions. In `createIntent(...)` I'm creating the same intent as before. Then in `parseResult(...)` I'm doing the same result code checks and getting the result from the `Bundle` as before.

```kotlin
class MyCustomContract : ActivityResultContract<String, String?>() {  
    override fun createIntent(context: Context, input: String): Intent {  
        val intent = Intent(  
            context,  
            SecondActivity::class.java  
        ).putExtra(  
            MAIN_ACTIVITY_BUNDLE_ID,  
            input  
        )
        return intent
    }  

    override fun parseResult(resultCode: Int, intent: Intent?): String? {  
        if (resultCode == SecondActivity.SECOND_ACTIVITY_RESULT_CODE) {  
            intent?.getStringExtra(SecondActivity.SECOND_ACTIVITY_BUNDLE_ID)?.let {  
                return it  
            }  
        }  
        return null  
    }   
}

```

Now that I have my custom contract I can go ahead and define another top level `ActivityResultLauncher` property for the `MainActivity` class. Notice that now I don't have to handle any result code checking in the callback since I handle all of that in `MyCustomContract.parseResult(...)` instead.

```kotlin
private val startSecondActivityCustomContractLauncher =
        registerForActivityResult(MyCustomContract()) { result: String? ->
            result?.let {
                Toast.makeText(this, "Got Result: $it", Toast.LENGTH_SHORT).show()
            }
        }

```

I again updated the UI with another button and click listener. Similarly to the simplified callback definition, the launch call is also simplified since all I have to do is pass the input and `MyCustomContract.createIntent(...)` handles any `Intent` building logic.

```xml
<Button  
    android:id="@+id/btnStartSecondActivityCustomContract"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:text="Start SecondActivity (Custom Contract)"  
    app:layout_constraintEnd_toEndOf="parent"  
    app:layout_constraintStart_toStartOf="parent"  
    app:layout_constraintTop_toBottomOf="@+id/btnStartSecondActivityNew" 
/>
```
```kotlin
binding.btnStartSecondActivityCustomContract.setOnClickListener {  
    startSecondActivityCustomContractLauncher.launch("Custom Contract Input")  
}

```

![gif showing the workflow of clicking the button to open the second activity, then using the second activity to return a result using the Activity Result API with a custom contract](https://cdn.hashnode.com/res/hashnode/image/upload/v1698178001479/37962912-4655-40b5-a2b2-6cc6585fa6be.gif?height=600)

## Conclusion

The Activity Result API provides us with a safer and more convenient way of launching activities that are expected to return some result.

We first looked at the old approach and its straightforward implementation. However, although it works well for simple cases, it can get messy for complex cases where we need to handle many different requests. Managing request code uniqueness, and having one large callback function to handle _all_ requests is less than ideal from a maintainability standpoint.

We then explored how to utilize `ActivityResultContract`, `ActivityResultCallback`, `ActivityResultLauncher`, and `registerForActivityResult(...)` from the Activity Result API. This implementation removed the need for request code handling, brought us type safety, and cleaned up the code by allowing for individual callbacks for each request type.

Lastly, we looked at how to create our own `ActivityResultContract` implementation. By rolling our own, we were able to further clean up the code in the activity by moving the intent creation and much of the result handling into the contract.

* * *

If you noticed anything in the article that is incorrect or isn't clear, please let me know. I always appreciate the feedback.