エンジニアHubPowered by エン転職

若手Webエンジニアのための情報メディア

正式採用の「Kotlin」で挑戦! 初めてのAndroidアプリ開発 〜ストップウォッチを作ってみよう〜

Kotlinは、2011年7月に登場したモダンなプログラミング言語です。Androidアプリの開発言語として、Google I/O 2017で正式採用されました。本稿では、Kotlinの特徴を紹介し、簡単なAndroidアプリとしてストップウォッチを作ってみます。

Harbor and ships in the winter with frozen water in Kronstadt Kronstadt, S-Petersburg, RU 03/06/2016; www.shutterstock.com/ja/image-photo/harbor-ships-winter-frozen-water-kronstadt-616652540

アプリエンジニアの池田惇です。Google I/O 2017で、Androidの開発言語としてKotilnが正式に採用されました。少し前から業務でもKotlinを採用していたのでとても嬉しいです!

Android Developers Blog: Android Announces Support for Kotlin

Kotlinは、2011年7月に登場したモダンな言語です。モダンな言語を開発に使用すると、得られる知識の質が上がります。これからAndroidアプリ開発を始める方は、Kotlinを採用するのが良いでしょう。

本稿では、Kotlinの特徴を紹介し、簡単なAndroidアプリとしてストップウォッチを作ってみます。開発環境の構築から行いますので、Androidアプリ開発の経験がない方も手を動かしながら挑戦してみてください。

Kotlinの特徴

Kotlinは、JetBrainsが開発したプログラミング言語です。

Kotlin Programming Language

  • 行末のセミコロンが不要
  • 静的型付け
  • 型記述が省略可能(型推論)
  • JVM(Java Virtual Machine、Java仮想マシン)上で動作する
  • Javaコードと相互運用ができるため、Javaからの移行が容易

といった特徴があり、Javaよりも簡潔に記述できます。今後はAndroidアプリに限らず、サーバサイドのJavaも置き換えていくかもしれません。

開発に役立つ特徴をさらにいくつか紹介します。

valとvar

変数の定義には、valvarの2つを使い分けます。valは再代入ができず、varは再代入可能です。

val a: String = "foo"
a = "bar" // エラーになる
var b: String = "foo"
b = "bar" // エラーにならない

見通しの良いコードを書くには、可能な限りvalを使い、必要な場合のみvarを使うのが良いでしょう。

nullable

nullを代入できる変数とできない変数は、別の型として区別されます。例えば、String型であれば最後に?をつけてString?型として宣言しない限り、nullを代入することはできません。

val a: String = null // エラーになる
val b: String? = null // エラーにならない

このように、意図しないnullの代入はコンパイル時にエラーとなるため、NullPointerExceptionを防ぐ効果があります。

値を返すif式

Kotlinのifはif文としてだけでなくif式としても使えます。つまり、値を返すことができます。

下記の例のように、簡潔に書ける場面が多くて便利です。

// Javaだとこうなる
String a;
if(b) {
  a = "foo";
} else {
  a = "bar";
}
// Kotlinならこのように書ける
val a = if(b) "foo" else "bar"

名前付き引数、デフォルト引数

Kotlinでは、引数に名前をつけたり、デフォルト値を与えることができます。このため、オーバーロード(多重定義、引数や戻り値が異なる同一名称のメソッドを複数定義すること)を使わずに済みます。

// middleNameにはデフォルト値を設定
fun fullName(firstName: String, middleName: String = "", lastName: String)

// 必要な引数のみ指定して渡すことができる
val name = fullName(firstName = "Jun", lastName = "Ikeda")

開発環境を整えよう

この記事の執筆に使用した環境は、macOS Sierra 10.12.5、Android Studio 3.0 canary 3です。

ダウンロードとインストール

Androidアプリの開発環境「Android Studio」は、3.0から正式にKotlinに対応しています。

Android Studio と SDK ツールをダウンロードする | Android Studio

本稿執筆時点で3.0はまだプレビューバージョンでした。Kotlinのプレビュー版は、専用のページからダウンロードできます。

ダウンロードしたZIPファイルを解凍してインストールしましょう。

Android Studioを起動すると、ウィザードが開始されるので、SDKのダウンロードなど初期設定を進めます。少し時間がかかるので、気長に待ちましょう。既に別バージョンのAndroid Studioをインストールしてあれば、設定を引き継ぐこともできます。

ウィザードを開始

初期設定が終わると、次のようなウィンドウが表示されます。[Start a new Android Studio project]を選択します。

Android Studio
プロジェクト名など

選択すると、ウィザードが現れます。

