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.