Step 4: Android Intents — Navigate Between Screens and Share Data

📚 Learn Android with Kotlin — Series
1. Kotlin 2. Android Studio 3. Activities 4. Intents 5. Fragments 6. RecyclerView 7. ViewModel 8. Room 🔒 9. Retrofit 🔒 10. Material 🔒 11. Firebase 🔒 12. Testing 🔒 13. Publish 🔒

Your app has multiple screens. Right now they're all isolated — tapping a button does nothing. Intents are what connect them. They're the messaging system that tells Android "open this screen", "share this text", or "open a browser to this URL".

This step covers everything you need to navigate your app confidently — explicit intents between your own screens, implicit intents to trigger system apps, passing data with extras, and getting a result back from another screen using the modern ActivityResultContracts API.

Android Intents — Navigate Between Screens and Share Data

Step 3 of the Learn Android with Kotlin series — Android Intents

What Is an Intent?

An Intent is a message object that describes what you want to do. Android reads the Intent and decides who handles it — either a specific component you named (explicit) or the best-matching app on the device (implicit).

Type What it does Example
Explicit Navigate to a specific Activity in your own app Open ProfileActivity from MainActivity
Implicit Ask the OS to find an app that can handle an action Open a URL, share text, dial a number

1. Explicit Intents — Navigate Between Your Screens

Use explicit intents to move between Activities inside your own app. You specify the exact class you want to open.

Basic Navigation

// In MainActivity.kt — navigate to ProfileActivity
binding.btnOpenProfile.setOnClickListener {
    val intent = Intent(this, ProfileActivity::class.java)
    startActivity(intent)
}

Passing Data with Extras

Intents carry data as key-value pairs called extras. Pass them when starting the Activity, retrieve them on the other end:

// Sending data from LoginActivity
binding.btnLogin.setOnClickListener {
    val username = binding.etUsername.text.toString()
    val age = 25

    val intent = Intent(this, ProfileActivity::class.java).apply {
        putExtra("USERNAME", username)
        putExtra("USER_AGE", age)
        putExtra("IS_PREMIUM", true)
    }
    startActivity(intent)
}
// Receiving data in ProfileActivity
class ProfileActivity : AppCompatActivity() {

    private lateinit var binding: ActivityProfileBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityProfileBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Retrieve extras — provide defaults in case they're missing
        val username = intent.getStringExtra("USERNAME") ?: "Guest"
        val age = intent.getIntExtra("USER_AGE", 0)
        val isPremium = intent.getBooleanExtra("IS_PREMIUM", false)

        binding.tvWelcome.text = "Welcome, $username!"
        binding.tvAge.text = "Age: $age"
        binding.tvPlan.text = if (isPremium) "Premium ⭐" else "Free"
    }
}
💡 Use constants for extra keys — hardcoding "USERNAME" in both Activities is fragile. Define them as constants in a companion object to avoid typo bugs:
// In ProfileActivity
companion object {
    const val EXTRA_USERNAME = "extra_username"
    const val EXTRA_AGE = "extra_age"
}

// Sending — use the constant
intent.putExtra(ProfileActivity.EXTRA_USERNAME, username)

// Receiving — same constant
val username = intent.getStringExtra(ProfileActivity.EXTRA_USERNAME) ?: "Guest"

2. Getting a Result Back from an Activity

Sometimes you need information back from the Activity you opened — a photo the user picked, a contact they selected, a setting they changed. The modern way to do this is ActivityResultContractsstartActivityForResult() is deprecated.

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    // Register the launcher — must be at class level, not inside a function
    private val editProfileLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) { result ->
        if (result.resultCode == RESULT_OK) {
            val updatedName = result.data?.getStringExtra("UPDATED_NAME") ?: return@registerForActivityResult
            binding.tvUsername.text = updatedName
            Toast.makeText(this, "Profile updated!", Toast.LENGTH_SHORT).show()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.btnEditProfile.setOnClickListener {
            val intent = Intent(this, EditProfileActivity::class.java)
            editProfileLauncher.launch(intent)
        }
    }
}
// In EditProfileActivity — send result back
class EditProfileActivity : AppCompatActivity() {

