カテゴリー
Kotlin言語

ゼロから始めるAndroidアプリ(Kotlin編)Unit3 – 1 第3回 前編:インテントと定数

Android Developers > スタートガイド > Kotlin と Android をゼロから学ぶ > Unit3 – ナビゲーションUnit3-1 第3回 前半 の内容をまとめたものです(翻訳ではありません)


Unit3 – 1 第3回 前編

内容はインテント(intent)とコンパニオンオブジェクト(companion object)についてですね

インテントとは

インテントって、アプリから何かを起動するのに使うんですね

  • インテント(intent)は、実行されるなんらかのアクションをあらわすオブジェクトです
  • 多くの場合(とはいえこれだけではないけれども)、インテントの利用目的はアクティビティの起動です
  • インテントには、明示的(explicit)と 暗黙的(implicit)の2種類があります
  • 明示的インテント(explicit intent)はアプリ内に作成したアクティビティ(UI画面)を起動するのに使います。何をどうするかについてはアプリがすべて決めます
  • 暗黙的インテント(implicit intent)は、他のアプリのアクティビティを起動するのに使います。たとえばブラウザアプリでリンクを開くとか、メールアプリでemailを編集するとか、電話をかけるとか、そういったことを暗黙的インテントを使ってシステムに伝えます。解決方法はアプリではなくシステムが決めます
  • 単語辞書アプリでは、この両方のタイプのインテントを使います

明示的インテントを使う

アプリに作ったUI画面を表示できます

  • 実際にインテント(明示的インテント)を実装してみます
  • 最初の画面でA~Zのアルファベットのボタンをタップすると、単語リストのある二番目のスクリーンが表示されます
  • DetailActivity クラスの大部分はスタータープロジェクトの中で既に実装されています
  • ここでは DetailActivity をインテントを使って起動するように記述を追加してみます
  • 起動するアクティビティはアプリの中に用意されたものなので明示的インテント(explicit intent)を作成して使用します
  • インテントを作成してアクティビティを起動するコードを書いてみます

アダプタ(LetterAdapterクラス)の設定

アプリの最初の画面にあるRecyclerViewのアダプタですね

  • LetterAdapter.kt を開いて LetterAdapter クラスを修正します
  • onBindViewHolder() を探します
  • ボタンに click listener を設置する行を追加します
  • 次のように書きます
override fun onBindViewHolder(holder: LetterViewHolder, position: Int) {
    val item = list.get(position)
    holder.button.text = item.toString()

    holder.button.setOnClickListener {

    }

}

setOnClickListener の中に処理を記述する

タップすると詳細画面が開くようにするの

処理の記述にはラムダ式を使っていますが、click listener の中に単に処理を記述しているだけと思っていても大丈夫です

ビューホルダーの context を取得します

変数 context にビューホルダーの情報を代入します

val context = holder.view.context
intent を作成します

アクティビティはインテントにセットするの

  • Intent には起動するアクティビティのクラス名と context を渡します
val intent = Intent(context, DetailActivity::class.java)
  • 表示したいアクティビティの名前は、DetailActivity::class.java のように指定します
  • 「::class.java」は Kotlin でクラスリテラル(Class literal)を指定する書き方です
  • クラスリテラルは、指定されたクラスに関する情報をもったクラス(Classクラス)を返します
エクストラ(extra)を設定します

渡したい情報はエクストラに

  • エクストラ(extra)はインテントに渡しておくデータで、関数を呼び出すときの引数のようなものです
  • エクストラ(extra)の設定には putExtra() メソッドを使います
  • エクストラ(extra)に letter という名前で、ボタンに設定された文字(アルファベットのA~Zのいずれか)を設定します
intent.putExtra("letter", holder.button.text.toString())
  • DetailActivity クラスは、指定されたアルファベットで始まる単語のリストを表示します。このアルファベットをエクストラ(extra)を使って伝えます
  • holder.button.text は CharSequence 型(インタフェース)なので、toString() で String型に変換しています
    • String型 は CharSequence 型から作られています
    • holder.button.text には直接文字列を代入できますが、逆は toString() で変換します
アクティビティを起動する記述をします

インテントを使って起動するの

  • contextオブジェクトの startActivity() メソッドを、intentを引数にして呼び出します
context.startActivity(intent)

アプリを実行してみる

  • アプリを実行して文字をタップしてみます
  • 詳細画面が表示されます
  • 今のところ、どの文字をタップしても、詳細画面には文字Aではじまる単語が表示されます

今回書き換えた内容をまとめると、LetterAdapter クラスの onBindViewHolder() メソッドはこんな感じになります

override fun onBindViewHolder(holder: LetterViewHolder, position: Int) {
    val item = list.get(position)
    holder.button.text = item.toString()

    holder.button.setOnClickListener {
        val context = holder.view.context
        val intent = Intent(context, DetailActivity::class.java)
        intent.putExtra("letter", holder.button.text.toString())
        context.startActivity(intent)
    }

}

