Cómo enviar al usuario a otra app

Una de las funciones más importantes de Android es la capacidad que tienen las apps de enviar al usuario a otra app basándose en una "acción" que desea realizar. Por ejemplo, si tu app tiene la dirección de una empresa que deseas mostrar en un mapa, no necesitas crear una actividad para mostrar mapas en tu app. En cambio, puedes crear una solicitud para ver la dirección utilizando un Intent. Luego, el sistema Android inicia una app que puede mostrar la dirección en un mapa.

Como se explicó en la primera clase, Cómo crear tu primera app, debes utilizar intents para navegar entre las actividades de tu propia app. En general, debes utilizar un intent explícito, que define el nombre exacto de la clase de componente que deseas iniciar. Sin embargo, si deseas que otra app realice una acción, como "ver un mapa", debes utilizar un intent implícito.

En esta lección, se muestra cómo crear un intent implícito para una acción determinada, y cómo utilizarla para iniciar una actividad que realice la acción en otra app. También mira el video incorporado aquí para entender por qué es importante que incluyas verificaciones del tiempo de ejecución para tus intents implícitos.

Cómo crear un intent implícito

Los intents implícitos no indican el nombre de clase del componente que se ha de iniciar, sino una acción que se ha de realizar. La acción especifica lo que deseas hacer, como ver, editar, enviar y obtener algo. Los intents también suelen incluir datos asociados a la acción, como la dirección que quieres ver o el mensaje de correo electrónico que deseas enviar. Según el intent que quieras crear, los datos pueden ser un Uri o uno de varios otros tipos de datos. También es posible que el intent no necesite información.

Si tu información es un Uri, existe un simple constructor Intent() que puedes usar para definir la acción y los datos.

Por ejemplo, a continuación se muestra la manera de crear un intent a fin de iniciar una llamada telefónica usando los datos de un Uri para especificar el número de teléfono:

Kotlin

val callIntent: Intent = Uri.parse("tel:5551234").let { number ->
    Intent(Intent.ACTION_DIAL, number)
}

Java

Uri number = Uri.parse("tel:5551234");
Intent callIntent = new Intent(Intent.ACTION_DIAL, number);

Cuando tu app invoca este intent llamando a startActivity(), la app de teléfono inicia una llamada al número especificado.

A continuación, se muestran otros dos intents, así como su acción y pares de datos Uri:

  • Cómo ver un mapa:

    Kotlin

    // Map point based on address
    val mapIntent: Intent = Uri.parse(
            "geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California"
    ).let { location ->
        // Or map point based on latitude/longitude
        // Uri location = Uri.parse("geo:37.422219,-122.08364?z=14"); // z param is zoom level
        Intent(Intent.ACTION_VIEW, location)
    }
    

    Java

    // Map point based on address
    Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
    // Or map point based on latitude/longitude
    // Uri location = Uri.parse("geo:37.422219,-122.08364?z=14"); // z param is zoom level
    Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
    
  • Cómo ver una página web:

    Kotlin

    val webIntent: Intent = Uri.parse("http://www.android.com").let { webpage ->
        Intent(Intent.ACTION_VIEW, webpage)
    }
    

    Java

    Uri webpage = Uri.parse("http://www.android.com");
    Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);
    

Otras clases de intents implícitos requieren datos "adicionales" que proporcionan diferentes tipos de datos, como una string. Puedes agregar uno o más datos adicionales usando los diferentes métodos putExtra().

De forma predeterminada, el sistema determina el tipo de MIME requerido por el intent sobre la base de los datos del Uri incluido. Si no incluyes un Uri en el intent, deberás utilizar un setType() para especificar el tipo de datos relacionados con el intent. Establecer el tipo de MIME también especifica qué clases de actividades debe recibir el intent.

A continuación, se incluyen otros intents que agregan datos adicionales para especificar la acción deseada:

  • Cómo enviar un correo electrónico con un archivo adjunto:

    Kotlin

    Intent(Intent.ACTION_SEND).apply {
        // The intent does not have a URI, so declare the "text/plain" MIME type
        type = HTTP.PLAIN_TEXT_TYPE
        putExtra(Intent.EXTRA_EMAIL, arrayOf("jon@example.com")) // recipients
        putExtra(Intent.EXTRA_SUBJECT, "Email subject")
        putExtra(Intent.EXTRA_TEXT, "Email message text")
        putExtra(Intent.EXTRA_STREAM, Uri.parse("content://path/to/email/attachment"))
        // You can also attach multiple items by passing an ArrayList of Uris
    }
    

    Java

    Intent emailIntent = new Intent(Intent.ACTION_SEND);
    // The intent does not have a URI, so declare the "text/plain" MIME type
    emailIntent.setType(HTTP.PLAIN_TEXT_TYPE);
    emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"jon@example.com"}); // recipients
    emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Email subject");
    emailIntent.putExtra(Intent.EXTRA_TEXT, "Email message text");
    emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://path/to/email/attachment"));
    // You can also attach multiple items by passing an ArrayList of Uris
    
  • Cómo crear un evento de calendario:

    Kotlin

    Intent(Intent.ACTION_INSERT, Events.CONTENT_URI).apply {
        val beginTime: Calendar = Calendar.getInstance().apply {
            set(2012, 0, 19, 7, 30)
        }
        val endTime = Calendar.getInstance().apply {
            set(2012, 0, 19, 10, 30)
        }
        putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.timeInMillis)
        putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.timeInMillis)
        putExtra(Events.TITLE, "Ninja class")
        putExtra(Events.EVENT_LOCATION, "Secret dojo")
    }
    

    Java

    Intent calendarIntent = new Intent(Intent.ACTION_INSERT, Events.CONTENT_URI);
    Calendar beginTime = Calendar.getInstance();
    beginTime.set(2012, 0, 19, 7, 30);
    Calendar endTime = Calendar.getInstance();
    endTime.set(2012, 0, 19, 10, 30);
    calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis());
    calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis());
    calendarIntent.putExtra(Events.TITLE, "Ninja class");
    calendarIntent.putExtra(Events.EVENT_LOCATION, "Secret dojo");
    

    Nota: Este intent para crear un evento de calendario solo se admite en el nivel de API 14 y versiones posteriores.

