3.2 Explicit Intents

We know that activities exist and we know that each app can have multiple, so how do we actually transition between different Activity classes and display a new screen to the user? The answer lies in explicit intents.

Explicit intents are used to request an action from another app component, and at its most basic level calls the onCreate() method of another Activity class within your project. In order to use these, we first need to make sure that we have at least two activities in our project. Activities can be created through File > New > Activity > Empty Activity, and after creating the Activity class, Android Studio automatically create a corresponding XML and class file for you.

To transition to this new activity, we first must create an intent to transition to the activity with:

var intent = Intent(this, TargetActivity::class.java)

You might have noticed that the TargetActivity class is tagged with .java. This is a backwards compatibility system where Android's Intent class was built in Java and expects a Java class, and even if TargetActivity is written in Kotlin, you'll want to add that bit when creating the Intent. Afterwards, whenever we want to actually transition (perhaps in the on click listener of a button), we launch the intent by running startActivity(intent) . To close an activity, call finish() from the corresponding Activity and Android Studio will return the control and display to the last activity that hasn’t been finished, or will exit the application if no such activity exists.

Sending Data Through Intents

Sometimes it is necessary to pass information from one activity to another -- this is also done with intents. We can attach key-value pairs to our intent in the same Activity which we declare the Intent, using the method putExtra(key, value) . This stores all of our data in something called a bundle!

As shown below, when you first create an intent, you'll need to specify the current activity as well as the target activity. You will also have the opportunity to put data inside an "extra" which send the data from the current activity to the target activity.

var intent = Intent(CurrentActivity::class.java, TargetActivity::class.java)
intent.putExtra("EATERY_NAME", "Okenshields")
startActivity(intent)

Then, in the our target activity, we can recover the associated value using the key.

// eateryName = "Okenshields"
var eateryName = getIntent().extras?.getString("EATERY_NAME")

Sending information between activities becomes much more important when the information you want to display in a specific activity changes based on circumstantial actions. Once again returning to this example with Eatery:

After pressing an Eatery card on the left, an intent is used to transition to the CampusMenuActivity, and the data transferred with the intent includes the name, closing time, and status of each Eatery. Thus, we can dynamically display the correct information in CampusMenuActivity by passing in new values after clicking on different Eatery cards.

Getting a result from an activity

We can leverage the power of explicit intents and intents in general!

First we introduce a new method: registerForActivityResult(ActivityResultContract, ActivityResultCallback). This method registers a request to start an activity for result, designated by some given contract. These contracts are just standard/common activity calls in Android (like taking a photo or opening a document). The callback allows us to immediately act upon the results of said contracts (so if I launch taking a photo, the callback result would be said photo!)

The contract we will focus on is ActivityResultContracts.StartActivityForResult which takes a raw Intent as an input (allowing us to launch different activities) and ActivityResult as an output (allowing us to read data the activities send us!)

We start off by creating our launcher:

MainActivity.kt
class MainActivity : AppCompatActivity() {
    private lateinit var intentLauncher: ActivityResultLauncher<Intent>
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        intentLauncher = registerForActivityResult(
            ActivityResultContracts.StartActivityForResult()) { result ->
                ...
            }
        ...
    }
}

We can then launch activities as so:

MainActivity.kt
val intent = Intent(this, TargetActivity::class.java)
// Use intent.putExtra if you need to pass in some data to the TargetActivity
intent.putExtra(...)
intentLauncher.launch(intent)

To pass data back to our MainActivity, we use setResult

TargetActivity.kt
...
// Can put any data we want to pass along back to the MainActivity with setResult
// with an intent. 
val intent = Intent()
intent.putExtra("hi", "hi")
setResult(SOME_UNIQUE_INTEGER, intent)
// finish pops the current activity from the stack, closes it, and pushes in the previous
// activity
finish()
...

We can also intercept onBackPressed and send data back to our launching activity if a user tries to hit the back button or swipe back:

TargetActivity.kt
override fun onBackPressed() {
    val intent = Intent()
    intent.putExtra("hi", "hi")
    setResult(SOME_UNIQUE_INTEGER, intent)
    super.onBackPressed()
}

SOME_UNIQUE_INTEGER is our result code! Make sure it's uniform across both activities!

A useful place to add your result code is in the companion object of your MainActivity (ensures consistency and uniqueness):

MainActivity
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
         ...
     }
     
     companion object {
         // Can be referenced both within the MainActivity and other
         // Activities (e.g. your TargetActivity)
         const val SOME_UNIQUE_INTEGER = 1
     }
 }

We now have to upgrade our launcher a little to account for this new possible result:

TargetActivity.kt
private lateinit var intentLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    intentLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            // Make sure the resultCode is the same as what was passed
            // in from your other activity! We can have different
            // resultCodes for different activites that we launch
            // in case we want to do something different for each (like
            // in the case where the extra keys aren't the same across
            // intents from the result, etc).
            if (result.resultCode == SOME_UNIQUE_INTEGER) {
                val hi = result.data?.extras?.get("hi")
                ...
            }
        }
    ...
}

With this we can call as many Activities for result as you wish and have separate callback for each!

When the launching activity is returned back with some results set, the code in our intentLauncher initializer (formally called a callback) will trigger!

Last updated