Android development
Main Activity
data communication
Android Intents
Activity result

Sending data back to the Main Activity in Android

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Sending data back to a main activity is a normal Android pattern for pickers, settings screens, and one-off forms. The modern solution is the Activity Result API, which replaces the older startActivityForResult flow with a lifecycle-aware callback model that is easier to test and maintain.

Register a Result Launcher in the Main Activity

The main activity should register its launcher once, usually as a property, and handle the returned payload in the callback.

kotlin
1import android.app.Activity
2import android.content.Intent
3import android.os.Bundle
4import androidx.activity.result.contract.ActivityResultContracts
5import androidx.appcompat.app.AppCompatActivity
6
7class MainActivity : AppCompatActivity() {
8
9    private val pickerLauncher = registerForActivityResult(
10        ActivityResultContracts.StartActivityForResult()
11    ) { result ->
12        if (result.resultCode == Activity.RESULT_OK) {
13            val value = result.data?.getStringExtra("selected_value")
14            println("Received: $value")
15        } else {
16            println("User canceled")
17        }
18    }
19
20    override fun onCreate(savedInstanceState: Bundle?) {
21        super.onCreate(savedInstanceState)
22
23        val intent = Intent(this, PickerActivity::class.java)
24        pickerLauncher.launch(intent)
25    }
26}

This keeps the request and response logic close together and avoids integer request codes.

Return Data from the Child Activity

The child activity creates a result intent, attaches extras, and finishes with RESULT_OK.

kotlin
1import android.app.Activity
2import android.content.Intent
3import android.os.Bundle
4import androidx.appcompat.app.AppCompatActivity
5
6class PickerActivity : AppCompatActivity() {
7    override fun onCreate(savedInstanceState: Bundle?) {
8        super.onCreate(savedInstanceState)
9
10        val result = Intent().apply {
11            putExtra("selected_value", "dark_mode")
12        }
13
14        setResult(Activity.RESULT_OK, result)
15        finish()
16    }
17}

If the user backs out or cancels, call setResult(Activity.RESULT_CANCELED) or simply finish without a success result.

Pass Input into the Child Screen Too

Most result flows are not one-way. The main activity usually sends initial context, and the child returns the user's choice.

kotlin
1val intent = Intent(this, PickerActivity::class.java).apply {
2    putExtra("current_value", "system_default")
3}
4pickerLauncher.launch(intent)

In the child activity:

kotlin
val currentValue = intent.getStringExtra("current_value")
println("Current value: $currentValue")

Define extra keys as constants or a contract object so both sides stay aligned during refactors.

Use a Custom Contract for Reusable Flows

If the same result pattern appears in several screens, a custom ActivityResultContract is worth it. It centralizes input creation and output parsing.

kotlin
1import android.app.Activity
2import android.content.Context
3import android.content.Intent
4import androidx.activity.result.contract.ActivityResultContract
5
6class PickThemeContract : ActivityResultContract<String, String?>() {
7    override fun createIntent(context: Context, input: String): Intent {
8        return Intent(context, PickerActivity::class.java)
9            .putExtra("current_value", input)
10    }
11
12    override fun parseResult(resultCode: Int, intent: Intent?): String? {
13        if (resultCode != Activity.RESULT_OK) return null
14        return intent?.getStringExtra("selected_value")
15    }
16}

This is cleaner than duplicating extra keys and parsing rules across activities.

Handle Nulls and Cancellation Explicitly

The result callback is still input-handling code, which means it should treat missing extras and cancellation as normal cases, not exceptional ones. A child activity may return RESULT_OK with incomplete data during development, or a future refactor may change a key unexpectedly.

A practical pattern is to centralize key names and branch clearly on RESULT_CANCELED, missing extras, and valid payloads. That makes activity-to-activity contracts easier to debug and test over time.

Common Pitfalls

  • Registering a launcher inside a click listener instead of as part of activity setup.
  • Forgetting to call setResult before finish.
  • Assuming the returned intent is always non-null.
  • Using inconsistent extra keys between caller and child activity.
  • Adding new code with deprecated startActivityForResult when the Activity Result API is available.

Summary

  • Use the Activity Result API for modern Android result flows.
  • Return data through an intent plus an explicit result code.
  • Treat the input contract and output contract as one unit.
  • Use a custom contract when the same request-response flow appears in multiple places.
  • Keep keys centralized so result handling stays stable during refactors.

Course illustration
Course illustration

All Rights Reserved.