Notes on Data Binding with Kotlin and Recycler View w/ LiveData, DiffUtil
From the last time (Memo: Kotlin + Room + RecyclerView + DataBinding) Various support libraries have been added, so I will write it again.
| Library | Version | - |
|---|---|---|
| AndroidStudio | 3.2 Beta 2 | |
| Kotlin | 1.2.51 | |
| Room | 1.0.0 | not use |
| RxJava2 | 2.1.16 | |
| Dagger2 | 2.15 | |
| SupportLibrary | 28.0.0-alpha3 |
DI part is omitted this time as well Since Dao (Room) and Repository are not created, only interface is used.
Repository Just a repository. I think it's not much different from Dao.
BookmarkRepository.kt
interface BookmarkRepository{
fun bookmark(bookmarkId: String): Single<Bookmark>
fun bookmarks(userId: String): Flowable<List<Bookmark>>
fun store(bookmark: Bookmark): Completable
}
Usecase Usecase to get unread books from the repository
class GetReadingAndUnreadBooks(private val repository: BookmarkRepository){
fun execute(params: Params): Flowable<List<Bookmark>> =
repository.bookmarks(params.userId)
.map { it.filter { isReading(it) }
.sortedBy { it.state().sort }
.sortedByDescending { it.latestDate() }
}
.distinctUntilChanged()
private fun isReading(bookmark: Bookmark): Boolean =
when (bookmark.state()) {
ReadState.Unread -> true
ReadState.Reading -> true
else -> false
}
class Params(val userId: String)
}
ViewModel
Inject the repository.
Get unread Bookmarks as LiveData with ʻuncompletedItems (). You can get a list of Bookmarks from Room with Flowable by calling load ()`.
Now when the Bookmark list is updated, you will be automatically notified where you have subscribed to incompletedItems ().
BookstackViewModel.kt
class BookstackViewModel @Inject constructor(private val bookmarkRepository: BookmarkRepository) : ViewModel() {
private val usecase = GetReadingAndUnreadBooks(bookmarkRepository)
val userId = MutableLiveData<String>()
private val _uncompletedItems = MutableLiveData<List<Bookmark>>()
fun uncompletedItems() = _uncompletedItems as LiveData<List<Bookmark>>
fun load(): Disposable {
val id = userId.value?: throw IllegalStateException("User id is must be not null.")
return usecase.execute(GetReadingAndUnreadBooks.Params(id))
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
_uncompletedItems.postValue(it)
}
}
}
This is not connected to Room.
Properties (text, ʻidtext) that mainly display list item data, It has a method that receives the interaction of list items. (ʻUpdate () )
For the time being, I updated the time when I clicked on the item.
UnreadItemViewModel.kt
class UnreadItemViewModel: ViewModel() {
val text = MutableLiveData<String>()
val idText = MutableLiveData<String>()
private val item = MutableLiveData<Bookmark>().apply {
this.observeForever {
it?.apply {
text.postValue(this.comment)
idText.postValue(this.id)
}
}
}
fun setBookmark(bookmark: Bookmark) {
item.postValue(bookmark)
}
fun bookmark() = item as LiveData<Bookmark>
fun update() {
item.value?.copy(comment = SimpleDateFormat("yyyy/mm/dd HH:mm:ss").format(Date()))?.apply {
item.postValue(this)
}
}
}
RecyclerViewAdapter
This time's Kimo.
Subscribe to the BookstackViewModel.uncompletedItems () created above at the beginning.
At this time, AppCompatActivity is specified in the constructor in order to pass LifecycleOwner or Lifecycle.
Call ʻupdate () when the data is notified. In ʻupdate, DiffUtil is used to notify the change.
LiveData is an update notification, so if you set the same value, you will be notified by the set amount.
Since I want you to update the item only when the content of each item is changed, use DiffUtil to bindViewHolder only when it is changed.
BookstackRecyclerViewAdapter.kt
class BookstackRecyclerViewAdapter(val context: AppCompatActivity, viewModel: BookstackViewModel): RecyclerView.Adapter<BookstackRecyclerViewAdapter.ViewHolder>() {
init {
viewModel.uncompletedItems().observe({ context.lifecycle }, {it?.apply {
update(this)
}})
}
private lateinit var recyclerView: RecyclerView
private var items: MutableList<Bookmark> = arrayListOf()
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
this.recyclerView = recyclerView
}
private fun update(list: List<Bookmark>) {
val adapter = recyclerView.adapter as BookstackRecyclerViewAdapter
val diff = DiffUtil.calculateDiff(DiffCallback(adapter.items, list))
diff.dispatchUpdatesTo(adapter)
this.items.clear()
this.items.addAll(list)
}
override fun getItemCount() = this.items.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val vm = UnreadItemViewModel()
vm.setBookmark(items[position])
holder.binding.vm = vm
}
override fun onCreateViewHolder(parent: ViewGroup, position: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding: ItemUnreadBookBinding = DataBindingUtil.inflate(inflater, R.layout.item_unread_book, parent, false)
binding.setLifecycleOwner(context)
return ViewHolder(binding)
}
class ViewHolder(val binding: ItemUnreadBookBinding): RecyclerView.ViewHolder(binding.root)
class DiffCallback(private val oldList: List<Bookmark>, private val newList: List<Bookmark>): DiffUtil.Callback() {
override fun areContentsTheSame(oldPosition: Int, newPosition: Int) = oldList[oldPosition] == (newList[newPosition])
override fun areItemsTheSame(oldPosition: Int, newPosition: Int) = oldList[oldPosition].id == (newList[newPosition]).id
override fun getNewListSize() = newList.size
override fun getOldListSize() = oldList.size
}
}
Activity abridgement
Fragment In Fragment, Adapter is set for RecyclerView. In addition, the list of unread books has started to be acquired for ViewModel.
BookstackFragment.kt
class BookstackFragment : Fragment(), Injectable {
private lateinit var binding: BookstackFragmentBinding
//Abbreviation
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
this.binding = DataBindingUtil.inflate(inflater, R.layout.bookstack_fragment, container, false)
this.binding.setLifecycleOwner(this.activity as AppCompatActivity)
this.binding.list.adapter = BookstackRecyclerViewAdapter(this.activity as AppCompatActivity, viewModel)
this.binding.list.layoutManager = LinearLayoutManager(context)
this.binding.list.setHasFixedSize(true)
return binding.root
}
override fun onStart() {
super.onStart()
disposable = viewModel.load()
}
override fun onStop() {
super.onStop()
disposable?.apply {
if (isDisposed) return
dispose()
}
}
//Abbreviation
}

that's all.