ViewModel 正在为 recyclerview 复制项目

ViewModel is duplicating items for recyclerview

我在我的项目中使用 Firebase firestore 分页和 ViewModel class,我已经为 recyclerview 设置了一个 onScroll 侦听器,并在滚动时获取数据,但是当我导航到另一个片段并返回到主片段时,整个项目都是重复的,我该如何解决这个问题?

这是我的代码

NewsViewModel.kt

class NewsViewModel : ViewModel() {

private val repo = FirebaseRepo(this)

val mutableLiveData = MutableLiveData<List<News>>()

fun getNewsList(tm:Timestamp): LiveData<List<News>> {
    repo.getNewsData(tm)
    return mutableLiveData
}
}

Repository.kt

class FirebaseRepo(private val viewModel: NewsViewModel) {

private val db = FirebaseFirestore.getInstance().collection("news")

fun getNewsData(tm: Timestamp) {
    val newsList = ArrayList<News>()
    if(viewModel.mutableLiveData.value != null) {
        newsList.addAll(viewModel.mutableLiveData.value!!)
    }
    db
        .orderBy("timestamp", Query.Direction.DESCENDING)
        .whereLessThan("timestamp",tm)
        .limit(6)
        .get()
        .addOnSuccessListener {
            Log.i("CodeCamp", it.toString())
            for (doc in it) {
                val imgUrl = doc.getString("imageUrl")
                val heading = doc.getString("headline")
                val timestamp = doc.getTimestamp("timestamp")
                val tagline = doc.getString("tagline")
                val type = doc.getString("type")
                newsList.add(News(doc.id, imgUrl!!, heading!!, tagline!!, type!!, timestamp!!))
            }
            viewModel.mutableLiveData.value = newsList
        }
}
}

MainActivity.kt

viewModel = ViewModelProvider(this).get(NewsViewModel::class.java)
    val layoutManager = LinearLayoutManager(view.context)
    recyclerView.layoutManager = layoutManager
    recyclerView.adapter = newsAdapter
    recyclerView.addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL))

    //observe to the viewModel
    viewModel.getNewsList(Timestamp.now()).observe(viewLifecycleOwner, Observer {
        newsAdapter.submitList(it)
    })

    recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            val visibleItemCount = layoutManager.childCount
            val totalItemCount = layoutManager.itemCount
            val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
            if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
                && firstVisibleItemPosition >= 0
                && totalItemCount >= PAGE_SIZE && !isLoading
            ) {
                isLoading != isLoading
                val list = viewModel.mutableLiveData.value!!
                viewModel.getNewsList(list[list.size - 1].timestamp).value
                Handler().postDelayed({
                    isLoading != isLoading
                },2000)
            }
        }
    })

我的适配器

class NewsAdapter : ListAdapter<News, NewsAdapter.ViewHolder> (NEWS_COMPARATOR) {

companion object {
    private val NEWS_COMPARATOR =  object : DiffUtil.ItemCallback<News>() {
        override fun areItemsTheSame(old: News, new: News): Boolean = old.id == new.id
        override fun areContentsTheSame(old: News, new: News): Boolean = old == new
    }
}

class ViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {

    fun bindView(news: News) {
        Glide.with(view).load(news.imageUrl).into(itemView.img)
        itemView.news_title.text = news.heading
        itemView.news_src.text = news.tagline
        itemView.news_type.text = news.type
        itemView.news_time.text = DateTime.getTimeAgo(news.timestamp.seconds)
        itemView.setOnClickListener {
            it.findNavController().navigate(R.id.action_homeFragment_to_newsFragment, bundleOf("id" to news.id))
        }
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.item_news,parent,false)
    return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val news = getItem(position)
    holder.bindView(news)
}

}

LiveData 旨在保存数据,每次您订阅它时,returns 都是它当前拥有的数据。一旦你回到你的片段,已经保存在 LiveData 中的数据将再次传回。

您可以通过几种不同的方式解决此问题: 您可以使用 SingleLiveEvents 来包装您的列表,并在您每次收到片段中的新数据时检查是否使用了数据。如果未使用,则表示这是来自 ViewModel 的全新数据。我使用这样的东西:

class SingleLiveData<T>(dataToBeConsumed: T? = null) {
    private var _data: T? = dataToBeConsumed

    val isConsumed
        get() = _data == null

    fun consumeData(): T {
        val curData = _data!!
        _data = null
        return curData
    }

    fun consumeDataSafely(): T? {
        val curData = _data
        _data = null
        return curData
    }
}

这将导致修改 ViewModel 并改为:

val mutableLiveData = MutableLiveData<SingleLiveData<List<News>>>()

并更改填充数据的方式,例如

viewModel.mutableLiveData.value = SingleLiveData(newsList)

在您的代码中,您将在更新 RecyclerView 之前检查数据 isConsumed

//observe to the viewModel
viewModel.getNewsList(Timestamp.now()).observe(viewLifecycleOwner, Observer {
    // Now it is SingleLiveData<List>
    if (!it. isConsumed)
     newsAdapter.submitList(it.consumeData())
})

您可以浏览有关该主题的更多信息:https://proandroiddev.com/singleliveevent-to-help-you-work-with-livedata-and-events-5ac519989c70

另一种方法是在更新您的 recyclerView 时使用 DiffUtil,这将导致仅更新新对象而没有重复项。 参考:https://blog.mindorks.com/the-powerful-tool-diff-util-in-recyclerview-android-tutorial

与您的问题无关,我建议不要在您的 FirebaseRepo 中保存 ViewModel 引用,而是 return 使用回调 lambda 函数保存数据。您正在创建一个循环依赖项,这可能会导致您的应用程序出现错误和问题。

问题出在你的解构中:

每次调用您的 ViewModel 的 Observer 都是来自 firebase 的 re-fetching 数据,并将其保存在您定义的名为 mutableLivedata 的变量中。

您需要为您的 recyclerView 观察 mutableLiveData 并在 init 函数中调用 getNewsItem() ,如下所示:

ViewModel.kt

val mutableLiveData = MutableLiveData<List<News>>()

fun getNewsList(tm:Timestamp) {
    repo.getNewsData(tm)
}

init {
    getNewsList(Timestamp.now())
}

MainActivity.kt

viewModel.mutableLiveData.observe(viewLifecycleOwner, Observer {
        newsAdapter.submitList(it)
    })

快乐编码..