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 |
@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を作る。
data class Role(val id: Long?
, val name: String
, val description: String
, val isLock: Boolean): BaseObservable()
Repository
※抜粋
リポジトリはDaoの戻り値Flowable<List<RoleEntity>>
をFlowable<List<Role>>
へ変換するだけ。
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用のリスナ追加も行う。
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が更新されるようになる。
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にセットする。
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