Manejo de Intents de Android entre Actividades utilizando Kotlin

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.

AndroidlyIntents

Source:
https://www.digitalocean.com/community/tutorials/android-intent-handling-between-activities-using-kotlin