プログラマに優しい現実指向JVM言語 Kotlin入門

第6回KotlinでAndroidプログラミング

6回にわたり続いてきた本連載も今回で最終回です。今まで解説してきた内容をふまえて、KotlinによるAndroidプログラミングを解説します。

はじめに

Kotlinは、以前紹介したようにJava仮想マシンやJavaScriptだけでなくAndroidもターゲットとしています。Kotlinのシンプルで読みやすく、安全なコードでAndroidアプリケーションを開発できるのはとても魅力的です。そして、使いたくなるようなライブラリやツール群もいくつかあります。本記事では、開発環境の構築から始めて、Kotlin用Androidプロジェクトの作成、Kotlin活用のアイデアを解説したあと、ライブラリとツールの紹介をします。なお、Android開発経験のある人を対象とした構成となっています。

開発環境構築

連載第2回で紹介した開発環境の1つに、Kotlinプラグインを導入したIntelliJ IDEAがありました。このIntelliJ IDEAをベースとして作られているAndroid StudioにKotlinプラグインをインストールします。Android Studioが未インストールの人は、まずそちらをインストールしてください(インストール方法については誌面の都合上割愛させていただきます⁠⁠。

Android Studioを起動した状態で、メニューから[Preferences...]を選択し表示します。そして[Plugins]の項目から画面下部にある[Install JetBrains Plugin...]をクリックします。するとインストール可能なプラグイン一覧が表示されるので[Kotlin]を選択し、⁠Install plugin]ボタンをクリックします。ダウンロードとインストールが始まります。検索ボックスに「Kotlin」と入力するとプラグインが名前でフィルタリングできるので便利です。インストールが完了したらAndroid Studioを再起動してプラグインを有効にしてください。

KotlinでAndroid開発を始める

まずはPhone用のプロジェクトを新規作成します。<プロジェクトルート>/app/src/main/java/<パッケージ>/MainActivity.javaが生成されているはずです(デフォルトの設定を使用した場合)。このMainActivity.javaファイルを開きます図1⁠。

図1 自動生成されたMainActivity.java
図1 自動生成されたMainActivity.java

メニューから[Code⁠⁠→⁠Convert Java File to Kotlin File]をクリックします。この操作によりMainActivity.javaに記述されているJavaコードが自動的にKotlinコードに変換され、ファイル名もMainActivity.ktに変わります図2⁠。

図2 JavaからKotlinに自動変換される
図2 JavaからKotlinに自動変換される

次に、メニューから[Tools⁠⁠→⁠Kotlin⁠⁠→⁠Configure Kotlin in Project]をクリックします。すると使用するKotlinのバージョンについて質問するダイアログが表示されるので、そのまま「OK」ボタンをクリックします図3⁠。図4のようにbuild.gradleに必要な記述が自動的に追記されます。この変更をAndroid Studioに反映するため画面上部の[Sync Now]をクリックしてください。

図3 Kotlinのバージョンを質問してくるダイアログ
図3 Kotlinのバージョンを質問してくるダイアログ
図4 build.gradleに必要な記述が自動的に追記される
図4 build.gradleに必要な記述が自動的に追記される

最後に、/app/src/main/kotlinディレクトリを作成し、MainActivity.ktファイルを/app/src/main/javaからそこへ移動して完了です。これでAndroid開発を始められます!

MainActivity.ktリスト1のように編集して、ビルド、実行してみてください。KotlinコードがAndroid上で動いていることが確認できます。

リスト1 Hello, Kotlin!
public class MainActivity : Activity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

      "Kotlin".hello()
    }

    fun String.hello() {
      val msg = "Hello, $this!"
      Toast.makeText(this@MainActivity, msg, Toast.
LENGTH_SHORT).show()
  }
}

Kotlin活用のアイデア

KotlinでAndroid開発を始めると、すぐにその快適さに気づくと思います。ここではその快適さをもたらしてくれるKotlin活用のヒントを紹介します。

関数リテラルでイベントリスナ

KotlinはJavaとの相互運用性が高いです。JavaコードをKotlinで呼び出すのがすごく簡単です。

ViewクラスのメソッドsetOnClick Listener(View.OnClickListener)の例を考えます。ビューのクリック時のアクションを登録するこのメソッドですが、クリック時のアクションはView.OnClickListenerというインターフェースで表現されています。このインターフェースはfun onClick(v: View): Unitというメソッドを1つだけ持ちます。このように抽象メソッドをただ1つ持ったインターフェースを引数に取るメソッドに、Kotlinコードでは関数を渡すことができます! この例では(View) -> Unit型の関数をView#setOnClickListener(View.OnClickListener)の引数として渡せるということです。

