LoginSignup
22
20

More than 5 years have passed since last update.

メモ:Kotlin+Room+RecyclerView+DataBinding

Last updated at Posted at 2018-03-17

KotlinとRoomでDataBindingした時のRecyclerView実装を試してみたのでメモ。

続き→ メモ:Kotlin+Room+RecyclerView+DataBinding(その2)

環境

Library Version
AndroidStudio 3.0.1
Kotlin 1.2.30
Room 1.0.0
RxJava2 2.1.10
Dagger2 2.15
SupportLibrary 26.1.0

やりたいこと

Kotlin+Roomでテーブルへの更新があると自動的にRecyclerViewへ反映させたい。

メモ

今回はRecyclerViewのところだけにする。Dagger2はまたの機会に。

Dao

RoomのDaoクラス。(一部)
interfaceにFlowableを指定すると、SQLite3上で変更を検知したらonNextで流してくれる。

Room上で使用できるRxJavaのクラスは以下の通り。

Class データがない時 データがある時 変更通知
Maybe onComplete onSuccess No
Single onError onSuccess No
Flowable no action onNext Yes


RoleDao.kt
@Dao
abstract class RoleDao {

    @Query("SELECT * FROM role WHERE id = :id")
    abstract fun find(id: Long): Single<RoleEntity>

    @Query("SELECT * FROM role")
    abstract fun findAll(): Flowable<List<RoleEntity>>

// […]

Model

普通のDataClassを作る。

Role.kt
data class Role(val id: Long?
                , val name: String
                , val description: String
                , val isLock: Boolean): BaseObservable()

Repository

※抜粋
リポジトリはDaoの戻り値Flowable<List<RoleEntity>>Flowable<List<Role>>へ変換するだけ。

RoleRepository.kt

fun RoleEntity.toRole() = Role(id , name, description, isLock)

class RoleRepository @Inject constructor(private val database: AppDatabase): Repository<Role> {
 // […]
    override fun all() = database.roleDao() 
            .findAll()                 // Flowable<List<RoleEntity>>
            .map {
                it.map { it.toRole() } // return Flowable<List<Role>>
            }
 // […]
}

ViewModel

RecyclerViewのAdapterに必要な件数とアイテム取得メソッドを作成。
DataBinding用のリスナ追加も行う。

RoleViewModel.kt
class RoleViewModel @Inject constructor (private val repo: RoleRepository): ViewModel(), LifecycleObserver {

    private val mRoleList: ObservableList<Role> = ObservableArrayList()

    fun initializeList() = repo.removeAll().subscribeOn(Schedulers.io())

    fun getItem(index: Int) = mRoleList[index]

    fun count() = mRoleList.size

    fun addRoleItemChangeListener(listener: ObservableList.OnListChangedCallback<ObservableList<Role>>) {
        mRoleList.addOnListChangedCallback(listener)
    }

    fun addRole(roleName: String) = repo
            .store(Role(id = null, name = roleName, description = roleName, isLock = false))
            .flatMap {
                repo.resolve(it)
            }
            .subscribeOn(Schedulers.io())
}

Adapter

RecyclerViewとViewModelを引っ付ける。
ViewModel.addRoleItemChangeListener()したことにより、通知によってViewが更新されるようになる。

RoleBindingRecyclerViewAdapter.kt

class RoleBindingRecyclerViewAdapter(val viewModel: RoleViewModel) : RecyclerView.Adapter<RoleBindingRecyclerViewAdapter.BindingHolder>() {
    init {
        viewModel.addRoleItemChangeListener(object: ObservableList.OnListChangedCallback<ObservableList<Role>>() {
            override fun onChanged(list: ObservableList<Role>?) {
                notifyDataSetChanged()
            }
            override fun onItemRangeChanged(p0: ObservableList<Role>?, positionStart: Int, itemCount: Int) {
                notifyItemRangeChanged(positionStart, itemCount)
            }
            override fun onItemRangeInserted(p0: ObservableList<Role>?, positionStart: Int, itemCount: Int) {
                notifyItemRangeInserted(positionStart, itemCount)
            }
            override fun onItemRangeMoved(p0: ObservableList<Role>?, fromPosition: Int, toPosition: Int, itemCount: Int) {
                for (i in 0..itemCount) {
                    notifyItemRangeRemoved(fromPosition + i, toPosition + i)
                }
            }
            override fun onItemRangeRemoved(p0: ObservableList<Role>?, positionStart: Int, itemCount: Int) {
                notifyItemRangeRemoved(positionStart, itemCount)
            }
        })
    }

    override fun getItemCount() = viewModel.count()

    override fun onBindViewHolder(holder: BindingHolder, position: Int) {
        holder.binding.apply {
            setVariable(BR.role, viewModel.getItem(position))
            notifyChange()
            executePendingBindings()
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): BindingHolder {
        val inflater = LayoutInflater.from(parent!!.context)
        val viewDataBinding: ViewDataBinding = DataBindingUtil.inflate(inflater, R.layout.item_role, parent, false)
        return BindingHolder(viewDataBinding)
    }

    class BindingHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)
}

Fragment

ViewModelをどっかからとってきて、Adapterにセットする。

RoleSettingFragment.kt

class RoleSettingFragment : DaggerFragment(), Injectable {

    private lateinit var binding: FragmentRoleSettingBinding

    @Inject
    lateinit var viewModelFactory: ViewModelFactory

    private val roleViewModel: RoleViewModel by lazy {
        ViewModelProviders.of(this, viewModelFactory).get(RoleViewModel::class.java)
    }

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

        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_role_setting, container, false)

        binding.recyclerView.layoutManager = LinearLayoutManager(this.context)
        binding.recyclerView.adapter = RoleBindingRecyclerViewAdapter(roleViewModel)

        // For Sample
        binding.imageAdd.setOnClickListener {
            binding.viewModel?.apply {
                this.addRole("ITEM:${System.currentTimeMillis()}")
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe{item ->
                            Snackbar.make(binding.root, "Created. ${item.name}", Snackbar.LENGTH_SHORT).show()
                        }
            }
        }
        return binding.root
    }
}

Layout XML

[省略]

思ったこと

割と簡単にできた、DataBindingすごい。
Adapterとか昔はもっとゴリゴリ書いてたような気がする。

RoomがFlowableで返せることをつかってるので、Realmとかでもできるはず。
多分だれかが書いてる。。。
いまのORMはどれもできるのかなあ・・・?

今回、RecyclerViewというよりDagger2が厄介だった。
次はDagger2に関するメモを書こう。

参考

RecyclerViewのDataBinding
Room 🔗 RxJava
DroidKaigi 2018 official Android app

22
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
20