Android Intents Navigation


Navigating Between Activities using Intents (Explicit and Implicit Intents)

Activities are often the building blocks of different screens or tasks in your Android application. To move from one Activity to another, or to interact with components in other applications, you use a powerful messaging object called an Intent.

What is an Intent?

An Intent is a messaging object you can use to request an action from another app component. Although intents are most commonly used to start activities, you can also use them to start services and deliver broadcasts.

An Intent provides a way to:

  • Start an Activity: Launch a new screen in your app or another app.
  • Start a Service: Start a background operation.
  • Deliver a Broadcast: Announce an event system-wide or to specific components.

In the context of navigating between Activities, an Intent acts as a messenger, specifying which Activity you want to start and potentially carrying data to that Activity.

Types of Intents for Starting Activities:

There are two main types of Intents used for starting Activities:

1. Explicit Intents:

  • What they are: Explicit intents specify the component to start by its fully qualified class name.
  • When to use them: You use explicit intents when you know exactly which Activity you want to start within your own application.
  • How to create them: You create an explicit intent by providing the application context and the class name of the target Activity.
// Assuming you have an Activity named SecondActivity in your project
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
  • Intent(this, SecondActivity::class.java): Creates a new Intent.
    • this: The current context (usually your Activity).
    • SecondActivity::class.java: The class object of the Activity you want to start.
  • startActivity(intent): Sends the intent to the Android system, which then launches the specified Activity.

2. Implicit Intents:

  • What they are: Implicit intents declare a general action to perform, allowing the system to find the appropriate component (potentially in another app) that can handle that action.
  • When to use them: You use implicit intents when you want to request an action (like viewing a webpage, making a phone call, taking a photo) but you don't know or care which specific app component will perform the action. The system will present the user with a choice of apps that can handle the intent (if multiple exist).
  • How to create them: You create an implicit intent by specifying an action (a string constant from the Intent class or a custom action) and optionally data (a URI) that the action operates on.
// Example: Opening a webpage
val webpage = Uri.parse("http://www.android.com")
val intent = Intent(Intent.ACTION_VIEW, webpage)

// Check if there's an app that can handle this intent
if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
} else {
    // Handle the case where no app can handle the intent (e.g., show a message)
    Toast.makeText(this, "No app available to view webpage", Toast.LENGTH_SHORT).show()
}

// Example: Making a phone call (requires CALL_PHONE permission)
val phoneNumber = Uri.parse("tel:1234567890")
val intent = Intent(Intent.ACTION_DIAL, phoneNumber) // Use ACTION_DIAL to open dialer with number pre-filled
// Or Intent(Intent.ACTION_CALL, phoneNumber) // Use ACTION_CALL to directly initiate call (requires permission)

if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
} else {
    Toast.makeText(this, "No app available to make a call", Toast.LENGTH_SHORT).show()
}
  • Intent.ACTION_VIEW: A common action for displaying data to the user.
  • Uri.parse("..."): Creates a URI (Uniform Resource Identifier) representing the data.
  • intent.resolveActivity(packageManager) != null: This is a crucial check for implicit intents. It verifies if there is at least one activity on the device that can handle the intent. You should always perform this check before calling startActivity() with an implicit intent to avoid crashing the app if no suitable activity is found.

Passing Data Between Activities:

You can pass data to the target Activity using the putExtra() method of the Intent object. Data is stored in key-value pairs within a Bundle that is part of the Intent.

// In the sending Activity
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("EXTRA_MESSAGE", "Hello from the first activity!")
intent.putExtra("EXTRA_NUMBER", 123)
startActivity(intent)
// In the receiving Activity (e.g., in onCreate)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_second)

    // Get the Intent that started this Activity
    val intent = intent

    // Get data from the Intent's extras Bundle
    val message = intent.getStringExtra("EXTRA_MESSAGE")
    val number = intent.getIntExtra("EXTRA_NUMBER", 0) // Provide a default value

    // Use the received data (e.g., display in a TextView)
    val receivedTextView: TextView = findViewById(R.id.receivedTextView)
    receivedTextView.text = "Message: $message, Number: $number"
}
  • intent.getStringExtra("EXTRA_MESSAGE"): Retrieves a String value associated with the key "EXTRA_MESSAGE".
  • intent.getIntExtra("EXTRA_NUMBER", 0): Retrieves an Int value associated with the key "EXTRA_NUMBER". The second argument is a default value to return if the key is not found.
  • You can use putExtra() and corresponding get...Extra() methods for various data types (String, Int, Boolean, Double, Parcelable, Serializable, etc.).

