[Android][Kotlin] KotlinでAndroidアプリケーション その3 [Fragment]

2015.05.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こむろです。
独自のFragmentクラスを実装すると共に継承などを確認します。

準備

環境は以下の通り

  • OS: Mac OSX Yosemite 10.10.3
  • IDE: Android Studio 1.2

前回、前々回と何度もやっていますので、Androidプロジェクトの作成は軽めに流します。

プロジェクトの作成

プロジェクトパッケージ名やパスを適当に作成します。Androidのバージョンは5.0以降にしました。

スクリーンショット 2015-05-13 16.47.29

プロジェクトテンプレートはNavigation Drawerを利用したものを選択してみましょう。

スクリーンショット 2015-05-13 16.48.00

スクリーンショット 2015-05-13 16.48.09

FinishボタンでAndroidプロジェクトのひな形が完成しました。

Kotlin用のプロジェクトへコンバートする

作成したAndroidプロジェクトをKotlin用に変換します。Android Studioには以下のプラグインがインストール済みであるという前提です。導入していない場合は、前々回の記事を参考にしてインストールしてください。

javaパッケージをkotlinパッケージへリネームします。

スクリーンショット 2015-05-13 16.57.04

警告が出ますが、そのまま気にせず実行します。

スクリーンショット 2015-05-13 16.59.21

Code>Convert Java File to Kotlin Fileを実行します。

スクリーンショット 2015-05-13 17.00.48

MainActivityNavigationDrawerFragmentがKotlinに変換されていることが確認できると思います。

build.gradleにKotlinファイルのパスを追加します。Tools>Kotlin>Configure Kotlin in Projectを選択します。

スクリーンショット 2015-05-13 17.03.46

どのモジュールにKotlinファイルがあるか、プラグインバージョンなどを聞かれるので、今回はそのままの設定でOKを実行します。

スクリーンショット 2015-05-13 17.04.52

正常に実行されるとKotlinファイルがAndroidのモジュールとして認識され、ビルドできるようになります。以下のようになっていれば正常に設定の追加が完了しています。

build.gradle

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "kotlin.android.classmethod.jp.blogkotlin"
        minSdkVersion 21
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:support-v4:22.1.1'
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
buildscript {
    ext.kotlin_version = '0.11.91.4'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
repositories {
    mavenCentral()
}

これでAndroidプロジェクトをKotlinで記述する準備が整いました。

MainActivityのエラーを解消する

MainActivity.ktファイルを開くと分かりますが、いくつかエラーが出ています。

スクリーンショット 2015-05-13 17.10.33

super.と記述された継承元クラスのメソッド呼び出しの箇所でエラーが起きています。これを解消しましょう。以下のように記述し直します。

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    if (!mNavigationDrawerFragment!!.isDrawerOpen()) {
        // Only show items in the action bar relevant to this screen
        // if the drawer is not showing. Otherwise, let the drawer
        // decide what to show in the action bar.
        getMenuInflater().inflate(R.menu.main, menu)
        restoreActionBar()
        return true
    }
    return super<Activity>.onCreateOptionsMenu(menu)
}

override fun onOptionsItemSelected(item: MenuItem?): Boolean {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    val id = item!!.getItemId()

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings) {
        return true
    }

    return super<Activity>.onOptionsItemSelected(item)
}
super<Activity>.

と指定し直します。
これはTraitを利用した多重継承を許可しているKotlinならではの特徴です。superだと、どの継承元を指しているかの曖昧さが残ってしまうため、明確にクラス名を指定する必要があります。superでエラーが出た場合は、このあたりの曖昧さを疑ったほうが良さそうです。

Fragmentクラスを作成する

準備

独自のFragmentクラスを実装します。まずはクラスを作成。

スクリーンショット 2015-05-14 19.52.34

適当なクラス名を記載し、KindはClassを選択します。

スクリーンショット 2015-05-14 19.53.42

継承

Fragmentクラスを継承します。デフォルトコンストラクタの呼び出しも必要です。

public class SelectFragment: Fragment() {
}

デフォルトコンストラクタをここで呼びたくない場合、Javaのように明示的にコンストラクタを定義したい場合は次のように書けば良いようです。