Nota: Es importante que definas tu Intent de la forma más específica posible. Por ejemplo, si deseas mostrar una imagen usando el intent ACTION_VIEW, debes especificar un tipo de MIME image/*. Esto evita que las apps que pueden "ver" otros tipos de datos (como una de mapas) sean iniciadas por el intent.

Cómo verificar que exista una app para recibir el intent

Si bien la plataforma Android garantiza que ciertos intents se podrán ejecutar con una de las apps integradas (como Teléfono, Correo electrónico o Calendario), siempre debes incluir un paso de verificación antes de invocar un intent.

Advertencia: Si invocas un intent y no hay una app disponible en el dispositivo que pueda manejar el intent, la app fallará.

Para verificar que haya una actividad disponible que pueda responder al intent, invoca a queryIntentActivities() a fin de obtener una lista de actividades capaces de manejar tu Intent. Si el objeto List no está vacío, puedes usar el intent de forma segura. Por ejemplo:

Kotlin

val activities: List<ResolveInfo> = packageManager.queryIntentActivities(
        intent,
        PackageManager.MATCH_DEFAULT_ONLY
)
val isIntentSafe: Boolean = activities.isNotEmpty()

Java

PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent,
        PackageManager.MATCH_DEFAULT_ONLY);
boolean isIntentSafe = activities.size() > 0;

Si isIntentSafe es true, significa que al menos una app responderá al intent. Si es false, significa que no existen apps para manejar el intent.

Nota: Debes realizar esta comprobación cuando tu actividad se inicia por primera vez, en caso de que necesites inhabilitar la función que utiliza el intent antes de que el usuario trate de utilizarlo. Si conoces una app específica que puede manejar el intent, también puedes proporcionar un vínculo para que el usuario descargue la app (consulta el método para agregar un vínculo a tu producto en Google Play).

Cómo iniciar una actividad con el intent

Figura 1: Ejemplo del diálogo de selección que aparece cuando más de una app puede controlar un intent.

Una vez que hayas creado tu Intent y establecido la información adicional, invoca a startActivity() para enviar el elemento al sistema. Si el sistema identifica más de una actividad que puede controlar el intent, muestra un diálogo (también conocido como "diálogo de desambiguación") para que el usuario seleccione la app que quiera usar, como se muestra en la figura 1. Si hay solamente una actividad que maneja el intent, el sistema la iniciará inmediatamente.

Kotlin

startActivity(intent)

Java

startActivity(intent);

A continuación, se incluye un ejemplo completo que muestra cómo crear un intent para ver un mapa, verificar que exista una app para manejar el intent y luego iniciarla:

Kotlin

// Build the intent
val location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California")
val mapIntent = Intent(Intent.ACTION_VIEW, location)

// Verify it resolves
val activities: List<ResolveInfo> = packageManager.queryIntentActivities(mapIntent, 0)
val isIntentSafe: Boolean = activities.isNotEmpty()

// Start an activity if it's safe
if (isIntentSafe) {
    startActivity(mapIntent)
}

Java

// Build the intent
Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);

// Verify it resolves
PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0);
boolean isIntentSafe = activities.size() > 0;

// Start an activity if it's safe
if (isIntentSafe) {
    startActivity(mapIntent);
}

Cómo mostrar un selector de apps

Figura 2: Diálogo de selección.

Ten presente que cuando inicias una actividad transfiriendo tu Intent a startActivity() y existe más de una app que responde al intent, el usuario puede seleccionar la app que se usará de forma predeterminada (seleccionando la casilla de verificación de la parte inferior del diálogo; consulta la figura 1). Esto es útil cuando se realiza una acción para la cual el usuario generalmente desea utilizar siempre la misma app, como al abrir una página web (los usuarios suelen utilizar un solo navegador) o tomar una foto (los usuarios suelen preferir una cámara).

Sin embargo, si la acción que se realizará puede controlarse a través de varias apps y es posible que el usuario prefiera usar una app diferente cada vez (como una acción de "compartir", para la cual los usuarios pueden tener varias apps mediante las cuales deseen compartir un elemento), debes mostrar explícitamente un diálogo de selección como el de la figura 2. Este obliga al usuario a seleccionar la app que desea usar para la acción en cada ocasión (el usuario no puede seleccionar una app predeterminada para la acción).

Para mostrar el diálogo de selección, crea un Intent usando createChooser() y transfiérelo a startActivity(). Por ejemplo:

Kotlin

val intent = Intent(Intent.ACTION_SEND)
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
val title = resources.getString(R.string.chooser_title)
// Create intent to show chooser
val chooser = Intent.createChooser(intent, title)

// Verify the intent will resolve to at least one activity
if (intent.resolveActivity(packageManager) != null) {
    startActivity(chooser)
}

Java

Intent intent = new Intent(Intent.ACTION_SEND);
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show chooser
Intent chooser = Intent.createChooser(intent, title);

// Verify the intent will resolve to at least one activity
if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

Aquí se muestra un diálogo con una lista de apps que responden al intent transferido al método createChooser(), con el texto proporcionado como título del diálogo.