Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
45
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

とにかく簡単にDataBindingまとめ

〇前置き

前回、前々回の続編です。
とにかく簡単にViewModelまとめ
とにかく簡単にLiveDataまとめ
ViewModel,LiveDataを会得している人はそのままどうぞ。

〇DataBindingって何?

JetPackのコンポーネントの一つ。
簡単に書くとViewModelの内容をレイアウトファイル(activity_mail.xml等)に結び付けて、UIの操作をアクティビティ、やフラグメントから完全に分離できる。
つまりMVVMができる!!

〇参考URL

・[公式]
https://developer.android.com/topic/libraries/data-binding/index.html
・Data Binding — Lessons Learnt
https://medium.com/androiddevelopers/data-binding-lessons-learnt-4fd16576b719
・Android Data Binding codelab
https://codelabs.developers.google.com/codelabs/android-databinding/#0
コードラボのサンプルは手順を追っていてとてもわかりやすかったです。

〇プログラム

・GitHub

build.gradle[app]

DataBindingには以下が必要。
'kotlin-kapt'は公式にはなかったのですが、Adapterを使用するときにないとエラーが出てしまいました。

build.gradle[app]
apply plugin: 'kotlin-kapt'

android {
    dataBinding {
        enabled = true
    }
}

User.kt

名前と年齢を保持するだけのデータクラスを作成。

User.kt
data class User(val name: String, val age: Int)

UserViewModel.kt

前回と違いViewModelの更新対象をクラス型(User)にしてみました。
別にnameとageをViewModelに定義して1つ1つMutableLiveDataしても同じ結果です。
change関数を作成して、呼ばれると監視対象のuserの中身が変わります。
せっかくなのでTransformationsを使用しています。
これは、LiveDataの更新を受けて他のLiveDataに連動させるものです。
今回はuserのageの更新を受けて変数logoの値が変わります。
これによってlogoを参照しているところが切り替わり、最終的には画像が切り替わるというピタゴラスイッチ的な感じです。

UserViewModel.kt
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.Transformations
import android.arch.lifecycle.ViewModel

class UserViewModel : ViewModel() {

    // LiveViewを作成
    val user: MutableLiveData<User> by lazy {
        MutableLiveData<User>()
    }
    // 初期値を設定
    init{
        user.value = User("SUZUKI",45)
    }
    // ボタンクリック時の値を変更する関数
    fun change(){
        user.value = User("OHTANI",25)
    }

    // LiveDataの更新を他のLiveDataの更新に依存させる
    val logo: LiveData<LogoMark> = Transformations.map(user) {
        when {
            user.value!!.age > 40 -> LogoMark.LOGO1
            else -> LogoMark.LOGO2
        }
    }
}

enum class LogoMark {
    LOGO1,
    LOGO2
}

MainActivity.kt

アクティビティを見てみると、データの詳細に一切触れず、スッキリしていることがわかります。
ViewModelとDataBindingのインスタンスを作成して、結び付けています。

MainActivity.kt
import android.arch.lifecycle.ViewModelProviders
import android.databinding.DataBindingUtil
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kirin3.jp.viewmodel_livedata_databinding.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ViewModelのインスタンスを作成
        val viewModel: UserViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)

        // DataBindingのインスタンスを作成(onCreateの外でもよい)
        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        // ViewModelのインスタンスを作成を設定
        binding.viewModel = viewModel
        // ライフサイクル所有者を設定
        binding.lifecycleOwner = this
    }
}

activity_main.xml

代わりに大きく変わるのがレイアウトファイルになります。
作法があって<Layout>で囲まれていること、<data>で連携するViewModelを指定することがあります。
また変数を表示するには
android:text="@{viewModel.user.name}"/>
関数を実行するには
android:onClick="@{(v) -> viewModel.change()}"
といった独自の書き方が必要になります。
他にも
android:visibility="@{viewModel.flag ? View.VISIBLE : View.GONE}"
とか
android:transitionName='@{"image_" + id}'
とか、色んな書き方ができますが、まとめてあるサイトが見つからないので、見かけたものはメモしています。

この中でも複雑なのが<ImageView>に設定した
app:logoIcon="@{viewModel.logo}"
です。
これはDataBindingにはBindingAdapterというメソッド群が用意されていて、独自に拡張してレイアウトファイルから呼び出すことができ、その時のレイアウトの挙動を設定できるものです。
logoIconという名前は私が勝手につけたものになります。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>

<!-- <layout>で囲まないと駄目 -->
<layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <!-- どのViewModelと結び付けるか宣言 -->
    <data>
        <variable
                name="viewModel"
                type="kirin3.jp.viewmodel_livedata_databinding.UserViewModel"/>
        <import type="android.view.View"/>
    </data>

    <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">

        <LinearLayout
                android:orientation="horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="50dp"
                app:layout_constraintTop_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent">
            <TextView
                    android:id="@+id/text1"
                    android:layout_margin="10dp"
                    android:textSize="20sp"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@{viewModel.user.name}"/>
            <TextView
                    android:id="@+id/text2"
                    android:layout_margin="10dp"
                    android:textSize="20sp"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@{String.valueOf(viewModel.user.age)}"/>

        </LinearLayout>


        <ImageView
                android:id="@+id/imageView"
                android:layout_width="60dp"
                android:layout_height="60dp"
                android:layout_marginBottom="60dp"
                app:layout_constraintBottom_toTopOf="@+id/button"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:logoIcon="@{viewModel.logo}"/>

        <Button
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginBottom="30dp"
                android:text="Change"
                android:onClick="@{(v) -> viewModel.change()}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent" />

    </android.support.constraint.ConstraintLayout>
</layout>

BindingAdapters.kt

BindingAdapterの設定です。
ここではLiveViewで設定した変数logoを受け取って、表示する画像を選択しています。
他にもBindingAdapterはたくさんあり、Viewに様々な変更を行うことができます。
adapters一覧

BindingAdapters.kt
import android.databinding.BindingAdapter
import android.support.v4.content.ContextCompat
import android.widget.ImageView

@BindingAdapter("app:logoIcon")
fun logoIcon(view: ImageView, logo: LogoMark) {

    when(logo) {
        LogoMark.LOGO1 -> {
            view.setImageDrawable(ContextCompat.getDrawable(view.context, R.drawable.bat))
        }
        else -> {
            view.setImageDrawable(ContextCompat.getDrawable(view.context, R.drawable.ball))
        }
    }
}

〇結果

Screenshot_1568470805.png

CHANGEボタンを押すと

Screenshot_1568470808.png

情報が切り替わり、年齢に応じて画像も切り替わる。

Screenshot_1568470819.png

横にしてアクティビティが再起動してもViewModelの情報が生きていてDatabindingで即座に反映

〇まとめ

これで、ViewModel,LiveData,DataBindingのAndroid MVVM三種の神器のまとめができたかと思います。
次まとめるのはDagger2かなぁ。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
45
Help us understand the problem. What are the problem?