In this tutorial, we’ll be discussing Android Intents and implement them using Kotlin in our application.
What Will You Learn?
- What are Intents?
- Types Of Intents?
- Using Intents Between Activities
- Sending Data Using Android Intents
- Using Parcelable and Serializable to pass objects
- Creating shorthand intents
Android Intents
As the name says Intent is something that’s used to perform some action with respect to the flow of the android application. Intents can be used to:
- Starting a new activity and passing some data.
- Starting Fragments/Communicating between fragments.
- Start/End service.
- Launch activities from a broadcast receiver
In this tutorial, we’ll be looking mainly at intents to handle activities.
An intent definition mainly consists of an instance of the current activity. We set the component name which can be:
The fully qualified class name of the activity to be called. This type of Intent is an explicit intent.
An action such as URL, phone number, location. It’ll display all the available applications of those types.
This falls under the implicit intent category.
In Kotlin, following is the way to create an activity.
1 2 3 4 |
val intent = Intent(this, OtherActivity::class.java) startActivity(intent) |
startActivity
would add OtherActivity
on the activity stack and launch it.
How does our Application, realise which activity is the first to be invoked?
In the AndroidManifest.xml we set the intent filter with the action android.intent.action.MAIN
and category android.intent.category.LAUNCHER
on the first activity to be launched when our application opens.
finish()
is used to destroy an activity and remove it from the stack.
Intent Flags
Flags are like options that can be set on intents to customise the launch process.
If you start the same activity everytime, a new instance would be created and added onto the activity stack
To prevent this, you can use the flags:
FLAG_ACTIVITY_SINGLE_TOP
– If set, the activity will not be launched if it is already running at the top of the activity stack.
1 2 3 |
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP |
Similarly using a flag FLAT_ACTIVITY_CLEAR_TOP
would not launch another instance of the activity if it already exists. This flag would clear all the activities above the activity that’s called and set it on the top of the stack.
Passing Data Through Intents
To pass data onto the new activities we use key value pairs inside the function putExtra
, putStringArrayListExtra
etc.
putExtra generally passes the basic types such as Int, Float, Char, Double, Boolean, String along with IntArray…. etc.
1 2 3 4 |
val intent = Intent(this, OtherActivity::class.java) intent.putExtra("keyString", "Androidly String data") |
These Extras fields are under the hood wrapped into the Bundle
object which ultimately holds all the data to be passed.
To retrieve the data in the other activity, we need to use the extras
property over the bundles
.
Retrieving Data in the new Activity
1 2 3 4 5 |
val bundle: Bundle? = intent.extras val string: String? = intent.getString("keyString") val myArray: ArrayList<String>? = intent.getStringArrayList("myArray") |
intent
, extras
are equivalent to getIntent()
, getExtras()
in Java.
We’ve used a nullable type Bundle?
to prevent NullPointerExceptions
when not data exists. Similarly, for the data that’s fetched using the keys, we’ve used the nullable types to prevent NPE that can occur when the key is incorrect.
Using Parcelable and Serializable Data
Sometimes we need to pass a complete object from one activity to another. It’s not possible to do so unless we implement the Parcelable or Serializable interface.
Difference between Parcelable and Serializable
- Parcelable interface is a part of the Android SDK. Serializable is a standard interface of Java.
- In Parcelable you need to set all of the data you need to pass in a Parcel object and also override the writeToParcel() methods etc. In serializable implementing the interface is sufficient to pass the data.
- Parcelable is faster than Serializable.
Sending Parcelable Data
Kotlin comes up with some handy annotations to save us from overriding the writeToParcel() method to set the data on the Parcelable. Instead, we can use @Parcelize annotation as shown below:
1 2 3 4 5 6 7 |
@Parcelize data class Student( val name: String = "Anupam", val age: Int = 24 ) : Parcelable |
Note: Currently in your build.gradle you must add the following code for the @Parcelize annotation to work:
1 2 3 4 5 6 7 8 9 |
android { androidExtensions { experimental = true } //.. .... } |
In your Activity you do:
1 2 3 4 5 6 |
val student = Student() val intent = Intent(this, OtherActivity::class.java) intent.putExtra("studentData", student) startActivity(intent) |
Sending Serializable Data
1 2 3 4 5 6 7 |
data class Blog(val name: String = "Androidly", val year: Int = 2018) : Serializable val blog = Blog("a", 1) val intent = Intent(this, OtherActivity::class.java) intent.putExtra("blogData", blog as Serializable) startActivity(intent) |
Let’s use over above knowledge in our Android Studio Project.
Project Structure
Layout Code
The code for the activity_main.xml
layout is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/btnSimpleIntent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="SIMPLE INTENT" /> <Button android:id="@+id/btnSimpleIntentAndData" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="SIMPLE INTENT WITH DATA" /> <Button android:id="@+id/btnParcelableIntent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Parcelable Intent" /> <Button android:id="@+id/btnSerializableIntent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Serializable Intent" /> <Button android:id="@+id/btnBrowserIntent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Browser Intent" /> <Button android:id="@+id/btnMapsIntent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Maps Intent" /> <Button android:id="@+id/btnGenericIntent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Generic Intent" /> </LinearLayout> |
The code for the activity_other.xml layout is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Intent Data goes here" /> </LinearLayout> |
Activity Code
The code for the MainActivity.kt class is given below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
package net.androidly.androidlyintents import android.app.Activity import android.content.ComponentName import android.content.Context import android.content.Intent import android.net.Uri import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.os.Parcelable import android.view.View import android.widget.Toast import android.widget.Toast.LENGTH_LONG import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.activity_main.* import java.io.Serializable @Parcelize data class Student( val name: String = "Anupam", val age: Int = 24 ) : Parcelable data class Blog(val name: String = "Androidly", val year: Int = 2018) : Serializable class MainActivity : AppCompatActivity(), View.OnClickListener { fun Context.gotoClass(targetType: Class<*>) = ComponentName(this, targetType) fun Context.startActivity(f: Intent.() -> Unit): Unit = Intent().apply(f).run(this::startActivity) inline fun <reified T : Activity> Context.start( noinline createIntent: Intent.() -> Unit = {} ) = startActivity { component = gotoClass(T::class.java) createIntent(this) } var arrayList = ArrayList<String>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btnSimpleIntent.setOnClickListener(this) btnSimpleIntentAndData.setOnClickListener(this) btnParcelableIntent.setOnClickListener(this) btnSerializableIntent.setOnClickListener(this) btnBrowserIntent.setOnClickListener(this) btnMapsIntent.setOnClickListener(this) btnGenericIntent.setOnClickListener(this) arrayList.add("Androidly") arrayList.add("Android") arrayList.add("Intents") } override fun onClick(v: View?) { when (v?.id) { R.id.btnSimpleIntent -> { val intent = Intent(this, OtherActivity::class.java) startActivity(intent) } R.id.btnSimpleIntentAndData -> { val intent = Intent(this, OtherActivity::class.java) with(intent) { putExtra("keyString", "Androidly String data") putStringArrayListExtra("arrayList", arrayList) putExtra("keyBoolean", true) putExtra("keyFloat", 1.2f) } startActivity(intent) } R.id.btnParcelableIntent -> { val student = Student() val intent = Intent(this, OtherActivity::class.java) intent.putExtra("studentData", student) startActivity(intent) } R.id.btnSerializableIntent -> { val blog = Blog("a", 1) val intent = Intent(this, OtherActivity::class.java) intent.putExtra("blogData", blog as Serializable) startActivity(intent) } R.id.btnBrowserIntent -> { val url = "https://www.androidly.net" val uri = Uri.parse(url) val intent = Intent(Intent.ACTION_VIEW, uri) if (intent.resolveActivity(packageManager) != null) { startActivity(intent) } else { Toast.makeText(applicationContext, "No application found", LENGTH_LONG).show() } } R.id.btnMapsIntent -> { val loc = "12.9538477,77.3507442" val addressUri = Uri.parse("geo:0,0?q=" + loc) val intent = Intent(Intent.ACTION_VIEW, addressUri) if (intent.resolveActivity(packageManager) != null) { startActivity(intent) } else { Toast.makeText(applicationContext, "No application found", LENGTH_LONG).show() } } else -> start<OtherActivity> { putExtra("keyString", "Androidly Generic Intent") } } } } |
In the above code, we’ve used Buttons for each type of Intent.
We’ve used Kotlin’s with
expression to prevent setting data over the intent
object every time.
Besides, we’ve created three different intents apart from the ones already discussed above.
A browser intent is used to launch the url present in the intent in the browser app.
It uses Intent(Intent.ACTION_VIEW, uri)
.
A location intent is used to launch the lat,lng location in the maps application.
Both of these are implicit intents.
Lastly, we’ve used a generic intent in which we use the Kotlin’s extension functions and lambda expressions to create a shorthand function to launch an intent.
For this we use the following functions:
1 2 3 4 5 6 7 8 9 10 11 12 |
fun Context.gotoClass(targetType: Class<*>) = ComponentName(this, targetType) fun Context.startActivity(createIntent: Intent.() -> Unit): Unit = Intent().apply(createIntent).run(this::startActivity) inline fun <reified T : Activity> Context.start( noinline createIntent: Intent.() -> Unit = {} ) = startActivity { component = gotoClass(T::class.java) createIntent(this) } |
startActivity is an exension function which looks for a higher order function as it’s parameter.
Thanks to this, we can now launch intents in as few lines as:
start<OtherActivity>
The code for the OtherActivity.kt class is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package net.androidly.androidlyintents import android.content.Context import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.widget.Toast import kotlinx.android.synthetic.main.activity_other.* class OtherActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_other) val bundle: Bundle? = intent.extras bundle?.let { bundle.apply { //Intent with data val string: String? = getString("keyString") textView.text = string val myArray: ArrayList<String>? = getStringArrayList("myArray") showToast(message = "MyArrayList size:${myArray?.size}") val arrayList: ArrayList<String>? = getStringArrayList("arrayList") showToast(message = "ArrayList size:${arrayList?.size}") val float: Float? = bundle.get("keyFloat") as Float? var boolean = bundle.get("boolean") as? Boolean showToast(message = "Float data is:$float") showToast(message = "Boolean data is:$boolean") boolean = bundle.get("keyBoolean") as? Boolean showToast(message = "Boolean correct key data is:$boolean") } bundle.apply { //Serializable Data val blog = getSerializable("blogData") as Blog? if (blog != null) { textView.text = "Blog name is ${blog?.name}. Year started: ${blog?.year}" } } bundle.apply { //Parcelable Data val student: Student? = getParcelable("studentData") if (student != null) { textView.text = "Name is ${student?.name}. Age: ${student?.age}" } } } } private fun showToast(context: Context = applicationContext, message: String, duration: Int = Toast.LENGTH_SHORT) { if (!message.contains("null")) Toast.makeText(context, message, duration).show() } } |
We’ve used let
and apply
to handle nullable types and prevent doing bundle.field in every line.
The output of the above application in action is given below:
This brings an end to this tutorial on Android intents in Kotlin. You can download the project from the link below.