ViewModel正在为recyclerview复制项目



我在我的项目中使用Firebase firestore分页和ViewModel类,我为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
}
}

存储库.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
}
}
}

主要活动.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旨在保存数据,每次订阅它时都会返回它当前拥有的数据。一旦你回到你的片段,已经保存在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引用,而是使用回调lambda函数返回数据。你正在创建一个循环依赖项,这可能会在你的应用程序中导致错误和问题。

问题出在您的析构函数中:

调用ViewModel的Everytime Observer正在从firebase中重新获取数据,并将其保存在您定义的名为mutableLivedata的变量中。

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

ViewModel.kt

val mutableLiveData = MutableLiveData<List<News>>()
fun getNewsList(tm:Timestamp) {
repo.getNewsData(tm)
}
init {
getNewsList(Timestamp.now())
}

主活动.kt

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

快乐编码。。