The newIntent() pattern in Android development - An Activity creation factory

Indirect object creation

I’m currently working my way through “Android Programming - The Big Nerd Ranch Guide” (BNR) - (4th Edition completely in Kotlin, by the way). I came across the newIntent() pattern that I had to look over a few times to finally understand. newIntent() pattern was probably adapted from the more widely discussed newInstance() pattern which is even used more broadly outside of Android development. Essentially, newInstance() and newIntent() are types of ‘Factory’ design pattern in an Android context because it’s often related to creating a fragment when multiple arguments are needed to be passed in. However, I’ve also seen this terminology used with the ‘Singleton’ pattern elsewhere on various Java projects.

Creating a new activity with putExtra()

This is best explained with an example Let’s say we have an activity called PrimaryActivity and we’re going to create an Intent to instantiate a new activity called SecondaryActivity. We’re going to use putExtra to send a value on the Intent so that the second activity can use it for whatever reason, let’s say it needs a String.

Looking at the Android Developer Documentation for Intent, we can see that there are a whole bunch of different putExtra methods we can use, depending on the value we want to pass. In this case we want putExtra(String name, String value). The value we want to pass is the second argument. But what about that first argument? Well, it’s described as “The name of the extra data, with package prefix”. You can think of this as a key, value store. The first argument being the key and the data we want to pass as the value. So, that’s interesting - we can’t just pass a value. We have to pass a key and then a value. So what do you use for the key? Well, pretty much any String value will do as long as it’s uniquely meaningful.

Take the advice of what was written there in the Developer Documentation and include the package prefix in the key. The reason for this, it seems, is that when an Intent is created it calls the Android Operating System’s ActivityManager to create the new activity. If other extras are present on the device with the same extra key ID you’ll end up passing the wrong data. Hence, it’s best to keep it unique by prefixing with the package name. Something like com.mycompany.android.myapp.my_string will do just fine.

In the code example below, in PrimaryActivity, let’s say we have a button on screen that launches this new activity on click. So we set up a listener for that button and create a new intent that refers to our new activity, SecondaryActivity. We use putExtra with a String value to pass along.

// PrimaryActivity.kt

class PrimaryActivity.kt : AppCompatActivity() {

    ...
    
    // when a button is pressed, we will start the new activity:
    button.setOnClickListener {
    
        // the string we want to pass to the second activity:    
        val myString = "myString"
        
        // we create the intent that refers to the new activity
        val intent = Intent(this, SecondaryActivity::class.java)
        
        // we put an extra to pass our string to the activity as key, value
        intent.putExtra("com.mycompany.android.myapp.my_string", myString)
        
        startActivity(intent)
    }
}

The newIntent() pattern in Android

At first, I thought this code seemed pretty straight forward. I was then introduced to the newIntent() pattern. The newIntent() pattern is designed like a ‘Factory’ because it enforces the creation of this intent from SecondaryActivity rather than PrimaryActivity. Why? Well, why should we expose the key of this String that we’re passing along? This is really a responsibility of SecondaryActivity. It is SecondaryActivity that is actually using the value! Therefore, we really shouldn’t let PrimaryActivity know the implementation details of the Intent creation.

We can refactor to the following code to implement the pattern.

In the code below, we create a ‘companion object’ in SecondaryActivity , which in Java is like a static method. This static method builds an instance of itself. Then, in PrimaryActivity, rather than calling putExtra and creating a the Intent directly we call the static method and have it created indirectly from SecondaryActivity. This keeps the responsibility of object creation via the Intent to the place where the String argument is actually being used, rather than calling it from PrimaryActivity. This way, PrimaryActivity becomes less of an ‘orchestrator of all the things’ and knows about what activity to create and what arguments to pass, to just knowing about what activity to create without having to know about any arguments to pass. Let the activity that we want to create determine what it needs, rather than telling it explicitly what it needs.

// SecondaryActivity.kt

class SecondaryActivity : AppCompatActivity() {

    ...
    
     companion object {
        fun newIntent(packageContext: Context, myString: String): Intent {
        
            //create an intent and use the myString argument for its value
            return Intent(packageContext, SecondaryActivity::class.java).apply {
                putExtra("com.mycompany.android.myapp.my_string", myString)
            }
        }
    }
    
// PrimaryActivity.kt

class PrimaryActivity.kt : AppCompatActivity() {

    ...
    
    // when a button is pressed, we will start the new activity:
    button.setOnClickListener {
    
        // the string we want to pass to the second activity:    
        val myString = "myString"
        
        // we create the intent indirectly via the newIntent() method
        val intent = SecondaryActivity.newIntent(this@PrimaryActivity, myString)                
        
        startActivity(intent)
    }
}

Potential drawbacks

This allows us to ensure responsibilities are managed closer to where those responsibilities are required. This is a technique/pattern that can be used in some scenarios that might help you improve readability and reduce significant refactoring if you have a whole bunch of putExtra’s around your codebase (which you probably shouldn’t have…). The main potential drawback I see of this technique is that by avoiding having to pass a key, we pass the entire context. I’m still learning about this but from what I understand, passing context everywhere is not necessarily a mark of good design and it could lead to issues later down the track. Do we really want an activity using another activity’s context? However, if it’s for ‘Factory’ method creation, is that ok? There may be some more potential drawbacks here that I’d like to learn more about, however, for the most part, I like the idea that we’re aiming to encapsulate object creation in SecondaryActivity and have that responsibility isolated to itself.

More Information

You can find more information on the newIntent() pattern in Chapter 6 of “Android Programming - The Big Nerd Ranch Guide” (BNR) 4th Edition by Kristin Marsiacano, Brian Gardner, Bill Phillips & Chris Stewart.