    private lateinit var binding: ActivityEditProfileBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityEditProfileBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.btnSave.setOnClickListener {
            val updatedName = binding.etName.text.toString()

            // Package the result and send it back
            val resultIntent = Intent().apply {
                putExtra("UPDATED_NAME", updatedName)
            }
            setResult(RESULT_OK, resultIntent)
            finish()  // Close this Activity and return to the caller
        }

        binding.btnCancel.setOnClickListener {
            setResult(RESULT_CANCELED)
            finish()
        }
    }
}

3. Implicit Intents — Use System Apps

Implicit intents let you hand off tasks to other apps — the browser, camera, dialer, share sheet. You describe what you want to do and Android finds the right app.

Common Implicit Intents

// Open a URL in the browser
fun openWebPage(url: String) {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
    startActivity(intent)
}

// Share text via share sheet
fun shareText(text: String) {
    val intent = Intent(Intent.ACTION_SEND).apply {
        type = "text/plain"
        putExtra(Intent.EXTRA_TEXT, text)
    }
    startActivity(Intent.createChooser(intent, "Share via"))
}

// Open email composer
fun sendEmail(to: String, subject: String) {
    val intent = Intent(Intent.ACTION_SENDTO).apply {
        data = Uri.parse("mailto:")
        putExtra(Intent.EXTRA_EMAIL, arrayOf(to))
        putExtra(Intent.EXTRA_SUBJECT, subject)
    }
    startActivity(intent)
}

// Open phone dialer with a number pre-filled
fun openDialer(phoneNumber: String) {
    val intent = Intent(Intent.ACTION_DIAL).apply {
        data = Uri.parse("tel:$phoneNumber")
    }
    startActivity(intent)
}

// Open Google Maps to a location
fun openMaps(query: String) {
    val intent = Intent(Intent.ACTION_VIEW).apply {
        data = Uri.parse("geo:0,0?q=$query")
    }
    startActivity(intent)
}
How an implicit intent is delivered through the system to start another activity

How an implicit intent is delivered through the system to start another activity

Always Check if an App Can Handle the Intent

On Android 11+ (API 30+), if no app is installed to handle an implicit intent, startActivity() throws an ActivityNotFoundException and crashes your app. Always check first - and for sensitive implicit intents like ACTION_CALL, see Android Runtime Permissions:

fun openWebPageSafely(url: String) {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))

    // Check if any app can handle this intent before launching
    if (intent.resolveActivity(packageManager) != null) {
        startActivity(intent)
    } else {
        Toast.makeText(this, "No browser app found", Toast.LENGTH_SHORT).show()
    }
}
⛔ Android 11+ requirement: For resolveActivity() to work on API 30+, you must declare the intent's action in your AndroidManifest.xml under a <queries> block:
<!-- AndroidManifest.xml -->
<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="https" />
    </intent>
    <intent>
        <action android:name="android.intent.action.SENDTO" />
        <data android:scheme="mailto" />
    </intent>
</queries>

4. Intent Flags — Control the Back Stack

Intent flags control how activities are added to the back stack — the history of screens the user can go back through. Two flags you'll use regularly:

// After login — clear the back stack so user can't press back to login screen
fun navigateToHome() {
    val intent = Intent(this, HomeActivity::class.java).apply {
        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
    }
    startActivity(intent)
    finish()
}

// Navigate to an existing instance of an Activity instead of creating a new one
fun navigateToMain() {
    val intent = Intent(this, MainActivity::class.java).apply {
        flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
    }
    startActivity(intent)
}
Flag What it does Use when
FLAG_ACTIVITY_NEW_TASK
+ FLAG_ACTIVITY_CLEAR_TASK
Clear entire back stack, start fresh After login — prevent going back to login screen
FLAG_ACTIVITY_CLEAR_TOP Clear all activities above the target in the stack Navigate back to home from deep in the app
FLAG_ACTIVITY_SINGLE_TOP Reuse existing instance if already on top Prevent duplicate screens being opened

