はじめに
既存のプロジェクトにDataBindig/ViewBindingのどちらを適用するか悩んでいましたが、画面によってロジックの複雑さが異なっていたため、使い分けて適用するのがベストかなと思い、使い分けたポイントと使い方について書いていきたいと思います。
そもそも、DataBindingとViewBindingについては簡単に説明すると以下となります。
- DataBinding
2015年に登場し、XMLで定義したレイアウトと、そのレイアウトで使用されるデータモデルの連携をし易くするのを目的として提供されました。双方向バインティングが出来るのが特徴です。 - ViewBinding
2019年に登場し、DataBindingに比べてシンプルかつ軽量な代替手段として提供されました。特に、シンプルなUIとロジックで複雑な双方向バインディングを必要としないケースでのViewの参照方法を簡単にするために設計されています。
これから紹介するコードは、ベースとなるプロジェクトが以下となりますので、参考にして頂ければと思います。
DataBindingとViewBindingの有効化
app/build.gradle
に以下を設定
android {
buildFeatures {
viewBinding true
dataBinding true
}
}
上記を設定するだけで基本的には使えるようになります。ただし、使っているkotlinやライブラリのバージョンが低い場合はある程度上げないといけないので注意です。私の場合は
- Kotlin:1.6.21→1.8.22
- Room:2.4.2→2.5.2
にバージョンを上げました。
DataBindingの使い方と使い所
使い所について
上記のログイン画面にて、
- EditTextレイアウトに入力された文字列を照合するためにViewModelで参照
- ログインボタン押下時に入力文字列をクリアするためにViewModelから制御
の処理を満たす必要があり、双方向バインディングが必要なため、DataBindingを適用しました。
レイアウトファイルの設定
レイアウトXMLファイルとViewModelをDataBindingさせるため以下のように修正します。
修正前
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/login_fragment_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.LoginFragment">
<RelativeLayout
android:id="@+id/login_area"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal">
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
修正後
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="loginViewModel"
type="com.highcom.passwordmemo.ui.viewmodel.LoginViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/login_fragment_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.fragment.LoginFragment">
<RelativeLayout
android:id="@+id/login_area"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal">
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
双方向バインディングのレイアウト設定
パスワード入力エリアのandroi:text
の要素に以下のように設定します。
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_master_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:ems="10"
android:textSize="12sp"
android:text="@={loginViewModel.editMasterPassword}"
android:inputType="textPassword" />
@={}
の書式で設定する事で、editMasterPassword
の変数にレイアウトからの設定、ViewModelからの設定の双方に対応できます。
ViewModelからの設定を画面に反映させたいだけの場合
@{}
の書式になります。例として、パスワードの入力状況に応じて案内メッセージを変更する部分は以下のようになっています。
<TextView
android:id="@+id/navigate_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{loginViewModel.naviMessage}" />
ViewModelに変数の設定
パスワード入力エリアに入力された文字列を制御するための変数を以下のように定義する事で、レイアウトファイルから設定が出来るようになります。
class LoginViewModel : ViewModel() {
/** 入力パスワード */
val editMasterPassword = MutableStateFlow("")
// etc...
}
ViewModelから変数に設定
ログインボタン押下時に、入力されたパスワードをクリアするために、ログインボタン押下時に呼び出されるメソッド内で文字列をクリアする処理を以下のように実装します。
fun passwordLogin(context: Context, editPassword: String) {
// etc...
// 入力したパスワードはクリアする
editMasterPassword.value = ""
}
この処理が呼び出されると、入力文字列がクリアされた状態で画面にも反映されます。
ログインボタン押下時のイベントバインディング
以下のようにandroid:onClick
にラムダ式@{()->method}
でメソッドを呼び出すことでイベントメソッドのバインディングができます。
<Button
android:id="@+id/login_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> loginViewModel.passwordLogin(context, loginViewModel.editMasterPassword)}"
android:text="@string/login" />
レイアウトViewを引数で渡せるイベントメソッドバインディング
android:onClick
に設定するイベントメソッドは別の指定の方法もあり、以下のように@{instance::method}
の形式で記載する事で、レイアウトのViewを引数に渡したメソッドをバインディングすることが出来ます。
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/edit_ref_url"
android:inputType="text"
android:text="@{fragment.passwordEditData.url}"
android:onClick="@{fragment::onUrlParseTextClick}" />
fun onUrlParseTextClick(view: View) {
if (view is EditText) {
if (view.text.toString() == "") return
val uri = Uri.parse(view.text.toString())
val intent = Intent(Intent.ACTION_VIEW, uri)
val chooser = Intent.createChooser(intent, "選択")
startActivity(chooser)
}
}
ViewBindingの使い方と使い所
使い所について
上記の設定画面のように、この画面で設定されたデータをSharedPreference
に保存し、他の画面にて設定された状態に応じて表示内容を変える場合です。
そういった場合には、データの変更状態に応じてリアルタイムに画面へと反映させる必要が無いため、シンプルなバインディング方法であるViewBindingを利用します。
レイアウトファイルの設定
レイアウトファイルに変更の必要はありません。
なので、例として生体認証ログインの有効/無効スイッチは以下のようなレイアウト設定のままです。
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/setting_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fastScrollEnabled="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Switch
android:id="@+id/biometric_login_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/login_biometrics_enable"
android:checked="true" />
FragmentでViewBindingの利用
ViewBindingをする事で、findViewById
をする必要が無くなり、いきなり、上記の生体認証ボタンの有効/無効スイッチにアクセス出来るようになります。
ViewBindingの実装方法は以下です。
class SettingFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSettingBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 生体認証ログインスイッチ処理
val biometricLoginSwitch = binding.biometricLoginSwitch
biometricLoginSwitch.setOnCheckedChangeListener { _, b ->
// SharedPreferenceにデータを保存する
loginDataManager?.setBiometricLoginSwitchEnable(b)
}
}
}
oncreateView
の中でFragmentSettingBinding
と言う名称でいきなり、inflate
メソッドを呼び出す事が出来ます。
命名規則が決まっており、XMLファイル名+Bindingといった規則となるため、fragment_setting.xml
という名称ならFragmentSettingBinding
になります。
その後、onViewCreated
の中で、バインディング変数であるbinding
から、binding.biometricLoginSwitch
という変数でSwitch
レイアウトにアクセスする事が可能となり、リスナーメソッドの実装を記述する事が出来ます。
さいごに
以上で、DataBindingとViewBindingの使い方と使い分けについての説明は以上となります。
最初は、すべてDataBindingで実装すればOKだと思って進めていたのですが、途中で、やろうとしていることはViewBindingで十分な部分もあるなと思い直したため、一部には簡単に実装ができるViewBindingを適用するようにしました。
なので、冒頭で紹介しているGitHubのプロジェクトでは、上記で紹介しているファイル以外にもDataBindingとViewBindingを使い分けて実装をしておりますので、ぜひ参考にして頂ければと思います。