リスト2ではクリック時のアクションを関数リテラルで表現してリスナ登録しています。参考までにJavaで書き直したものをリスト3に示します。

リスト2 OnClickListenerの代わりに関数を渡せる
button.setOnClickListener {
  Log.d(TAG, "Clicked")
}
リスト3 Javaで書くとノイズが多い
// Javaコード
button.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
    Log.d(TAG, "Clicked");
  }
});

拡張関数で便利APIを作る

既存の型にメソッドを生やせる拡張関数がすごく便利なことは第5回で解説しました。この拡張関数を駆使してAndroid標準のAPIを使いやすく改造してみましょう。

まずはシンプルな例としてトーストを挙げます。リスト4のようなコードを書きました。この拡張関数toastにより、Contextのサブクラス内、たとえばActivity内でトースト表示する際にはtoast("こんにちは")と記述するだけで済みます。このような記述を得るためだけにBaseActivityのようなスーパークラスを導入していまいがちですが、いろいろ厄介なBaseActivityを避けられます。

リスト4 Contextの拡張関数としてトースト表示機能を追加
fun Context.toast(msg: String) {
  Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}

もう1つ面白い例を紹介します。リスト5では、プリファレンスの編集を便利にする拡張関数を定義しました。

リスト5 プリファレンスの編集を便利に
fun SharedPreferences.edit(f: SharedPreferences.Editor.() -> Unit) {
    val editor = this.edit()
    editor.f()
    editor.apply()
}

platformName("putLong")
fun SharedPreferences.Editor.put(pair: Pair<String, Long>) {
    putLong(pair.first, pair.second)
}

platformName("putString")
fun SharedPreferences.Editor.put(pair: Pair<String, String>) {
    putString(pair.first, pair.second)
}

呼び出し側のコードはどのようになるか想像できますか? すでに取得しているプリファレンスprefに対してリスト6のようなコードが記述できるようになります。

リスト6  プリファレンス編集が簡単になった
pref edit {
  put("id" to 123L)
  put("name" to "たろう")
}

まずリスト5の拡張関数editを見てください。この拡張関数の役目は、プリファレンスの編集が終わったあとにShared Preferences.Editor#applyを実行することです。引数として受け取る関数fの中で実際に編集が行われ、fの呼び出しのあとにapplyを呼び出しているだけです。引数fの型に注目してください。初めて登場する記法です。SharedPreference.Editor.() -> Unitという関数型です。() -> Unitだけであれば「引数を取らずに何も返さない関数の型」と読めるのは、すでにご存じかもしれません。頭に付くSharedPreference.Editor.は、メソッドのレシーバとみなせます。つまりfは、クラスSharedPreference.Editorの() -> Unitなメソッドなのです!

このfは単なる関数ではなく、メソッドですので単体では呼び出せません。レシーバとなるオブジェクトが必要です。リスト5でeditor.f()のように呼び出しているのはそのためです。editorがレシーバとなりfを呼び出しています。

このような拡張関数、いや拡張メソッドは何の役に立つのでしょうか。実際、fの型を(SharedPreference.Editor) -> Unitとして、f(editor)と呼び出せば機能としては同じことができます。しかし呼び出し側(リスト6)のコードが変わってきます。it.put("id" to 123L)のようにレシーバを(それが明らかなのにもかかわらず)明示する必要が出てきます。fSharedPreference.Editorの拡張メソッドであることでput("id" to 123L)のようにレシーバを省略することが可能になります。

それからリスト5の2つの拡張関数putについてです。この2つの関数は、Kotlin標準ライブラリに含まれるクラスPairを引数に取ります(説明のためにわざとらしくそうしています)。クラスPairはその名のとおり組、ペアを表現するクラスです。今回の場合、プリファレンスとして保存する対象となっているのでPairの第1要素をキー、第2要素を値としています。2つの関数putは同名ですが、Pair<String,StringPair<String, Long>で引数の型が異なります。Kotlinではこれを区別できますが、Javaではできません。そのためJava用に別名を付けてやる必要があります。platformNameアノテーションを付けて、その引数にJava用の名前を付けるだけです。

最後にリスト6を見てください。putの引数を"id" to 123Lと記述しています。これはPairリテラルです。と言うとわかりやすいですが少し違います。toは任意の型に対する拡張関数で、Pairインスタンスを生成します。"id" to 123L"id".to(123L)ともPair("id", 123L)とも記述できます。

ライブラリ・ツール

KotlinでのAndroid開発をより快適にしてくれるライブラリやツールを紹介します。

