Re: note

技術的な知見やポエムなど役に立たない情報を書き連ねる場所

【Android】LiveDataを使った双方向Data Binding

f:id:hik0leaf:20200505225329j:plain

LiveDataを使ってデータをViewに即座に反映させる方法はお馴染みですが、ViewからViewModelへリアクティブに制御する事例はまだ少ないため、ここではMediatorLiveDataを使ってその機能を実装してみたいと思います。

コードは以下の記事を参考にさせていただきました。ありがとうございます。

qiita.com

コード

まずはレイアウトです。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".MainActivity">

    <data>
        <variable
            name="viewModel"
            type="com.example.twowaydatabinding01.MainActivityViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        >

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="ユーザ名"
            android:layout_margin="8dp">

            <com.google.android.material.textfield.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:text="@={viewModel.username}"/>

        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="パスワード"
            android:layout_margin="8dp">

            <com.google.android.material.textfield.TextInputEditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:singleLine="true"
                android:text="@={viewModel.password}"/>

        </com.google.android.material.textfield.TextInputLayout>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:enabled="@{viewModel.canSubmit}"
            android:onClick="@{() -> viewModel.onClick()}"
            android:text="LOGIN"
            />

    </LinearLayout>

</layout>

次に要となるViewModelです。

MainActivityViewModel.kt

package com.example.twowaydatabinding01

import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import timber.log.Timber


class MainActivityViewModel : ViewModel() {

    val username = MutableLiveData<String>()
    val password = MutableLiveData<String>()

    private fun isValid(): Boolean {
        return !username.value.isNullOrBlank() && !password.value.isNullOrBlank()
    }

    val canSubmit = MediatorLiveData<Boolean>().also { result ->
        result.addSource(username) { result.value = isValid() }
        result.addSource(password) { result.value = isValid() }
    }

    fun onClick() {
        Timber.d(username.value + " - " + password.value)
    }
}

最後にActivityです。

MainActivity.kt

package com.example.twowaydatabinding01

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.example.twowaydatabinding01.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

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

        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
        binding.viewModel = viewModel
        binding.lifecycleOwner = this
    }
}

実行

よくあるログイン画面の構成です。ユーザ名とパスワードに何かしら入力されるまではLOGINボタンは押せません。ViewModelでユーザ名とパスワードの入力を監視し、両方入力された瞬間にボタンを有効化しています。再びどちらか一方でも空になるとボタンは無効化されます。

f:id:hik0leaf:20200505231447j:plain

まとめ

LiveDataを使って双方向のDataBindingを行うことができました。UIをリアクティブにすることで、ユーザ操作を円滑にするための手助けをすることができます。積極的に取り入れていきたいですね。

参考