[JAVA] Mémo: Kotlin + Room + RecyclerView + DataBinding (Partie 2)

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.

environnement

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

Chose que tu veux faire

Source et notes

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
}

mouvement

croped.gif

Question

Choses à faire

c'est tout.

Recommended Posts

Mémo: Kotlin + Room + RecyclerView + DataBinding (Partie 2)
[Memo] JSUG Study Group 2020 Partie 1 Spring x Kotlin
Kotlin Memorandum-Code Edition (Partie 1)