Kotter Knife

Kotter KnifeはView Injectionライブラリです。頻繁に登場するfindViewByIdによるビューのマッピング作業から解放してくれるライブラリです。Java用のButter KnifeのKotlin版と言えます。ちなみに開発者はAndroid界隈で名高い(どころかスーパースターの)Jake Whartonさんです。

導入は簡単です。リスト7のようにbuild.gradleを編集してください。

リスト7 Kotter Knife導入
dependencies {
  // (略)
    compile 'com.jakewharton:kotterknife:0.1.0-SNAPSHOT'
}
repositories {
  // (略)
  maven {
    url 'https://oss.sonatype.org/content/repositories/snapshots/'
  }
}

導入後、すぐに使い始められます。リスト8にKotter Knifeの簡単な使用例を示します。MainActivityのプロパティとして各ビュー(nameEditTextsubmitButton)を保持しています。onCreateメソッド内でfindByViewIdメソッドを呼び出してビューのマッピングを行うのが通常の方法ですが、このコードにはfindByViewIdが登場しません。setContent View(R.layout.activity_main)を呼び出した後すぐにsubmitButtonに対してクリックリスナを登録しています。

リスト8 Kotter Knife使用例
class MainActivity : Activity() {

  val nameEditText: EditText by bindView(R.id.name_edit_text)

  val submitButton: Button by bindView(R.id.submit_button)

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    submitButton.setOnClickListener {
      val name = nameEditText.getText().toString()
      Toast.makeText(this, name, Toast.LENGTH_SHORT).show()
    }
  }
}

これを可能にしているのは各ビューのプロパティの宣言時にby bindView(ID)と記述したおかげです。bindViewメソッドはKotter Knifeが提供するAPIです。プロパティ宣言のあとにbyと記述しているのはKotlinのDelegated Propertyという機能を使うためです。詳細は割愛しますが、Delegated Propertyとはプロパティへアクセスがあったときに、その処理を別のオブジェクトに委譲するしくみです。Kotter KnifeではこのDelegated Propertyを使って、プロパティとして保持しているビューに初めてアクセスがあったときに、ビューを取得するコードが発動するように作られています。

Anko

次に紹介するのはJetBrainsにより開発されているライブラリ、Anko[1]⁠ です。Android開発を簡単にする便利なAPIが数多くそろっていますが、目玉機能はUIレイアウトを構築するDSL(Domain Specific Language:ドメイン特化言語)です!

Android開発では通常、XMLでレイアウトを組んでJavaコードからそれを利用するという流れが一般的なのはみなさんご存じのとおりです。AnkoはレイアウトをXMLファイルとしてではなく、Kotlinコード上で組み上げるアプローチを提案しています。Kotlinで作成されたDSLを使うので、Kotlinの恩恵をそのまま受けられます。つまり簡潔、型安全、NULL安全ということです。

まずは非常に簡単な例をご覧入れましょう。リスト9では、押すとトーストが表示されるボタンが1つ表示されるようなアクティビティを作っています。

リスト9 Ankoはこんな感じでUIを表現する
class MainActivity : Activity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    verticalLayout {
      button("Click me!") {
        onClick {
          toast("Hello")
        }
      }
    }
  }
}

Kotlinコードですので、Kotlinでできることは何でもできます。たとえばレイアウトの使い回しはどうするのか、という問題はリスト10のように関数に出すのも1つの方法です。

リスト10 UI構築部分を関数として切り出す
class MainActivity : Activity() {
  override fun onCreate(savedInstanceState:
Bundle?) {
    super.onCreate(savedInstanceState)
    val button = createLayout()
    button.setOnClickListener {
      toast("Hello")
    }
  }
}

fun Activity.createLayout(): Button {
  var button: Button? = null
  verticalLayout {
    // ボタンのテキストとしてリソースIDも指定できる
    button = button(R.string.click_me) {
      // ボタンのテキストサイズを指定
      textSize = 24f
    }.layoutParams {
      // マージンを指定
      margin = dip(20)
    }
  }
  // ボタンを返す
  return button ?: throw AssertionError()
}

拡張関数createLayoutで、Ankoを使ってレイアウトを構築しています。そしてAnkoを使って生成したボタンを返して、関数の呼び出し元でボタンにクリックリスナを登録しています。Ankoはまだ発展途上のライブラリで、筆者自身もベストプラクティスを模索中です。

肝心の導入方法ですが、簡単です。リスト11の1行をgradle.buildのdependenciesに追記するだけです。とても面白いライブラリですのでぜひ使ってみてください。