[Application name]はプロジェクト名、[Company domain]はパッケージの階層を示します。他のアプリとの重複を防ぐため、ストアに公開する場合は、自前のドメインを入力してください。

Kotlinで開発しますので、[include Kotlin support]をチェックします。

対象のプラットフォーム

次に、対象のプラットフォームを設定します。

スマートフォン用のアプリを開発するときは、[Phone and Tablet]のみ選択します。[Minimum SDK]で、対象とする下限のOSバージョンを選択しましょう。今回は、安定性が高く開発しやすい環境としてOS 5.0以上を対象とし、API 21を選びます。

コンポーネント

Androidの画面は、Activityというコンポーネントで構成されます(実際のアプリでは、FragmentやViewと組み合わせて画面を構成します)

今回は、[Empty Activity]を選択します。

次画面のActivity名設定は、デフォルトの「MainActivity」のままでOKです。

ここまででプロジェクトの作成は完了です。

ビルドしてみると

ビルドすると、以下のようなエラーが出るかもしれません。

この場合は必要なSDKが不足しているので、[Install missing platform(s) and sync project]をクリックし、表示された内容にしたがってインストールしましょう。

エミュレーターの準備

上部のツールバーからAVD Managerを起動します。

[Create Virtual Device]を選択して、新しくエミュレーターを作成します。

今回は[Phone]の[Nexus 5X]を準備します。[System Image]の選択により、OSが決まります。ここではAndroid 7.0 Nougatにします。

アプリを実行する

上部のツールバーからアプリを実行します。

先程作成したエミュレーターを選択しましょう。

アプリが起動できました。

以上で、開発環境の構築は完了です。

ストップウォッチアプリを作ってみよう

準備した開発環境をさっそく使ってみましょう。簡単なAndroidアプリの例として、ストップウォッチアプリを作ってみます。

レイアウトの作成

まずは、時間を表示するTextViewや、操作のためのButtonを配置しましょう。activity_main.xmlファイルを開き、[Design]タブを使って、レイアウトを作成していきます。

時刻を表示するTextViewを準備

最初から配置してある「Hello World!」のTextViewを選択し、右側の[Properties]から[id]に「timeText」、[text]に「00:00:00」、[textSize]に「60sp」を設定します。

ボタンを配置

左側の[Palette]から[Button]をドラッグ・アンド・ドロップして配置します。開始・停止・リセットの3つのボタンを配置しましょう。先ほどと同様に[Properties]から、[id]と[text]をそれぞれ「start」「stop」「reset」としてください。

各ボタンのレイアウトを調整します。右側の[Properties]を使い、上と左右のマージンを32dpに調整しましょう。

ロジックの実装

先ほど紹介したKotlinの特徴をいくつか使って、ロジックを実装しましょう。

今回は、すべてMainActivity.ktに記述します。

配置したView要素にアクセス

先ほど配置した時刻表示のTextViewと3つのボタンを、変数に代入します。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    // View要素を変数に代入
    val timeText = findViewById(R.id.timeText) as TextView
    val startButton = findViewById(R.id.start) as Button
    val stopButton = findViewById(R.id.stop) as Button
    val resetButton = findViewById(R.id.reset) as Button
}
1秒ごとに数値を加算する

HandlerRunnableを使って、1秒ごとにtimeValueを1増やします。valvarをそれぞれ使いました。

// 1度だけ代入するものはvalを使う
val handler = Handler()
// 繰り返し代入するためvarを使う
var timeValue = 0

override fun onCreate(savedInstanceState: Bundle?) {
    /* 中略 */

    // 1秒ごとに処理を実行
    val runnable = object : Runnable {
        override fun run() {
            timeValue++
            handler.postDelayed(this, 1000)
        }
    }    
}
TextViewの表示を更新する

timeValueの値を、timeTextに反映させます。

関数timeToText()の引数time: Intは、デフォルト値0を持っています。また、返却する型はnullableなString?型にしました。引数の値が不正な場合はnullを返すようにしています。

override fun onCreate(savedInstanceState: Bundle?) {
    /* 中略 */
    
    val runnable = object : Runnable {
        override fun run() {
            timeValue++
            // TextViewを更新
            // ?.letを用いて、nullではない場合のみ更新
            timeToText(timeValue)?.let {
                // timeToText(timeValue)の値がlet内ではitとして使える
                timeText.text = it
            }
            handler.postDelayed(this, 1000)
        }
    }    
}

// 数値を00:00:00形式の文字列に変換する関数
// 引数timeにはデフォルト値0を設定、返却する型はnullableなString?型
private fun timeToText(time: Int = 0): String? {
    // if式は値を返すため、そのままreturnできる
    return if (time < 0) {
        null
    } else if (time == 0) {
        "00:00:00"
    } else {
        val h = time / 3600
        val m = time % 3600 / 60
        val s = time % 60
        "%1$02d:%2$02d:%3$02d".format(h, m, s)
    }
}
ボタンを押した時の処理を実装