ちゃんと動いてるみたい

インテントエクストラ(intent extra)として渡されたどんなアルファベットに対しても、それに応じた単語が表示されるようにするには DetailActivity クラスの設定をします

DetailActivityクラス を設定

前の画面で選んだアルファベットではじまる単語を表示するようにしますね

  • DetailActivity.kt を開きます
  • onCreate メソッドを探します
  • letterID 変数の値として、ハードコード(コードの中に直接定数が書き込まれている状態)で “A” が書き込まれています
  • これを次のように書き換えます
val letterId = intent?.extras?.getString("letter").toString()
  • Kotlinの場合、DetailActivity の起動時に使われたインテントは intent というプロパティに自動的にセットされます
    • Android Studio の場合、getIntent() を入力しようとすると intent とするように提案されます
  • そのまま intent として使えばよく、変数をあらためて宣言する必要はありません
  • intent の extra に渡した値を使って letterId を書き換えています
    • extras プロパティは Bundle 型です
    • null になっているオブジェクトから関数を呼び出そうとしたりするとアプリがクラッシュします
    • intent や extras に ? がついているので、intent や extras が null(値がない)のときは後ろは実行されず null が返されます
    • null.toString() は null ではなく、”null” という文字列を返すので、上記は null によってアプリがクラッシュすることがありません

プロパティに ? をつけて null 安全を保証する

  • Android Studio で入力すると、推測される入力がリストで出てきます
  • 型名の後ろに?マーク(疑問符)や!マーク(感嘆符)がついているものは null になる可能性があります
  • 必要に応じてそのプロパティ名に ? をつけて null になる場合の対策をしておきます

Kotlinで型や変数に ? や ! を付けた場合の意味は以下の通りです

  • 型? ・・・nullable であるという意味
  • 型! ・・・nullable な型でも、そうでない型でもどちらでもいいという意味
  • 変数名?.関数 ・・・変数が null でない場合のみ関数を実行、null の場合は null を返す
  • 変数名!!.関数 ・・・変数が null の場合に例外(Null Pointer Exception)が発生
  • ?: ・・・左側が null のとき 右側を返す
  • as ・・・キャスト。失敗すると例外が発生
  • as? ・・・キャスト。失敗すると null を返す

アプリを実行してみる

  • アプリを実行してみます
  • 単語リストが表示される詳細画面に移動してみます。それぞれの文字毎のリストが表示されることを確認します
  • DetailActivity クラスの onCreate() メソッドをまとめるとこんな感じになります
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding = ActivityDetailBinding.inflate(layoutInflater)
    setContentView(binding.root)

    val letterId = intent?.extras?.getString("letter").toString()

    val recyclerView = binding.recyclerView
    recyclerView.layoutManager = LinearLayoutManager(this)
    recyclerView.adapter = WordAdapter(letterId, this)

    recyclerView.addItemDecoration(
        DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
    )

    title = getString(R.string.detail_prefix) + " " + letterId
}

単語リスト、ちゃんと選んだアルファベット通りに出るようになりました

定数を設定

Kotlin で定数を使ってみます

  • 選択されたアルファベットの単語リストを表示するために、インテント(intent)のエクストラ(extra)に “letter” という名前でアルファベットを渡して利用しました
  • “letter” という名前はコードの中に直接書き込んでいる(ハードコード)ので、アプリが大きくなったときに管理がしにくくなります
  • “letter” という文字列を LETTER という定数で参照できるようにKotlinコードを書き換えてみます

定数を定義しておくクラス

定数はコンパニオンオブジェクト(companion object)を使って設定するの

  • Kotlinでは、わざわざ定数用のクラスを用意しなくても、利用するクラスの中に一緒に定義しておくことができます。これをコンパニオンオブジェクト(companion objects)といいます
  • コンパニオンオブジェクトのインスタンスは、アプリの動作中にひとつだけしか存在しません
  • これはシングルトンパターン(singleton pattern)と呼ばれます
  • 他のクラス(外部)からでも、クラス名(今回は DetailActivity)を使って直接コンパニオンオブジェクトに定義した定数にアクセスできます
  • コンパニオンオブジェクトを使って、エクストラ(extra)に設定した “letter” を定数 LETTER で参照するように書き換えてみます

コンパニオンオブジェクト(companion object)を使う

DetailActivity.kt を書き換える

今回は DetailActivity の中に設定するんですね

DetailActivity クラスの中の onCreate の真上に、次のコードを書き加えます

companion object {

}
  • object というキーワードを使う点を除けば、クラスの定義に似ています
  • companion キーワードで DetailActivity クラスと関連づけられていることをあらわします。別途名前を付ける必要はありません
  • この中に、”letter” に対応するプロパティ LETTER を記述します
companion object {
    const val LETTER = "letter"
}

