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)
})
快乐编码..
我在我的项目中使用 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)
})
快乐编码..