ViewModel дублирует элементы для recyclerview - PullRequest
1 голос
/ 07 августа 2020

Я использую разбиение на страницы Firebase firestore и класс ViewModel в своем проекте, я установил прослушиватель onScroll для recyclerview и извлекаю данные при прокрутке, но когда я перехожу к другому фрагменту и обратно к основному фрагменту, все элементы дублируются, Как я могу исправить эту проблему?

Вот мой код

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)
}

}

Ответы [ 2 ]

1 голос
/ 08 августа 2020

Проблема в вашей деструктуризации:

Everytime Observer, называемый вашей ViewModel, повторно извлекает данные из firebase и сохраняет их в переменной под названием mutableLivedata, как вы определили. для вашего recyclerView и вызовите getNewsItem () внутри функции init, как показано ниже:

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)
    })

Счастливое кодирование ..

1 голос
/ 07 августа 2020

LiveData предназначена для хранения данных, и каждый раз, когда вы подписываетесь на нее, она возвращает данные, которые у нее есть в настоящее время. Как только вы вернетесь к своему фрагменту, данные, которые уже хранятся в LiveData, передаются обратно.

Вы можете решить эту проблему несколькими разными способами: вы можете использовать SingleLiveEvents, чтобы обернуть свой список и проверить, данные используются каждый раз, когда вы получаете новые данные внутри вашего фрагмента. Если он не используется, это означает sh новых данных, поступающих из 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)

В вашем коде вы проверите, есть ли данные isConsumed перед обновлением RecyclerView.

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

Вы можете узнать больше о топах c: https://proandroiddev.com/singleliveevent-to-help-you-work-with-livedata-and-events-5ac519989c70

Другой подход - использовать DiffUtil при обновлении вашего recyclerView, это приведет только к обновлению новых объектов и не будет иметь дубликатов. Ссылка: https://blog.mindorks.com/the-powerful-tool-diff-util-in-recyclerview-android-tutorial

Не имеет отношения к вашей проблеме, я советую не хранить ссылку ViewModel в вашем FirebaseRepo, а возвращать данные с помощью лямбда-функции обратного вызова. Вы создаете зависимость cycli c, которая может вызывать ошибки и проблемы в вашем приложении.

...