arveltのソフトウェア技術メモ

Arvelt's software technology memo

RecycleViewでDataBindingとLiveDataを使う

これ今はもうスタートラインレベルなのに毎回用意するのが大変なので、そろそろ一気に自動生成してくれるようにしてほしい。 これは何度も書いているので未来の私のために残す。見ながら書いて足りないものに気づいたら直す感じで  
 
Android StudioでFragment(List)を選んで生成して使用する。 MemoとMemoItemというビューの関係を関係を作るのがこの記事がゴール。使用したkotlinは 1.3.31 、 minSdkVersionは 21

f:id:arvelt:20190924183034p:plainf:id:arvelt:20190924183050p:plain

 
 
gradleでdataBindingを有効にする

android {
    dataBinding {
        enabled = true
    }
}

 
 
まずデータクラスを定義。リストの1項目を表す。Memo.kt

data class MemoItem(
    val id: String,
    val message: String
) : Serializable

 
 
ビューモデルを定義。MemoListViewModel.kt

class MemoListViewModel : ViewModel() {
    val items = MutableLiveData<List<MemoItem>>()

    fun loadItems() {
        val items = arrayListOf(
            MemoItem("1", "aaa"),
            MemoItem("2", "bbb"),
            MemoItem("3", "ccc"),
            MemoItem("4", "ddd"),
            MemoItem("5", "eee")
        )
        this.items.value = items
    }
}

 
 
リサイクルビューのXMLをLayoutタグでくくり、variableを定義。fragment_memo_list.xmlとする

<?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="viewModel"type="net.arvelt.memoapp.ui.memolist.MemoListViewModel" />
    </data>

    <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/list"
            android:name="net.arvelt.memoapp.MemoListFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            app:layoutManager="LinearLayoutManager"
            tools:context=".ui.memolist.MemoListFragment"
            tools:listitem="@layout/fragment_memo_list_item" />
</layout>

 
 
リサイクルビューの1行分の項目のXMLをLayoutタグでくくり、variableを定義。fragment_memo_list_itemとする

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

    <data>
        <variable name="viewModel" type="net.arvelt.memoapp.model.MemoItem" />
    </data>

    <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

        <TextView
                android:id="@+id/itemId"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_margin"
                android:text="@{viewModel.id}"
                android:textAppearance="?attr/textAppearanceListItem" />

        <TextView
                android:id="@+id/content"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_margin"
                android:text="@{viewModel.message}"
                android:textAppearance="?attr/textAppearanceListItem" />
    </LinearLayout>

</layout>

 
 
Bindingクラスを生成させるために一度コンパイルする。アダプタークラスをデータバインディングを使用した形に置き換える。MyMemoRecyclerViewAdapter.ktとする

class MyMemoRecyclerViewAdapter(
    private var mValues: List<MemoItem>,
    private val mListener: OnListFragmentInteractionListener?
) : RecyclerView.Adapter<MyMemoRecyclerViewAdapter.ViewHolder>() {

    private val mOnClickListener: View.OnClickListener

    init {
        mOnClickListener = View.OnClickListener { v ->
            val item = v.tag as MemoItem
            mListener?.onListFragmentInteraction(item)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding: FragmentMemoListItemBinding =
            DataBindingUtil.inflate(
                LayoutInflater.from(parent.context), R.layout.fragment_memo_list_item, parent, false
            )
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = mValues[position]
        holder.binding.viewModel = MemoItem(item.id, item.message)
        with(holder.binding.root) {
            tag = item
            setOnClickListener(mOnClickListener)
        }
    }

    fun setData(items: List<MemoItem>) {
        this.mValues = items
    }

    override fun getItemCount(): Int = mValues.size

    inner class ViewHolder(val binding: FragmentMemoListItemBinding) : RecyclerView.ViewHolder(binding.root)
}

 
 
MemoListFragmentのOnCreateViewをDataBidingを使用した形に置き換える。 アダプターの登録をして、viewModelを監視する。 初期値がonResumeでロードされるようにする。

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        val binding: FragmentMemoListBinding = DataBindingUtil.inflate(
            inflater, R.layout.fragment_memo_list, container, false
        )
        binding.viewModel = ViewModelProviders.of(this).get(MemoListViewModel::class.java)
        binding.list.adapter = MyMemoRecyclerViewAdapter(arrayListOf(), listener)
        binding.viewModel!!.items.observe(this, Observer {
            val adapter = binding.list.adapter as MyMemoRecyclerViewAdapter?
            adapter?.setData(it)
        })
        this.binding = binding
        return binding.root
    }

    override fun onResume() {
        super.onResume()
        this.binding.viewModel!!.loadItems()
    }

 
 
アダプターでクリック時の処理をしているのでActivityでクリックされたときのMemoItemが取得できる。

override fun onListFragmentInteraction(item: MemoItem?) {
    // 遷移など
}