В этом учебнике мы будем обсуждать 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. Вы можете загрузить проект по ссылке ниже.