Simplifying Android Navigation: Intents and Backstack Explained for Seamless Data Sharing

Simplifying Android Navigation: Intents and Backstack Explained for Seamless Data Sharing

Discover how Intents improve Android apps and learn to manage the Backstack for better user journeys.

Intents in Android

Intent class helps us as a communication link between different application components by providing runtime binding between different components.

  • Start an Activity
    A new instance of an activity can be created by passing an intent to the startActivity() method. The intent defines the activity that needs to be started and the necessary data to be communicated.

  • Start a Service

    A service can be initiated and carried out by passing an intent to the startService() method. The intent defines the service that needs to be started and the necessary data to be communicated.

  • Delivering a Broadcast

    A broadcast can be delivered to other applications by passing an intent to the sendBroadcast() method.


Intent Types

  1. Explicit Intent: specifies the application that will satisfy the intent, by supplying either the target app's package name or a fully-qualified component class name.

    Explicit Intent is also used to initiate the different components of our application.

  2. Implicit Intent: doesn't satisfy a specific component, but declares a general action to perform, which can be handled by different application's components. Hence a dialog to pick the application is presented to the user if there's more than one application that can handle the intent.

Implicit Intent Implementation

val openWebBtn : Button = findViewById(R.id.openWebBtn)
val openCallBtn : Button = findViewById(R.id.openCallBtn)
val openCamBtn : Button = findViewById(R.id.openCamBtn)
val shareTextBtn : Button = findViewById(R.id.shareBtn)
val shareText : TextView = findViewById(R.id.textShare)
//assign variable to view components

openWebBtn.setOnClickListener {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.linkedin.com/in/vedant-garg-784a4421b/"))
//Uri.parse() creates URI(Uniform Resource Identifier) object from a given string
//used for web pages, file location, phone number, etc
//uri object used in intents and APIs
    startActivity(intent)
}
openCallBtn.setOnClickListener {
    val intent = Intent(Intent.ACTION_DIAL)
    intent.data = Uri.parse("tel:1234567890")
//pass data seprately
    startActivity(intent)
}
openCamBtn.setOnClickListener {
    val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
//MediaStore is an Android class
//provides access to shared media files like images, videos, and audio stored on the device 
//serves as a content provider that manages and organizes the media files,
//allowing apps to query, insert, delete, or modify media.
    startActivity(intent)
}
shareTextBtn.setOnClickListener {
    val text = shareText.text.toString()
    val intent = Intent(Intent.ACTION_SEND)
    intent.type = "text/plain"
    intent.putExtra("textShare", text)
//sending data with intent 
//parameters : name (to uniquely identify), data (that needs to be shared)
    startActivity(Intent.createChooser(intent,"Send Message Via : "))
//utility method used to display a system-provided chooser dialog.
//parameters : targetIntent, title
}

Implicit Intent follows the following passage :

  1. Activity A is created.

  2. An Intent is created by the Activity A, which is passed to the startActivity() method

  3. Android System searches all the applications for an Intent Filter that matches the Intent.

  4. The system starts the matching Activity by calling its onCreate() method

Intent Filter: an expression in the application's manifest file that specifies the type of intent, an activity, service, or broadcast receiver can respond to. That is, declares the capabilities of its parent components.

Explicit Intent Implementation

Creating Intent :

//MainActivity.kt
var intent = Intent(this@MainActivity, SecondActivity::class.java)
//where this@MainActivity represents our context
//SecondActivity::class.java represents our component to activate
startActivity(intent)
//calls the startActivity method with the intent object

This method calls the SecondActivity.kt Application from our MainActivity.kt file which creates the second_activity.xml during the onCreate() method on our application screen.

Passing Data with Intent :

//MainActivity.kt
var userName = "John"
var age = 18

intent.putExtra("username", userName)
intent.putExtra("userage", age)
//where the first parameter represents the tag to the data
//second parameter represents the data that needs to be passed

Receiving Data :

//SecondActivity.kt
var receivedName = intent.getStringExtra(username).toString()
var receivedAge = intent.getIntExtra(userage,18).toInt()
//where the parameter represents the data tag
//second parameter represents the default value which is optional

Creating a Navigation Icon:

making the MainActivity parent of SecondActivity creates a navigation icon that helps us easily go back to the activity from which we came.

<!--AndroidManifest.xml-->
<activity
    android:name = ".SecondActivity"
    android:exported = "true"
    android:parentActivityName = ".MainActivity"/>

Creating a Contract with Intent (when we need a result from our Intent) :

ActivityResultContract<I, O> is a part of the Activity Result API in Android. The Activity Result API simplifies communication between activities :

//Contract.kt
//package & import..
class Contract : ActivityResultContract<String, String>() {
//as both our i/p & o/p are strings : <String, String>
    override fun createIntent(context: Context, input: String): Intent {
//defines how to create the Intent to launch the activity.
//context : context used to start the activity, input : to send to target activity
        val intent = Intent(context, SecondActivity::class.java)
        intent.putExtra("Request_Result", input)
        return intent
    }
    override fun parseResult(resultCode: Int, intent: Intent?): String = "From Second Activity"
//processes result returned from activity
}
//MainActivity.kt
lateinit var headingText : TextView
private val contract = registerForActivityResult(Contract()){ 
//handling result from contract (intent) -> it
    headingText.text = it
}
override fun onCreate(savedInstanceState: Bundle?) {
    //statements..

    headingText = findViewById(R.id.headingText)
    val nextBtn : Button = findViewById(R.id.nextBtn)

    nextBtn.setOnClickListener {
        contract.launch("I am from Main Activity")
//triggering the contract
    }
}
//SecondActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    //statements..

    val hello : TextView = findViewById(R.id.textView)
    hello.text = intent.getStringExtra("Request_Result")
}

In the above example we simply return a string to the main activity, and we don’t use our activity to return a result. Here is how we can achieve this :

//SecondActivity.kt
class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        //statements..
        val resultIntent = Intent()
        resultIntent.putExtra("Result_Data", "This is the result!")
        setResult(Activity.RESULT_OK, resultIntent)
        finish() // Ends the activity and sends the result
    }
}
//Contract.kt
class Contract : ActivityResultContract<String, String>() {
    override fun createIntent(context: Context, input: String): Intent {
        val intent = Intent(context, SecondActivity::class.java)
        intent.putExtra("Request_Result", input)
        return intent
    }
    override fun parseResult(resultCode: Int, intent: Intent?): String {
        return if (resultCode == Activity.RESULT_OK && intent != null) {
            intent.getStringExtra("Result_Data") ?: "No Data Received"
        } else {
            "Operation Cancelled or No Result"
        }
    }
}

Backstack in Android

The backstack is a system-managed stack that maintains the order of opened activities. It works on the Last-In-First-Out (LIFO) principle. When an activity is launched, it is pushed onto the backstack. When the user presses the back button, the topmost activity is popped from the backstack and destroyed, revealing the previous activity.

val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
  • When switching between activities using Intent (e.g., startActivity()), the new activity is added to the backstack.

  • Pressing the back button removes the topmost activity (e.g., SecondActivity) from the backstack and destroys it.

  • Calling finish() explicitly removes the current activity from the backstack immediately.

The finish() method is used to remove the current activity from the backstack, destroy the current activity, and to the previous activity or close the app if no activities are left in the backstack.

val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
finish() // Closes the current activity (MainActivity) 
//and only the SecondActivity is in the backstack.