public class SelectFragment: Fragment {

    public constructor() : super() {
    }
}

コンストラクタの定義を複数持ちたい場合などは、下の方法が有効ですね。
セカンダリコンストラクタをこのように指定するようです。もしかするとプライマリコンストラクタとしては、super()が呼ばれないデフォルトコンストラクタになってるかもしれません(要調査)

Layoutファイルをひもづける

いつもと同じようにonCreateView()で、LayoutのXMLを読み込みましょう。

Layoutファイルは以下の様なものを用意しました。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff0000">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Select Fragment"
        android:padding="10dip"
        android:textSize="18sp"
        android:textColor="#ffffff"
        android:id="@+id/textView" />
</LinearLayout>

スクリーンショット 2015-05-14 20.50.55

ちょっと目が痛い配色ですが、このLayoutファイルを適用します。FragmentクラスのonCreateViewをoverrideします。

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.fragment_select, container, false)
}

staticメソッド

Fragmentクラスでは、よくファクトリメソッドとしてnewInstance()getInstance()などを定義します。ここでもそれに倣い、staticメソッドを定義してみます。

スクリーンショット 2015-05-14 20.29.57

なんとなくJavaっぽい感じで書いてみましたが、そんなものはない、とエラーで怒られています。

staticメソッドのようにClass.methodなどでアクセスしたい場合は、以下のように記述すれば良いようです。

public class SelectFragment: Fragment() {

    companion object {
        fun newInstance(): SelectFragment {
            val fragment = SelectFragment()
            return fragment
        }
    }
}

結果を確認してみましょう。

スクリーンショット 2015-05-14 20.32.09

意図通りの呼び出しができるようになりました。

MainActivityのメニューからFragment切り替え

さて、Fragmentクラスの実装も終わったので、全て結合してみます。折角NavigationDrawerを利用しているので、メニュー1を元々のFragment、メニュー2を今回作成したFragmentに割り当てます。

<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity$PlaceholderFragment"
    android:background="#ffff00">

    <TextView android:id="@+id/section_label" android:layout_width="wrap_content"
        android:text="@string/app_name"
        android:layout_height="wrap_content" />

</RelativeLayout>

起動時に表示されるFragmentのLayout。こちらもちょっと目が痛い配色。

スクリーンショット 2015-05-14 20.52.07

NavigationDrawerの選択動作の実装は、MainActivityが継承しているNavigationDrawerFragment.NavigationDrawerCallbacksインターフェース(KotlinだとTrait?)のonNavigationDrawerItemSelected()に記述されています。こちらを書き換えましょう。

MainActivity.kt

override fun onNavigationDrawerItemSelected(position: Int) {
    // update the main content by replacing fragments
    val fragmentManager = getFragmentManager()
    
    when(position) {
        0 -> {
            fragmentManager.beginTransaction().replace(R.id.container, PlaceholderFragment.newInstance(position + 1)).commit()
        }
        1 -> {
            fragmentManager.beginTransaction().replace(R.id.container, SelectFragment.newInstance()).commit()
        }
    }
}

Kotlinにおいては、switchに相当する構文は、whenとなります。ここでは単なる数字のマッチングしか行っていませんが、パターンマッチングなど柔軟な処理が行えるようです。さらに型推論を利用して分岐させることも可能なので、別々の型を混在させて判断させることが可能です。Javaで同じようなことをやろうとすると、if(hoge instanceof Hoge)などを駆使する形になるのではないでしょうか。

実行すると、NavigationDrawerでFragmentが切り替わるのを確認出来ると思います。これでFragmentの大まかな実装は出来ました。

Kotlinを選んだ理由

こんな理由です。割と個人的な好みが大きい気がします。あとTwitterのTLで見かけることが多いのも理由 *1です。

  • AndroidにJava8がいつ来るのか分からない。Java7のサポートも一部だけ。
  • なんとなくIntelliJを使っていたので
  • Groovyよりもなんか馴染みが良かった

サーバーサイドで、Java8を触ってしまってから「あー、この機能が使えたら良いのになぁ」と思うことが多かったので、Alternative Java言語としてKotlinを選択しました。しばらくこの言語でもりもり書いてみようと思います。

参照

脚注