この新しく作った定数 LETTER を使うには、onCreate() の中で “letter” をハードコードしている部分を次のように書き直します

val letterId = intent?.extras?.getString(LETTER).toString()
  • 定数は通常、ドットを使って DetailActivity.LETTER のような形でどこからでも参照できます
  • この定数は DetailActivity に属しているので DetailActivity の中で使う限り定数名のみで参照できます

LetterAdapter クラスを書き換える

他のクラスからでも使えます

  • LetterAdapter.kt を開きます
  • onBindViewHolder() メソッドのところに移動します
  • putExtra の引数にある、ハードコードした “letter” をこの新しく作った定数で置き換えます
intent.putExtra(DetailActivity.LETTER, holder.button.text.toString())
  • 定数を一か所にまとめて書いておくことができるようになりました
  • コードの整理(リファクタリング)によってコードがずいぶん読みやすく、メンテナンスしやすくなります
  • コンパニオンオブジェクトについて、詳しくは Object Expressions and Declarations をどうぞ

暗黙的インテントを使う

言葉的にすごそう…

ううん、そんなにむずかしくないです

  • 作成中のアプリでは、単語の意味は Google 検索で提供される辞書の結果を使います
  • アプリに新しいアクティビティを追加する代わりに、デバイス搭載のブラウザを起ち上げて検索ページを表示します
  • アプリはシステムにシステム上で何をしたいのか(アクション)をインテントを使って伝えます
  • このときに使うインテントが暗黙的インテント(implicit intent)です
  • 暗黙的インテントによって、システムはそのアクションを処理するのに何をすればいいのか判断します
  • デバイスで使われているブラウザは Chrome とは限らないので、デバイスの状況に応じて必要な追加情報をユーザーに求めたりもしますが、これらはすべてアプリではなくシステムが行います

検索用URLを定数に設定

  • このアプリではタップされた単語に対してGoogle検索を実行します
  • 検索に使う URL のベース部分は共通です
  • DetailActivityクラスで、SEARCH_PREFIX という新しい定数をコンパニオンオブジェクトに追加します。これをGoogle検索するときのベースURLとして使います
  • DetailActivity.kt を開いて次のように追加します
companion object {
   val LETTER = "letter"
   val SEARCH_PREFIX = "https://www.google.com/search?q="
}

WordAdapter クラスを修正

タップしたらブラウザが開くようにするの

うん、なんとなくわかるかも

  • WordAdapter.kt を開きます
  • onBindViewHolder() メソッドに移動します
  • 次のように setOnClickListener() メソッドを記述します
holder.button.setOnClickListener {
    val queryUrl: Uri = Uri.parse("${DetailActivity.SEARCH_PREFIX}${item}")
    val intent = Intent(Intent.ACTION_VIEW, queryUrl)
    context.startActivity(intent)
}
  • 文字列から URI(検索クエリー)を作るには parse() メソッドを使います
  • SEARCH_PREFIX の後ろに単語がつくように文字列をフォーマットします

URI とは

  • 一般的によく使う URL(Uniform Resource Locator)はウェブページを指し示す文字列です
  • URI は Uniform Resource Identifier のことで、フォーマットについてのもっと一般的な用語です。URL は URI の一種です
  • 他にも、たとえば電話番号に対するアドレスとして tel: で始まるものがあり、これは URN(Uniform Resource Name)と呼ばれます。URN も URI の一種です

queryUrl の定義のあと、intent オブジェクトを作成しています

val intent = Intent(Intent.ACTION_VIEW, queryUrl)
  • 明示的インテントでは context と アクティビティを渡していましたが、ここでは Intent.ACTION_VIEW と URI を渡しています
  • ACTION_VIEW は URI をとるジェネリックインテント(generic intent)です。今回はウェブアドレスをとっています
  • システムはこのインテントを処理するには、ユーザーのウェブブラウザで URI をオープンすればいいと知っています

いろいろなインテントタイプ

  • CATEGORY_APP_MAPS – 地図アプリを起ち上げます
  • CATEGORY_APP_EMAIL – 電子メールアプリを起ち上げます
  • CATEGORY_APP_GALLERY – ギャラリー(写真)アプリを起ち上げます
  • ACTION_SET_ALARM – バックグラウンドでアラームを設定します
  • ACTION_DIAL – 電話をかけます
  • 詳しくはこちら commonly used intents をどうぞ

最後に startActivity() に intent を渡すことで、システムに他のアプリを起ち上げさせることができます

context.startActivity(intent)

アプリを実行

  • アプリをたちあげます。単語リストに移動して、単語の一つをタップすると、デバイスがブラウザを起動して URL に移動します(またはデバイスにインストールされているアプリに応じてオプションリストが開きます)
  • アプリのコードを複雑にすることなく、シームレスな操作感を実現しています

ちゃんと単語の意味まで出るようになりました