En este tutorial, estaremos discutiendo sobre Android Intents e implementándolos usando Kotlin en nuestra aplicación.
¿Qué aprenderás?
- ¿Qué son los Intents?
- Tipos de Intents?
- Uso de Intents entre actividades
- Envío de datos usando Android Intents
- Uso de Parcelable y Serializable para pasar objetos
- Creación de intents abreviados
Android Intents
Como su nombre indica, Intent es algo que se utiliza para realizar alguna acción con respecto al flujo de la aplicación de Android. Los Intents se pueden usar para:
- Iniciar una nueva actividad y pasar algunos datos.
- Iniciar Fragmentos/Comunicarse entre fragmentos.
- Iniciar/Finalizar un servicio.
- Arrancar actividades desde un receptor de difusión
En este tutorial, nos centraremos principalmente en los Intents para manejar actividades. La definición de un intent consiste principalmente en una instancia de la actividad actual. Establecemos el nombre del componente que puede ser: El nombre de clase completamente calificado de la actividad a llamar. Este tipo de Intent es un intent explícito. Una acción como URL, número de teléfono, ubicación. Mostrará todas las aplicaciones disponibles de esos tipos. Esto cae en la categoría de intent implícito. En Kotlin, la siguiente es la forma de crear una actividad.
val intent = Intent(this, OtherActivity::class.java)
startActivity(intent)
startActivity
agregaría OtherActivity
en la pila de actividades y la lanzaría. ¿Cómo sabe nuestra aplicación qué actividad es la primera en ser invocada? En el AndroidManifest.xml configuramos el filtro de intención con la acción android.intent.action.MAIN
y la categoría android.intent.category.LAUNCHER
en la primera actividad que se lanzará cuando se abra nuestra aplicación. finish()
se utiliza para destruir una actividad y eliminarla de la pila.
Bandera de Intención
Las banderas son como opciones que se pueden establecer en las intenciones para personalizar el proceso de lanzamiento. Si se inicia la misma actividad cada vez, se crearía una nueva instancia y se agregaría a la pila de actividades. Para evitar esto, se pueden usar las banderas: FLAG_ACTIVITY_SINGLE_TOP
: si está establecida, la actividad no se lanzará si ya se está ejecutando en la parte superior de la pila de actividades.
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
De manera similar, el uso de una bandera FLAG_ACTIVITY_CLEAR_TOP
no lanzaría otra instancia de la actividad si ya existe. Esta bandera borraría todas las actividades por encima de la actividad que se llama y la colocaría en la parte superior de la pila.
Pasando Datos a Través de Intenciones
Para pasar datos a las nuevas actividades, utilizamos pares clave-valor dentro de la función putExtra
, putStringArrayListExtra
, etc. putExtra generalmente pasa tipos básicos como Int, Float, Char, Double, Boolean, String junto con IntArray… etc.
val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("keyString", "Androidly String data")
Estos campos Extras están envueltos en el objeto Bundle
, que finalmente contiene todos los datos que se van a pasar. Para recuperar los datos en la otra actividad, necesitamos usar la propiedad extras
sobre los bundles
. Recuperar datos en la nueva actividad
val bundle: Bundle? = intent.extras
val string: String? = intent.getString("keyString")
val myArray: ArrayList<String>? = intent.getStringArrayList("myArray")
intent
, extras
son equivalentes a getIntent()
, getExtras()
en Java. Hemos utilizado un tipo nullable Bundle?
para evitar NullPointerExceptions
cuando no existen datos. De manera similar, para los datos que se obtienen utilizando las claves, hemos utilizado tipos nullable para evitar NPE que pueden ocurrir cuando la clave es incorrecta.
Uso de datos Parcelable y Serializable
A veces necesitamos pasar un objeto completo de una actividad a otra. No es posible hacerlo a menos que implementemos la interfaz Parcelable o Serializable. Diferencia entre Parcelable y Serializable
- La interfaz Parcelable es parte del SDK de Android. Serializable es una interfaz estándar de Java.
- En Parcelable, necesitas establecer todos los datos que necesitas pasar en un objeto Parcel y también anular los métodos writeToParcel(), etc. En Serializable, implementar la interfaz es suficiente para pasar los datos.
- Parcelable es más rápido que Serializable.
Envío de datos Parcelable
Kotlin ofrece algunas anotaciones útiles para ahorrarnos tener que anular el método writeToParcel() para establecer los datos en el Parcelable. En su lugar, podemos usar la anotación @Parcelize como se muestra a continuación:
@Parcelize
data class Student(
val name: String = "Anupam",
val age: Int = 24
) : Parcelable
Nota: Actualmente, en su build.gradle debe agregar el siguiente código para que funcione la anotación @Parcelize:
android {
androidExtensions {
experimental = true
}
//..
....
}
En su Actividad, haga lo siguiente:
val student = Student()
val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("studentData", student)
startActivity(intent)
Envío de datos Serializable
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)
Utilicemos los conocimientos anteriores en nuestro proyecto de Android Studio.
Estructura del proyecto
Código de Diseño
El código para el diseño activity_main.xml
se proporciona a continuación:
<?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>
El código para el diseño activity_other.xml se proporciona a continuación:
<?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>
Código de Actividad
El código para la clase MainActivity.kt se proporciona a continuación:
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")
}
}
}
}
En el código anterior, hemos utilizado Botones para cada tipo de Intent. Hemos utilizado la expresión with
de Kotlin para evitar establecer datos sobre el objeto intent
cada vez. Además, hemos creado tres intenciones diferentes además de las ya discutidas anteriormente. Una intención de navegador se utiliza para abrir la URL presente en la intención en la aplicación del navegador. Utiliza Intent(Intent.ACTION_VIEW, uri)
. Una intención de ubicación se utiliza para abrir la ubicación lat,lng en la aplicación de mapas. Ambas son intenciones implícitas. Por último, hemos utilizado una intención genérica en la que utilizamos las funciones de extensión de Kotlin y expresiones lambda para crear una función abreviada para lanzar una intención. Para esto, utilizamos las siguientes funciones:
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 es una función de extensión que busca una función de orden superior como parámetro. Gracias a esto, ahora podemos lanzar intents en tan pocas líneas como: start<OtherActivity>
El código para la clase OtherActivity.kt se muestra a continuación.
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 con datos
val string: String? = getString("keyString")
textView.text = string
val myArray: ArrayList? = getStringArrayList("myArray")
showToast(message = "MyArrayList size:${myArray?.size}")
val arrayList: ArrayList? = 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 {
//Datos serializables
val blog = getSerializable("blogData") as Blog?
if (blog != null) {
textView.text = "Blog name is ${blog?.name}. Year started: ${blog?.year}"
}
}
bundle.apply {
//Datos parcelables
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()
}
}
Hemos utilizado let
y apply
para manejar tipos nulos y evitar hacer bundle.field en cada línea. El resultado de la aplicación anterior en acción se muestra a continuación: Esto concluye este tutorial sobre intents de Android en Kotlin. Puedes descargar el proyecto desde el siguiente enlace.