Real-World Example — Login to Profile Flow

// LoginActivity.kt — complete with ViewBinding
class LoginActivity : AppCompatActivity() {

    private lateinit var binding: ActivityLoginBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.btnLogin.setOnClickListener {
            val username = binding.etUsername.text.toString().trim()
            val password = binding.etPassword.text.toString().trim()

            if (username.isEmpty()) {
                binding.etUsername.error = "Username required"
                return@setOnClickListener
            }

            if (password.length < 6) {
                binding.etPassword.error = "Minimum 6 characters"
                return@setOnClickListener
            }

            // Navigate to ProfileActivity and clear back stack
            // User cannot press back to return to LoginActivity
            val intent = Intent(this, ProfileActivity::class.java).apply {
                putExtra(ProfileActivity.EXTRA_USERNAME, username)
                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
            }
            startActivity(intent)
        }
    }
}
// ProfileActivity.kt
class ProfileActivity : AppCompatActivity() {

    private lateinit var binding: ActivityProfileBinding

    companion object {
        const val EXTRA_USERNAME = "extra_username"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityProfileBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val username = intent.getStringExtra(EXTRA_USERNAME) ?: "Guest"
        binding.tvWelcome.text = "Welcome, $username!"

        // Share profile button
        binding.btnShare.setOnClickListener {
            val intent = Intent(Intent.ACTION_SEND).apply {
                type = "text/plain"
                putExtra(Intent.EXTRA_TEXT, "I'm using this awesome app! My username: $username")
            }
            startActivity(Intent.createChooser(intent, "Share profile via"))
        }
    }
}

Try It Yourself

  1. Pass data both ways — open a settings screen from MainActivity, let the user change their display name, return it using ActivityResultContracts, and update the name shown in MainActivity.
  2. Build a share button — add a share button to any screen that shares the blog post URL https://androidacademic.blogspot.com using Intent.ACTION_SEND with a chooser.
  3. Safe implicit intent — add a "Visit our website" button that opens a URL. Wrap it in a resolveActivity() check and show a Toast if no browser is found.
👉 What's next?
You can navigate between Activities. But what about showing multiple screens within a single Activity — like tabs? In Step 5: Android Fragments — Modular UI Design with Lifecycle and Navigation we learn how to build reusable, modular screen components.

Frequently Asked Questions

What is the difference between explicit and implicit intents?
Explicit intents name the exact Activity to open — used within your own app. Implicit intents describe an action (view a URL, share text) and Android finds the right app to handle it.

How do I pass data between Activities?
Use intent.putExtra(key, value) when starting the Activity and intent.getStringExtra(key) to retrieve it. Define keys as constants in a companion object to avoid typo bugs.

What replaced startActivityForResult?
ActivityResultContracts.StartActivityForResult() — register it at class level with registerForActivityResult(). Call launcher.launch(intent) to start and handle the result in the callback.

Why does my implicit intent crash on Android 11?
API 30+ requires a <queries> block in AndroidManifest.xml declaring which intents your app uses. Also always call resolveActivity(packageManager) before startActivity() to check if a handler exists.

📝 Step 4 Summary
  • Explicit intentIntent(this, TargetActivity::class.java) for your own screens
  • Implicit intent — describe an action, OS finds the right app to handle it
  • Pass data with putExtra(key, value) — use constants for keys
  • Get data back with ActivityResultContracts — not deprecated startActivityForResult()
  • Call setResult(RESULT_OK, intent) and finish() to return a result
  • Always check resolveActivity() before implicit intents — Android 11+ crashes without it
  • Declare intent queries in <queries> in AndroidManifest for API 30+
  • Use FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK after login to clear back stack

Pragnesh Ghoda

A forward-thinking developer offering more than 8 years of experience building, integrating, and supporting android applications for mobile and tablet devices on the Android platform. Talks about #kotlin and #android

Post a Comment

Please let us know about any concerns or query.

Previous Post Next Post

Contact Form