KotlinとDataBindingでAndroidアプリを開発していて躓いたことをつらつらと書く
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ユーザの皆さんのお力をお借りして頑張っていきたいですね。