Kotlinを使用したアクティビティ間のAndroidインテントの処理方法
このチュートリアルでは、Androidのインテントについて説明し、アプリケーションでKotlinを使ってそれらを実装します。
あなたは何を学びますか?(Anata wa nani o manabimasu ka?)
- What are Intents?
- Types Of Intents?
- Using Intents Between Activities
- Sending Data Using Android Intents
- Using Parcelable and Serializable to pass objects
- Creating shorthand intents
アンドロイドのインテント
名前の通り、Intent(インテント)はAndroidアプリケーションのフローに関連したアクションを実行するために使用されるものです。Intentsを使うことで、以下のことをすることができます。
- Starting a new activity and passing some data.
- Starting Fragments/Communicating between fragments.
- Start/End service.
- Launch activities from a broadcast receiver
このチュートリアルでは、主にアクティビティを処理するためのインテントについて見ていきます。インテントの定義は、現在のアクティビティのインスタンスから主に構成されています。呼ばれるアクティビティの完全修飾クラス名を設定します。このタイプのインテントは明示的なインテントです。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を使用しても、既に存在する場合は別のインスタンスを起動しません。このフラグは、呼び出されたアクティビティの上にあるすべてのアクティビティをクリアし、スタックの一番上に設定します。
インテントを介してデータの受け渡しを行う
val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("keyString", "Androidly String data")
これらのエクストラフィールドは、最終的にはすべてのデータを保持するバンドルオブジェクトに包まれています。他のアクティビティでデータを取得するためには、バンドルの上にエクストラプロパティを使用する必要があります。新しいアクティビティでデータを取得する
val bundle: Bundle? = intent.extras
val string: String? = intent.getString("keyString")
val myArray: ArrayList<String>? = intent.getStringArrayList("myArray")
意図は、JavaのgetIntent()、getExtras()と同等です。データが存在しない場合にNullPointerExceptionを防ぐため、nullableなタイプのBundle?を使用しています。同様に、キーを使用してフェッチされるデータに対しても、キーが正しくない場合に発生する可能性のあるNPEを防ぐためにnullableなタイプを使用しています。
ParcelableとSerializableデータを使用しています。
時には、1つのアクティビティから別のアクティビティに完全なオブジェクトを渡す必要があります。ParcelableまたはSerializableインターフェースを実装しない限り、それは不可能です。ParcelableとSerializableの違い
- Parcelable interface is a part of the Android SDK. Serializable is a standard interface of Java.
- In Parcelable you need to set all of the data you need to pass in a Parcel object and also override the writeToParcel() methods etc. In serializable implementing the interface is sufficient to pass the data.
- Parcelable is faster than Serializable.
Parcelable データの送信
Kotlinには、writeToParcel()メソッドをオーバーライドしてParcelableにデータを設定する手間を省くための便利なアノテーションがいくつか用意されています。代わりに、以下に示すように@Parcelizeアノテーションを使用することができます。
@Parcelize
data class Student(
val name: String = "Anupam",
val age: Int = 24
) : Parcelable
注: 現在の build.gradle には、@Parcelize アノテーションが正常に動作するために、以下のコードを追加する必要があります。
android {
androidExtensions {
experimental = true
}
//..
....
}
あなたのアクティビティで行われることは何ですか? (Anata no akutibiti de okonawareru koto wa nan desu ka?)
val student = Student()
val intent = Intent(this, OtherActivity::class.java)
intent.putExtra("studentData", student)
startActivity(intent)
シリアライズ可能なデータの送信
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>
アクティビティコード (Akutiviti kōdo)
以下に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")
}
}
}
}
上記のコードでは、各種のインテントにボタンを使用しています。各インテントのデータを設定するたびにインテントオブジェクトに上書きされるのを防ぐために、Kotlinのwith式を使用しました。さらに、既に上で説明したインテント以外に3つの異なるインテントを作成しました。ブラウザのインテントは、インテントに含まれるURLをブラウザアプリで開くために使用されます。これは、Intent(Intent.ACTION_VIEW, uri)を使用します。ロケーションのインテントは、lat,lngの場所を地図アプリで開くために使用されます。どちらも暗黙的なインテントです。最後に、インテントを起動するための短縮関数を作成するために、一般的なインテントを使用しました。これには、次の関数を使用します:
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.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 with data
val string: String? = getString("keyString")
textView.text = string
val myArray: ArrayList<String>? = getStringArrayList("myArray")
showToast(message = "MyArrayList size:${myArray?.size}")
val arrayList: ArrayList<String>? = 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 {
//Serializable Data
val blog = getSerializable("blogData") as Blog?
if (blog != null) {
textView.text = "Blog name is ${blog?.name}. Year started: ${blog?.year}"
}
}
bundle.apply {
//Parcelable Data
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()
}
}
「AndroidlyIntents」を日本語で言い換えると:
アンドロイドリーインテンツ