Getting a Result Back from an Activity:

Sometimes, you need to start an Activity and get a result back from it (e.g., selecting a contact from a list, taking a picture). You use startActivityForResult() and handle the result in the calling Activity's onActivityResult() method (or use the newer Activity Result APIs).

Using the older startActivityForResult() method:

// In the sending Activity
private val REQUEST_CODE_SECOND_ACTIVITY = 1

fun startSecondActivityForResult() {
    val intent = Intent(this, SecondActivity::class.java)
    startActivityForResult(intent, REQUEST_CODE_SECOND_ACTIVITY)
}

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

    if (requestCode == REQUEST_CODE_SECOND_ACTIVITY) {
        if (resultCode == Activity.RESULT_OK) {
            // Activity returned successfully
            val resultData = data?.getStringExtra("RESULT_DATA")
            Toast.makeText(this, "Result: $resultData", Toast.LENGTH_SHORT).show()
        } else if (resultCode == Activity.RESULT_CANCELED) {
            // Activity was canceled
            Toast.makeText(this, "Activity canceled", Toast.LENGTH_SHORT).show()
        }
    }
}
// In the receiving Activity, when you want to return a result
fun returnResult(result: String) {
    val resultIntent = Intent()
    resultIntent.putExtra("RESULT_DATA", result)
    setResult(Activity.RESULT_OK, resultIntent) // Set the result code and data
    finish() // Close the current activity
}

// If the user cancels (e.g., presses back button), setResult(Activity.RESULT_CANCELED) is often implicitly called.
// You can also explicitly call it if needed.
fun cancelActivity() {
    setResult(Activity.RESULT_CANCELED)
    finish()
}
  • startActivityForResult(intent, requestCode): Starts the activity and provides a unique request code to identify the request when the result is returned.
  • setResult(resultCode, data): In the receiving activity, sets the result code (Activity.RESULT_OK or Activity.RESULT_CANCELED) and optionally an Intent containing result data.
  • finish(): Closes the current activity.
  • onActivityResult(requestCode, resultCode, data): In the calling activity, this method is called when the started activity finishes. You check the requestCode to identify which request the result belongs to and the resultCode to see if the operation was successful.

Using the newer Activity Result APIs (Recommended for new development):

The Activity Result APIs provide a cleaner and more type-safe way to handle results. You define a contract for the result and register a callback.

// In the sending Activity

// 1. Define a contract (or use a predefined one like StartActivityForResult)
private val startSecondActivity = registerForActivityResult(StartActivityForResult()) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        val resultData = result.data?.getStringExtra("RESULT_DATA")
        Toast.makeText(this, "Result: $resultData", Toast.LENGTH_SHORT).show()
    } else if (result.resultCode == Activity.RESULT_CANCELED) {
        Toast.makeText(this, "Activity canceled", Toast.LENGTH_SHORT).show()
    }
}

// 2. Launch the activity
fun startSecondActivityForResultNew() {
    val intent = Intent(this, SecondActivity::class.java)
    startSecondActivity.launch(intent)
}

The receiving Activity code for setting the result remains the same.

Configuring Activities in the Manifest:

Every Activity in your application must be declared in the AndroidManifest.xml file. This tells the system about your activities.

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
    <application ...>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SecondActivity">
            <!-- Optional: Add intent filters here if this activity can be launched by implicit intents -->
        </activity>
        <!-- Declare other activities here -->
    </application>
</manifest>
  • <activity android:name=".MainActivity">: Declares an activity. The android:name attribute specifies the fully qualified class name.
  • <intent-filter>: Declares the types of intents an activity can respond to.
    • <action android:name="android.intent.action.MAIN" />: Indicates this is the main entry point of the application.
    • <category android:name="android.intent.category.LAUNCHER" />: Indicates this activity should appear in the device's app launcher.
  • For activities launched by implicit intents, you add intent filters that match the actions and data types they can handle.

Key Takeaways for Intents and Navigation:

  • Use Explicit Intents to start a specific Activity within your own app.
  • Use Implicit Intents to request an action that can be handled by any compatible app on the device. Always check if an activity can handle the implicit intent using resolveActivity().
  • Use putExtra() and get...Extra() to pass data between Activities.
  • Use startActivityForResult() and onActivityResult() (or the newer Activity Result APIs) to get a result back from a started Activity.
  • Declare all your Activities in the AndroidManifest.xml file.

Intents are a core concept in Android development. Understanding how to use them effectively for navigating between activities and communicating with other app components is crucial for building functional and interconnected Android applications.