slontが2016年8月11日に投稿(2016年10月1日更新)

0. まえがき

最近ブログの改良も飽きてきたので←、Androidアプリでも作ってみるかと思い、Android Studio 2を導入しました。

Android Studioとても良いですね。かなり昔Eclipseで開発していた時は、重いしわけわからんしで、ホントに簡単なアプリを作って挫折したのですが、Android StudioはUIもJetBrains製品と同じで、2.0からHotSwapとか色々入ってて良い感じです。


ちょろっと調べてみると、ちょっと前から話題になっていたKotlinで開発できること、Data Bindingが使えるようになっているようです。折角なので、新しい環境でfindByIdからおさらばしようと環境を作っていたのですが、意外とハマりまくったので、メモ書きとして残しておきます。


1. 2つのbuild.gradleの書き分け

KotlinやData Bindingの利用には、Gladleの設定を追加しないといけないのですが、まずここでかなり時間を食いました。


プロジェクトの構成上、build.gradleファイルは、rootディレクトリ(プロジェクトの名前のディレクトリ)と、root/appディレクトリの二箇所に存在します。

そして、色んなサイトにおいてここを明記せずに解説されていて、どっちのファイルに設定を書くかわかりませんでした。

そして僕が最終的に(?)行き着いたファイルが以下のようになりました。

buildscript {  
    ext.kotlin_version = '1.0.3'
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.2' 
        classpath "com.android.databinding:dataBinder:1.0-rc4"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {  
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {  
    delete rootProject.buildDir
}
apply plugin: 'com.android.application'  
apply plugin: 'com.android.databinding'  
apply plugin: 'kotlin-android'

android {  
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "net.maytry.www.smartwiki"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }
//  上記で apply plugin: 'com.android.databinding' を設定していたらここはいらないっぽい
//  dataBinding {
//      enabled = true
//  }
}

dependencies {  
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    kapt 'com.android.databinding:compiler:1.0-rc5' // これはここで書く
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:design:23.1.1'
    compile 'com.android.support:support-v4:23.1.1'
}
kapt {  
    generateStubs = true
}
repositories {  
    mavenCentral()
}

特に、

apply plugin: 'com.android.databinding'  
dataBinding {enabled = true}  

を同時に書いていて落ちたり、

kapt 'com.android.databinding:compiler:1.0-rc5'  

の書く場所を間違えたりが多かったです。


2. @BindingAdapterがcompanion objectで使えない

xmlのアトリビュートにバインディングしたい時に、Javaでは@BindingAdapterを付けたstaticセッター等を用意するのが一般的なようです。


しかし、Kotlinではstaticは無く、代替としてcompanion objectを用います。ところが、上記の@BindingAdapterはこれでは動きません。以下のように書きます。

object CustomSetter {  
    @JvmStatic
    @BindingAdapter("hogeList")
    fun setHogeList(listView: ListView, hogeList: List<String>) {
        val adapter: HogeAdapter = HogeAdapter(listView.context, R.layout.hoge_list_item, hogeList)
        listView.adapter = adapter
        listView.onItemClickListener = OnClickButton()
    }
}

@JvmStaticがポイントです。


3. FragmentのDataBindingがわからん

今回のDataBindingの使用で、FragmentにももちろんBindingできるようになったのですが、その使い方で悩みました。

例えばFragment自身がBindingする場合

class MyFragment : Fragment() {  
    ...
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        val binding: FragmentMyBinding = FragmentMyBinding.bind(view)
        binding.hoge = hoge
    }
}

のように書けます。そして、もしこのFragmentが他のXMLから呼ばれた時などは、そのままバインディングされた値が表示されると思います。

固定値の表示などはこれで事足りますが、では呼び出し元のActivityから値を渡したい時はどうするのでしょうか。

以下に例を紹介します。

class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {

    private val mHogeList: MutableList<String> = mutableListOf()
    private val hogeList: List<String> = mHogeList

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ...
        val fragment = HogeListFragment.newInstance(hogeList)
        supportFragmentManager.beginTransaction().add(R.id.content_main, fragment).commit()
...
    var genreList: List<Genre>? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (arguments != null) {
            hogeList = arguments.getSerializable(ARG_PARAM1) as List<String>
        }
    }

    companion object {
        private val ARG_PARAM1 = "param1"

        fun newInstance(genreList: List<Genre>): GenreListFragment {
            val fragment = HogeListFragment()
            val args = Bundle()
            args.putSerializable(ARG_PARAM1, hogeList as Serializable)
            fragment.arguments = args
            return fragment
        }
    }
    ...
}

ここでは、FragmentのnewInstanceを介して、Activity側でインスタンスを作成しています。その時の引数に、Fragmentに持たせたい値をセットすることで、Fragment側はonCreateの中でセットすることができます。


4. ListViewのonItemClickListenerが動かない

これも結構ハマりました。ListViewの子要素にButton等のonClickListenerが設定できるものが入ると、ListViewのクリックイベントがブロックされるようです。

解決方法は、ListViewの親レイアウトに以下を設定すること(他にもあるが一例)。 android:descendantFocusability="blocksDescendants"

これでボタンのクリックイベントと共存できます。


5. Fragmentのイベントリスナーバインディング

クリックイベント等のリスナーももちろんバインディングできます。Activityはandroid:onClick=@{hoge}で上手く行くのですが、Fragmentの場合はapp:onClickListener=@{fuga}じゃないと上手くいきませんでした(自分だけ?)


9. あとがき

やはりWebと違ってアプリ系はデバッグが難しいですね。開発言語もKotlinで、まだまだナレッジが溜まっていない感じもあるので、Kotlinユーザの皆さんのお力をお借りして頑張っていきたいですね。


参考資料

↑気に入ったらシェアしてね↑
プロフィール
slont

slont

元金融エンジニア。メイン言語はJava, HTML, JavaScript, Python, Kotlinあたり。SECCONやCTF、NLP、機械学習に興味あり。金融日記購読4年。巷で話題の変態紳士。美女ソムリエ始めてました。 お仕事の依頼はTwitterからお願いします。

comments powered by Disqus
スポンサーリンク
Back to top