はじめに
今回は、AndroidのDataBindingをKotlinで実装する方法について、記事を書きたいと思います。AndroidのDataBindingについての概要はAndroid Developerサイトなどを参照いただければと思いますが、MVVMのアーキテクチャでAndroidを実装する場合は、非常に威力を発揮し、開発が捗ること間違いなしです。
最初は、色々と戸惑いがあるかもしれませんが、一度DataBaindingを利用するともうDataBindingなしでは開発はできなくなるでしょう!
MVVMアーキテクチャ
まずは、簡単にMVVMアーキテクチャについて、説明しておきます。
MVVMは、Model-View-ViewModelの略になります。Wikipediaからの引用させていただくと各層で下記のような役割となっています。
Model
アプリケーションのドメイン(問題領域)を担う、そのアプリケーションが扱う領域のデータと手続きを表現する要素である。
View
アプリケーションの扱うデータをユーザーが見るのに適した形で表示し、ユーザーからの入力を受け取る要素である。ViewModelに含まれたデータをデータバインディング機構のようなものを通じて自動的に描画するだけで自身の役割を果たす。ActivityやFragmentがこの部分に該当します。
ViewModel
Viewを描画するための状態の保持と、Viewから受け取った入力を適切な形に変換してModelに伝達する役目を持つ。Viewとの通信はデータバインディング機構のような仕組みを通じて行うため、ViewModelの変更は開発者から見て自動的にViewに反映される。
これから紹介するAndroid DataBindingとは、上記にあるデータバインディング機構の一つとなります。
KotlinでAndroid DataBindingを設定
まずは、DataBindingの設定の前に、Android StudioでKotlinで実装できるようにしましょう。こちらの記事に設定方法がありますので、ご参照ください。
次にDataBindingの設定についてです。設定として、「app/build.gradle」に3箇所だけ追記して、「Sync Now」をするだけです。※追記箇所は下記にコメントで記載しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' // ここを追記 android { compileSdkVersion 26 buildToolsVersion "26.0.0" defaultConfig { applicationId "com.re_engines.myapp.databinding" minSdkVersion 15 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } // ここを追記 dataBinding { enabled = true } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:26.+' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" kapt 'com.android.databinding:compiler:2.3.2' // ここを追記 } repositories { mavenCentral() } |
説明用のサンプルアプリについて
DataBindingを説明するにあたり、1つのEditTextと1つのTextViewだけのアプリを作ります。EditTextに文字を入力するとTextViewにEditTextの文字数がカウントされるだけのアプリです。
実装対象のファイルはたったの3つです。とてもシンプルですが、DataBindingの概要を掴むには問題ないと思います。
- activity_main.xml(View)
- MainActivity.kt(View)
- MainViewModel.kt(ViewModel)
activity_main.xmlの実装
いきなりですが、コードです。ポイントは大きく3つあり、それぞれについて説明します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="viewModel" type="com.re_engines.myapp.databinding.viewmodel.MainViewModel"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="24dp" tools:context="com.re_engines.myapp.databinding.activity.MainActivity"> <EditText android:layout_width="368dp" android:layout_height="92dp" android:text="@={viewModel.messageEditText}" android:textSize="24sp" tools:layout_editor_absoluteY="81dp" tools:layout_editor_absoluteX="8dp"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:text="@{viewModel.messageLength}" android:textSize="24sp" tools:layout_editor_absoluteY="81dp" tools:layout_editor_absoluteX="0dp"/> </LinearLayout> </layout> |
従来のlayout xmlをlayoutタグで囲む
これはそいうものだと思って、layoutタグで囲んでください。後述しますが、こうすることで、AcitivityやFragmentなどから変数を使って、layoutにアクセスできるようになります。
dataタグ内のvariableタグにViewModelのクラスを指定する
type属性にViewModelのクラスを、name属性にxml内でアクセスする際に使う変数名を指定してください。後述しますが、ActivityやFragmentから実際にインスタンスを紐付けします。
タグ属性にViewModelのプロパティを設定する
ViewModelでのプロパティに変更があった場合に、Viewにもその変更を反映したい場合は、下記のように指定してください。(一方向)
今回の場合は、EditTextに入力された文字数をViewModelから通知してもらう必要があるので、下記の部分で設定されています。
1 2 3 4 | <TextView ... android:text="@{viewModel.messageLength}" .../> |
ユーザからの入力をViewからViewModelへ通知したい場合は、双方向にする必要があり、下記のように指定します。一方向との違いは、=があるかないかです。EditTextにユーザが入力した値をViewModelに通知するために双方向である必要があります。
1 2 3 4 | <EditText ... android:text="@={viewModel.messageEditText}" .../> |
MainActivity.ktの実装
コードです。ポイントは大きく2つあり、それぞれについて説明します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package com.re_engines.myapp.databinding.activity import android.databinding.DataBindingUtil import android.support.v7.app.AppCompatActivity import android.os.Bundle import com.re_engines.myapp.databinding.R import com.re_engines.myapp.databinding.databinding.ActivityMainBinding import com.re_engines.myapp.databinding.viewmodel.MainViewModel class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main) val binding: ActivityMainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) binding.viewModel = MainViewModel() } } |
DataBindingインスタンスの取得
Activityとlayout xmlの関連付けには従来は、コメントアウトされているようにsetContentViewメソッドを利用していました。DataBindingでは、setContentViewメソッドではなく、DataBindingUtilを利用します。
前述の通りlayoutタグを親とするlayout xmlを記述するとActivityMainBindingクラスというxmlファイル名に沿った名前のクラスが自動生成されますので、下記のようにインスタンスを取得します。(生成されない場合は、一度[Build]-[Rebuild Project]を実行してください)
1 2 | val binding: ActivityMainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) |
ViewModelインスタンスの設定
DataBindingのインスタンスが取得できたら、下記のようにViewModelのインスタンスを設定して、関連付けをしてください。
1 | binding.viewModel = MainViewModel() |
MainViewModel.kt
コードです。ポイントは大きく2つあり、それぞれについて説明します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package com.re_engines.myapp.databinding.viewmodel import android.databinding.BaseObservable import android.databinding.Bindable import com.re_engines.myapp.databinding.BR class MainViewModel: BaseObservable() { @Bindable var messageEditText: String = "" set(value) { field = value notifyPropertyChanged(BR.messageEditText) notifyPropertyChanged(BR.messageLength) } @get:Bindable var messageLength: String = "0" get() = "${messageEditText.length}" } |
Viewに変更を通知する
@get:Bindableを指定したプロパティは、xlayout xmlの属性に指定することで、Viewに反映させることができます。今回の場合は、messageLengthがそれにあたり、前述のTextViewに指定されているものになります。
また、下記で説明しますが、messageLengthに変更があったことを通知することで、Viewにも即座に反映させることができます。
Viewからの変更の通知を受けて、さらにViewにも変更を通知する
@Bindableを指定したプロパティは、Viewからの変更の通知を受け取ることもできます。(双方向)今回の場合は、messageEditTextプロパティになり、前述のEditTextに指定されているものになります。
ユーザがEditTextに文字を入力すると、messageEditTextプロパティのsetterが呼ばれます。そのsetterないで、messageLengthにも変更があったことを通知することで、ViewのTextViewにも変更内容(messageEditText.length)が反映されます。
1 | notifyPropertyChanged(BR.messageLength) |
さいごに
いかがでしたでしょうか。かなり単純な内容ではありますが、DataBindingの利便性が少しは理解できたのではないでしょうか。DataBindingはここで紹介したもの以外にも多くのことができますので、さらに詳しく知りたい方はAndroid Developerサイトで一読してみると良いと思います。
また、DataBindingを組み込むとコンパイルエラー時にどこで、なぜエラーとなっているのかがわかりにくい場合があります。その際は、こちらの記事でエラー内容の出力設定をしてみてください。
ぜひ、Kotlin + DataBindingで快適なAndroid開発ライフをお過ごしください。