Remarques sur la liaison de données avec Kotlin et Recycler View w/ LiveData, DiffUtil
Depuis la dernière fois (Mémo: Kotlin + Room + RecyclerView + DataBinding) Diverses bibliothèques de support ont été ajoutées, je vais donc l'écrire à nouveau.
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 |
La partie DI est également omise cette fois Puisque Dao (Room) et Repository ne sont pas créés, seule l'interface est utilisée.
Repository Juste un référentiel. Je pense que ce n'est pas très différent de Dao.
BookmarkRepository.kt
interface BookmarkRepository{
fun bookmark(bookmarkId: String): Single<Bookmark>
fun bookmarks(userId: String): Flowable<List<Bookmark>>
fun store(bookmark: Bookmark): Completable
}
Usecase Utilisez le cas pour récupérer les livres non lus du référentiel
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
Injectez le référentiel.
Obtenez des signets non lus sous forme de LiveData avec ʻuncompletedItems () . Vous pouvez obtenir une liste des signets de Room avec Flowable en appelant
load ()`.
Désormais, lorsque la liste des signets est mise à jour, vous serez automatiquement averti de l'endroit où vous vous êtes abonné à 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)
}
}
}
Ce n'est pas connecté à Room.
Propriétés (text
, ʻidtext) qui affichent principalement les données des éléments de la liste, Il a une méthode qui reçoit l'interaction des éléments de liste. (ʻUpdate ()
)
Pour le moment, j'ai mis à jour l'heure à laquelle j'ai cliqué sur un élément.
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
Cette fois, c'est Kimo.
Abonnez-vous au BookstackViewModel.uncompletedItems ()
créé ci-dessus au début.
À ce stade, AppCompatActivity est spécifié dans le constructeur afin de transmettre LifecycleOwner
ou Lifecycle
.
Appelez ʻupdate () lorsque les données sont notifiées. Dans ʻupdate
, DiffUtil est utilisé pour notifier le changement.
LiveData est une notification de mise à jour, donc si vous définissez la même valeur, vous serez informé du montant défini.
Puisque je veux que l'élément soit mis à jour uniquement lorsque le contenu de chaque élément est modifié, utilisez DiffUtil pour bindViewHolder uniquement lorsque le contenu est modifié.
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 réduction
Fragment Dans Fragment, Adapter est défini pour RecyclerView. De plus, la liste des livres non lus a commencé à être acquise pour ViewModel.
BookstackFragment.kt
class BookstackFragment : Fragment(), Injectable {
private lateinit var binding: BookstackFragmentBinding
//Abréviation
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()
}
}
//Abréviation
}
c'est tout.