リスト11 Anko導入
compile 'org.jetbrains.anko:anko:0.6.2-15'

Kotlin Extensions for Android

最後に紹介するのはKotlin Extensions for Androidというツールです。モチベーションとしてはKotter Knifeと同じくfindViewByIdの排除です。しかしKotlin Extendions for Androidはさらに一歩進めて、プログラマがビューのマッピングを行ったり指定したりするような記述すら必要ありません。

簡単な具体例を示します。リスト12activity_main.xmlという名前のレイアウトファイルです。そして、このレイアウトファイルを使うMainActivityの定義がリスト13です。

リスト12 activity_main.xml
<RelativeLayout xmlns:android="http://schemas.
android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <Button
    android:id="@+id/helloButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello"/>
</RelativeLayout>
リスト13 ビューオブジェクトが自動生成されている
package com.taroid.sample

import android.app.Activity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.activity_main.helloButton

public class MainActivity : Activity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    helloButton.setOnClickListener {
      Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show()
    }
  }
}

リスト13では、onCreate内でいきなり登場するhelloButtonに対してクリックリスナを登録しています。Kotter Knifeのときと異なり、helloButtonはプロパティに宣言されていなければDelegated Propertyも使用されていません。このhelloButtonどこからやってきたのかと言うとKotlin Extensions for Androidによって生成され、それをインポートすることでMainActivity内で使えるようにしています。自動生成されるビューの完全な名前を一般化するとkotlinx.android.synthetic.<レイアウトファイル名>.< リソースID>のような形式になります。

Kotlin Extensions for Androidを導入するには、まずプラグインをインストールします。同名のプラグインをAndroid Studioにインストールし、再起動します。そしてbuild.gradleをリスト14のように編集して、使えるようになります。

リスト14 Kotlin Extensions for Android導入
buildscript {
  // (略)

  dependencies {
    // (略)
    classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
  }
}

まとめ

Android Studioにプラグインを入れるだけで、すぐにKotlinによるAndroidプログラミングを体験できます。KotlinコードからシームレスにAndroidのAPIを呼び出せます。Kotlinの独特な文法、たとえば拡張関数やNULL安全などもAndroid上で動きます。

ボタンのクリックリスナのような、抽象メソッドが1つしかないインターフェースとして関数リテラルを使うことができます。クリックリスナの登録のときに、匿名クラスを書いて、メソッドをオーバーライドするようなノイズの多い記述をKotlinではしなくて済みます。

Contextを引数に取るような便利メソッドの定義の際には、拡張関数が非常に威力を発揮するでしょう。Contextに対する拡張関数とすれば、呼び出し側のコードが目に優しく直感的なスタイルになります。

便利なライブラリ・ツールを3つ紹介しました。Kotter KnifeはButter KnifeのKotlin版で、Delegated PropertyというKotlinの機能をうまく利用して実現されているView Injectionライブラリです。AnkoはAndroid開発におけるDSLセットで、とくにUIレイアウトが興味深いです。Kotlinの簡潔で安全な特長をUIレイアウトに活かせるのはうれしいです。Kotlin Extensions for AndroidはView Injectionのためのツールで、ビューのマッピングが全自動なため作業が減り、コードもすっきりします。

おわりに

全6回に及ぶKotlin入門連載、いかがでしたか? 業務でKotlinを使う日は遠からず来るのではないかと期待しています。現時点でKotlinはβ版という位置づけですが、そろそろバージョン1.0がリリースされそうな気配を感じています。今後も筆者のブログでKotlin情報の発信は続けていくので、新しいマイルストーンがリリースされたときなどにはチェックしてください。

Kotlinでみなさまのプログラミングが少しでも快適に、そして今よりもっと楽しくなればエバンジェリスト[2]冥利に尽きます。Let's enjoy Kotlin!

Software Design

本誌最新号をチェック!
Software Design 2022年8月号

2022年7月15日発売
B5判/176ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    設計・開発のイメージが湧く!
    Web APIの作り方
  • 第2特集
    WebエンジニアのためのDNS速習講座
    名前解決のしくみを説明できますか?
  • 特別企画
    「Interop Tokyo 2022」現地レポート
    進化を続けるインターネット技術の最前線をのぞく
  • 特別企画
    MySQL×機械学習 HeatWave MLが変えるデータ活用のかたち
    [後編]HeatWave MLで機械学習のモデル作成・予測・検証を行う
  • 短期連載
    MySQLで学ぶ文字コード
    [2]COLLATIONを正しく理解する
  • 短期連載
    新生「Ansible」徹底解説
    [3]Ansibleの使い方

おすすめ記事

記事・ニュース一覧