Обработка намерений Android между активностями с использованием Kotlin

В этом учебнике мы будем обсуждать Android Intents и реализовывать их с использованием Kotlin в нашем приложении.

Что вы узнаете?

  • Что такое Intents?
  • Типы Intents?
  • Использование Intents между активностями
  • Отправка данных с использованием Android Intents
  • Использование Parcelable и Serializable для передачи объектов
  • Создание кратких интентов

Android Intents

Как следует из названия, Intent – это нечто, что используется для выполнения определенного действия в контексте приложения Android. Intents могут быть использованы для:

  • Запуска новой активности и передачи данных.
  • Запуска фрагментов/взаимодействия между фрагментами.
  • Запуска/завершения службы.
  • Запуска активностей из приемника широковещательных сообщений

В этом учебнике мы главным образом будем рассматривать интенты для управления активностями. Определение интента включает в себя экземпляр текущей активности. Мы устанавливаем имя компонента, которое может быть: полностью определенным именем класса активности, которую нужно вызвать. Этот тип Intent является явным интентом. Действие, такое как URL, номер телефона, местоположение. Это отобразит все доступные приложения этих типов. Это относится к категории неявного интента. Ниже приведен способ создания активности в Kotlin.

val intent = Intent(this, OtherActivity::class.java)
startActivity(intent)

startActivity добавляет OtherActivity в стек активностей и запускает ее. Как наше приложение определяет, какая активность будет вызвана первой? В AndroidManifest.xml мы устанавливаем фильтр намерений с действием android.intent.action.MAIN и категорией android.intent.category.LAUNCHER на первую активность, которая будет запущена при открытии нашего приложения. finish() используется для уничтожения активности и удаления ее из стека.

Флаги намерений

Флаги – это как опции, которые можно установить для намерений, чтобы настроить процесс запуска. Если вы запускаете одну и ту же активность каждый раз, будет создан новый экземпляр и добавлен в стек активностей. Чтобы предотвратить это, вы можете использовать флаги: FLAG_ACTIVITY_SINGLE_TOP – Если установлен, активность не будет запущена, если она уже запущена в вершине стека активностей.

intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP

Точно так же использование флага FLAT_ACTIVITY_CLEAR_TOP не запустит другой экземпляр активности, если он уже существует. Этот флаг удалит все активности выше вызываемой активности и установит ее на вершину стека.

Передача данных через намерения

Чтобы передать данные в новые действия, мы используем пары ключ-значение внутри функции putExtra, putStringArrayListExtra и т. д. putExtra обычно передает базовые типы, такие как Int, Float, Char, Double, Boolean, String, а также IntArray… и так далее.

val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("keyString", "Androidly String data")

Эти поля Extras под капотом оборачиваются в объект Bundle, который в конечном итоге содержит все данные для передачи. Чтобы извлечь данные в другом действии, нам нужно использовать свойство extras над bundles. Извлечение данных в новом действии

val bundle: Bundle? = intent.extras
val string: String? = intent.getString("keyString")
val myArray: ArrayList<String>? = intent.getStringArrayList("myArray")

intent, extras эквивалентны getIntent(), getExtras() в Java. Мы использовали тип с возможным значением Bundle?, чтобы предотвратить NullPointerExceptions, когда данных не существует. Точно так же для данных, полученных с использованием ключей, мы использовали типы с возможным значением, чтобы предотвратить NPE, которые могут возникнуть, если ключ неверен.

Использование передачи данных Parcelable и Serializable

Иногда нам нужно передать полный объект из одного действия в другое. Это невозможно сделать, если мы не реализуем интерфейс Parcelable или Serializable. Разница между Parcelable и Serializable

  • Интерфейс Parcelable является частью Android SDK. Serializable – это стандартный интерфейс Java.
  • В интерфейсе Parcelable необходимо установить все данные, которые нужно передать в объект Parcel, а также переопределить методы writeToParcel() и т.д. В сериализуемом режиме реализация интерфейса достаточна для передачи данных.
  • Parcelable быстрее, чем Serializable.

Отправка данных Parcelable

Котлин предлагает несколько удобных аннотаций, чтобы избавить нас от переопределения метода writeToParcel() для установки данных в Parcelable. Вместо этого мы можем использовать аннотацию @Parcelize, как показано ниже:

@Parcelize
data class Student(
        val name: String = "Anupam",
        val age: Int = 24
) : Parcelable

Примечание: В данный момент в вашем build.gradle необходимо добавить следующий код для работы аннотации @Parcelize:

android {
    androidExtensions {
        experimental = true
    }
//..
....
}

В вашей Activity вы делаете:

val student = Student()
val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("studentData", student)
startActivity(intent)

Отправка данных 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)

Давайте использовать вышеуказанные знания в нашем проекте Android Studio.

Структура проекта

Код макета

Ниже приведен код макета activity_main.xml:

<?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>

Ниже приведен код макета activity_other.xml:

<?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>

Код активности

Ниже приведен код класса MainActivity.kt:

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")
            }
        }
    }

}

В вышеуказанном коде мы использовали кнопки для каждого типа намерений. Мы использовали выражение with Kotlin, чтобы не устанавливать данные в объект intent каждый раз. Кроме того, мы создали три разных намерения, помимо уже обсужденных выше. Намерение для браузера используется для запуска URL, указанного в намерении, в приложении браузера. Оно использует Intent(Intent.ACTION_VIEW, uri). Намерение для местоположения используется для запуска местоположения с координатами lat,lng в приложении карт. Оба этих намерения являются неявными. Наконец, мы использовали обобщенное намерение, в котором мы используем расширенные функции Kotlin и лямбда-выражения для создания сокращенной функции для запуска намерения. Для этого мы используем следующие функции:

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 – это функция расширения, которая ищет функцию более высокого порядка в качестве своего параметра. Благодаря этому мы теперь можем запускать намерения в нескольких строках, например: start<OtherActivity> Ниже приведен код класса OtherActivity.kt.

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 с данными
                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 {
                //Сериализуемые данные
                val blog = getSerializable("blogData") as Blog?
                if (blog != null) {
                    textView.text = "Blog name is ${blog?.name}. Year started: ${blog?.year}"

                }
            }

            bundle.apply {
                //Данные Parcelable
                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()
    }
}

Мы использовали let и apply для обработки nullable типов и предотвращения выполнения bundle.field в каждой строке. Результат работы вышеуказанного приложения приведен ниже: Это завершает данное руководство по намерениям Android в Kotlin. Вы можете загрузить проект по ссылке ниже.

AndroidlyIntents

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