Google codelabs Android DataBinding のコードのつながりをふわ~っと確認


最近、Databindingを理解するために、Google codelabs Android DataBindingで学習してみたのですが、思考停止で書かれている通りに実装したところ、Databindingは実装できたものの、どのコードと、どのコードが関連して動作しているのかが、いまいちピンときていなかったので、コード同士のつながりを確認することにしました。
この記事はその確認結果のまとめです。

Google codelabs Android DataBindingで作るもの

このCodelabsでは以下のようなものを作ります。(ほぼ完成しているものが準備されている)

LIKEボタンをポチポチするとLikesの数字が増えて、Likesが一定以上になると、プログレスバーとアイコンが変化します。
最初から準備されているコードは、DataBindingを使用せずにこの挙動を実現している状態なので、それを修正してDataBindingで実現する、というのがこのCodelabsの主旨です。

Codelabsの内容

全力でサボりますが、こちらの記事で全編内容が紹介されています。分かりにくい部分(Codelabs内のケアレスミスとか)も注釈として書かれてるので参考になりました。

コードのつながりを読み解く

読み解くというほどのものでもないかもですが、読み解きます笑
※ここで扱うコードは、Codelabsの最初に提供されるものではなく、Codelabsが完了した段階でのものです。

かなりざっくり言うと、このアプリは
・PlainOldActivity.kt
・SimpleViewModel.kt
・BindingAdapter
・plain_activity
で構成されています。

これらの中から、DataBindingの構成要素を独断と偏見で抜き出すと、こんな概略図になります。(いろいろ省略してます)


これらのつながりを、ざっくり見ていきます。


まず、plain_activity.xml内でレイアウト変数(layout variables)が定義されています。

plain_activity.xml
   <data>
       <variable
            name="viewmodel"
            type="com.example.android.databinding.basicsample.data.SimpleViewModel"/>
   </data>

nameはviewmodelとなっていて、
typeは"com.example.android.databinding.basicsample.data.SimpleViewModel"
を指定しており、これはSimpleViewModel.ktのことを指しています。

なので意味的には、「viewmodelは、SimpleViewModel.ktのことを指す」という具合になりそうです。

続いてタグ内で
android:text="@{}"というような表記 (layout expression、レイアウト式) が出てきます。
ここにdataタグ内で記述したレイアウト変数が入ります。
こんな感じ↓

plain_activity.xml
<TextView
            android:id="@+id/plain_name"
            android:text="@{viewmodel.name}"    
以下省略     
/>

ここでdataタグ内のnameで定義した、viewmodel@{viewmodel.name}という形で記述されています。
これはデータの紐付け先を示しています。

viewmodelはSimpleViewModel.kt(com.example.android.databinding.basicsample.data.SimpleViewModel)を型として指定していたので、「SimpleViewModel.ktからnameを引っ張って来い」というように読めます。

ただ、plain_activity.xmlからSimpleViewModel.ktへ直接紐付けされているわけでありません。
PlainOldActivity.ktは以下のようになっています。

PlainOldActivity.kt
class PlainOldActivity : AppCompatActivity() {

    // Obtain ViewModel from ViewModelProviders
    private val viewModel by lazy { ViewModelProviders.of(this).get(SimpleViewModel::class.java) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //➀
        val binding : PlainActivityBinding =
                DataBindingUtil.setContentView(this, R.layout.plain_activity)

        binding.lifecycleOwner = this

        //➁
        binding.viewmodel = viewModel
    }
}

➀はbinding先のレイアウト(plain_activity.xml)が定義されています。

➁はbinding.viewmodel = viewModelと定義されています。
binding.viewmodelの”viewmodel”は、plain_activity.xmlで定義したviewodelのことを指しています。

一方、右辺のviewModel(※Mが大文字なので”viewmodel”とは異なる)はSimpleViewModel.ktのViewModelを、 ViewModelProvidersで受け取ったものです。

なので、binding.viewmodel = viewModelは
plain_activity.xmlとSimpleViewModel.ktのViewModelの紐付けを表しています。

ここまでの関係図を矢印で書くと、こんな感じになります。↓

plain_activity.xmlとSimpleViewModel.ktが紐づけされた状態なので、
android:text="@{viewmodel.name}"や、app:hideIfZero="@{viewmodel.likes}"**のように、レイアウトからSimpleViewModel.ktの変数(name, likes)を引用することができます。

例えばnameは、_nameが代入されており、_nameはMutableLiveData("Ada")として定義されているので、Nameのところに”Ada”が表示されます。

BindingAdapter

さて、残りのBindingAdapter.ktでは、plain_activity.xmlが受け取った変数を引数とするメソッドが定義されています。以下に一例を挙げます。

BindingAdapter.kt
@BindingAdapter("app:popularityIcon")
fun popularityIcon(view: ImageView, popularity: Popularity) {

    val color = getAssociatedColor(popularity, view.context)

    ImageViewCompat.setImageTintList(view, ColorStateList.valueOf(color))

    view.setImageDrawable(getDrawablePopularity(popularity, view.context))
}
plain_activity.xml
<ImageView
app:popularityIcon="@{viewmodel.popularity}"
以下省略
/>

この場合だと@BindingAdapter(app:popularityIcon)という表記で、popularityIconメソッドが、plain_activity.xmlのImageViewと紐付けされています。

メソッドの引数の型は、(ビューの型,レイアウト側から受けとる変数の型)となっています。
上の例だと、fun popularityIcon (view: ImageView, popularity: Popularity)のようになっていて、渡されたpopularityの中身によってメソッドの処理結果が変わるため、ImageViewの画像表示が切り替わるようになっています。

これで、SimpleViewModel.ktからBindingAdapter.ktまでのつながりが(ふわ~っと笑)確認できました。

BindingAdapterの部分だけ抜き出した場合はこんな感じ↓

ボタンタップからUI更新の流れ

このアプリはLIKEボタンがトリガーになっていて、LIKEボタンのタップ回数によって画像や色が変わっていきますが、どういう挙動になっているのか確認するために、流れをまとめました。
(図はなんやかんや省略してます。"イメージをつかむ"程度の認識で眺めてください)

LIKEボタンのタップ

・LIKEボタンを押すと、ボタンがSimpleViewModelのonLike()メソッドと紐付いているので、_likesが増える。

・_likesはlikes、popularityの二つのLiveDataに代入されるので、2つに分岐する。

likes側

➀TextViewがlikesを受け取り、String型に変換し、タップ数として表示する。

➁プログレスバー(ProgressBar)は2つの@BindingAdapterと紐づいているので分岐する。
 ➁-1 likesが0ならばプログレスバーを非表示、それ以外の場合は表示する。
 ➁-2 likesの数量に応じてプログレスバーの進捗具合を表示する。

popularity側

Transformations.mapで_likesがPopularity型へ変換され、when式でPopularityレベルを分ける。

➀、➁共にSimpleViewModel.ktからpopularityを受け取り、@BindingAdapterで紐づいたメソッドへ、引数として渡す。
➀はImageViewの画像の切り替え、➁はプログレスバーの色変更をする。

感想

まとめるのにだいぶ時間がかかってしまいましたが、概略図がなんとなくイメージできるようになったので、やった意味はあったと思います。実際にたくさん使って、何か作ってみようと思います~^^