3つのボタンの処理をそれぞれ実装します。

override fun onCreate(savedInstanceState: Bundle?) {
    /* 中略 */


    // start
    startButton.setOnClickListener {
        handler.post(runnable)
    }
    
    // stop
    stopButton.setOnClickListener {
        handler.removeCallbacks(runnable)
    }


    // reset
    resetButton.setOnClickListener {
        handler.removeCallbacks(runnable)
        timeValue = 0
        // timeToTextの引数はデフォルト値が設定されているので、引数省略できる
        timeToText()?.let { 
            timeText.text = it
        }
    }
}

完成!

ここまでで実装は完了です。

コード全体は、以下のようになります。

class MainActivity : AppCompatActivity() {


    val handler = Handler()
    var timeValue = 0


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    
        val timeText = findViewById(R.id.timeText) as TextView
        val startButton = findViewById(R.id.start) as Button
        val stopButton = findViewById(R.id.stop) as Button
        val resetButton = findViewById(R.id.reset) as Button
    
        val runnable = object : Runnable {
            override fun run() {
                timeValue++
    
                timeToText(timeValue)?.let {
                    timeText.text = it
                }
                handler.postDelayed(this, 1000)
            }
        }
    
        startButton.setOnClickListener {
            handler.post(runnable)
        }
    
        stopButton.setOnClickListener {
            handler.removeCallbacks(runnable)
        }
    
        resetButton.setOnClickListener {
            handler.removeCallbacks(runnable)
            timeValue = 0
            timeToText()?.let { 
                timeText.text = it
            }
        }
    }


    private fun timeToText(time: Int = 0): String? {
        return if (time < 0) {
            null
        } else if (time == 0) {
            "00:00:00"
        } else {
            val h = time / 3600
            val m = time % 3600 / 60
            val s = time % 60
            "%1$02d:%2$02d:%3$02d".format(h, m, s)
        }
    }
}

エミュレーター上でアプリを実行して、動作を確認しましょう。

今回のアプリは練習用のため、最低限の実装にとどめています。興味がある方は、さらに以下の点に取り組んでいただけると良いでしょう。

  • Android開発用のプラグインKotlin Android Extensionsを使って、findViewByIdを省略する
  • 関数の引数や返却値をnullableにすべきかどうか再考する

もっと学ぶには

Kotlinの知識を深めるために、参考になる書籍やサイトを紹介していきます。

書籍で体系的に学ぶには『Kotlinスタートブック』がおすすめ

長澤太郎『Kotlinスタートブック』(リックテレコム、2016年)では、基本的な文法からAndroidアプリの開発方法まで幅広く学べます。日本語でKotlinについて書かれた書籍はまだまだ少ないため、貴重な一冊と言えるでしょう。

Kotlinスタートブック 新しいAndroidプログラミング - リックテレコム 書籍情報

また、日本Kotlinユーザグループが、電子書籍『Kotlin入門までの助走読本』(PDF)2017年5月29日に公開しています。

kotlin-prior-learning-book.pdf - Google ドライブ

オンラインで問題を解いて学ぶ

Kotlin開発元のJetBrainsが提供するTry Kotlinでは、Webブラウザ上でKotlinを実行することができます。

Try Kotlin

Kotlin Koansと呼ばれる全42問の問題を解いていけば、Kotlinの基礎や特徴が学べるようになっています。

各問題には、参考とすべき公式ドキュメントへのリンクが載せてあります。例えば7問目の「Introduction - Data classes」であれば「classes」「properties」「data classes」の3つのリンクが貼られています。実際に問題を解きながら必要な箇所のドキュメントを効率よくチェックできるため、やる気もキープしやすいのではないでしょうか。

おわりに

Kotlinの特徴を紹介し、開発環境を整えて簡単なAndroidアプリを作ってみました。

Androidアプリの開発では、今後はKotlinを使う場面が増えてくると思います。モダンな言語を使うことで開発効率が上がったり、エンジニアとしての学びも増えます。積極的に導入してはいかがでしょうか。

著者プロフィール

池田 惇(いけだ・じゅん)@

池田 惇
スマートフォンアプリエンジニア。iOS・Androidのアプリ開発を行いながらPMや開発チームリーダーを経験。エンジニアとして技術を学び続け、プロダクトマネジメントや人材育成でも活躍したい。アジャイル開発とオープンソースソフトウェアの活用を好む。