刷新后具有重复行的 Recycler View activity

Recycler View with duplicate rows after refreshing activity

经过一些研究后,我发现每次刷新 activity 时都需要清除我正在使用的列表,在尝试在不同的地方刷新列表后,结果保持不变我决定让我的大脑休息一下并寻求帮助。刷新发生在 PostDialogFragment class

我已经圈出导致问题的回收站视图

PostAdapter

package com.example.osrscritic.view

import android.text.method.ScrollingMovementMethod
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.osrscritic.databinding.PostRowItemBinding
import com.example.osrscritic.model.Skillvalue
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.QueryDocumentSnapshot
import com.google.firebase.firestore.QuerySnapshot

class PostsAdapter: RecyclerView.Adapter<PostsViewHolder>() {

    val postsMutableList : MutableList<DocumentSnapshot> = mutableListOf()

    fun setPostsList(statsList: List<DocumentSnapshot>) {
        postsMutableList.addAll(statsList)
        notifyDataSetChanged()
    }


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostsViewHolder {

        val binding = PostRowItemBinding.inflate(LayoutInflater.from(parent.context)
            ,parent,false)

        return PostsViewHolder(binding)
    }

    override fun onBindViewHolder(holder: PostsViewHolder, position: Int) {

        val post = postsMutableList[position]
        holder.binding.tvCritic.text = post["critic"].toString()

        val posts = post["posts"] as List<String>

        var count = 0;

        holder.binding.tvActualPost.movementMethod = ScrollingMovementMethod()

        for(p in posts) {
            holder.binding.tvActualPost.append("${count}: " + p + "\n")
            count++
        }



    }
    
    override fun getItemCount() = postsMutableList.size
}

class PostsViewHolder(val binding: PostRowItemBinding) : RecyclerView.ViewHolder(binding.root)

DisplayUserActivty

package com.example.osrscritic

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.osrscritic.databinding.ActivityDisplayUserBinding
import com.example.osrscritic.view.PostDialogFragment
import com.example.osrscritic.view.PostsAdapter
import com.example.osrscritic.view.StatsAdapter
import com.example.osrscritic.viewmodel.DisplayUserViewModel
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.DocumentSnapshot
import org.koin.androidx.viewmodel.ext.android.viewModel

class DisplayUserActivity : AppCompatActivity() {

    private val displayUserViewModel : DisplayUserViewModel by viewModel()
    lateinit var binding: ActivityDisplayUserBinding
    val statsAdapter = StatsAdapter()
    val postsAdapter = PostsAdapter()
    lateinit var critiquing: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val extras = intent.extras


        critiquing = extras?.getString("c")!!
        binding = ActivityDisplayUserBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.rsStatsRv.adapter = statsAdapter
        binding.rsStatsRv.layoutManager = LinearLayoutManager(this,
            LinearLayoutManager.VERTICAL, false)

        binding.rvPosts.adapter = postsAdapter
        binding.rvPosts.layoutManager = LinearLayoutManager(this,
            LinearLayoutManager.VERTICAL, false)

        configureObservers()

        displayUserViewModel.getFirebaseRef()

        displayUserViewModel.getOSRSPlayer()

        binding.ivWritePost.setOnClickListener {

           PostDialogFragment.newInstance(critiquing).show(supportFragmentManager
               , PostDialogFragment.KEY)


        }

    }
    
    private fun configureObservers() {



        displayUserViewModel.displayUserLiveData.observe(this, {
            statsAdapter.setStatsList(it.skillvalues)
            binding.tvRunescapeName.text = String.format("Account Name: %s", it.name)
            binding.tvCombat.text = String.format("Combat Level: %s", it.combatlevel.toString())

        })

        displayUserViewModel.loadingState.observe(this, {
            when(it){
                true -> binding.pgBar.visibility = View.VISIBLE
                false -> binding.pgBar.visibility = View.GONE
            }
        })

        displayUserViewModel.errorData.observe(this, {
            Toast.makeText(this, it, Toast.LENGTH_LONG).show()

        })

        displayUserViewModel.displayUserPostsLiveData.observe(this, {
            postsAdapter.postsMutableList.clear()
            it.document(critiquing).collection("posts").get()
                .addOnSuccessListener {
                    postsAdapter.setPostsList(it.documents)
                }
            
        })
        
    }

}

PostDialogFragment

package com.example.osrscritic.view

import android.app.AlertDialog
import android.app.Dialog
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import androidx.fragment.app.DialogFragment
import android.widget.LinearLayout

import android.widget.TextView

import android.widget.EditText

import android.view.Gravity
import androidx.core.app.ActivityCompat.recreate
import com.example.osrscritic.DisplayUserActivity
import com.example.osrscritic.viewmodel.DisplayUserViewModel
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FieldPath
import com.google.firebase.firestore.FieldValue
import com.google.firebase.firestore.SetOptions
import org.koin.androidx.viewmodel.ext.android.viewModel


class PostDialogFragment: DialogFragment() {

    private val displayUserViewModel : DisplayUserViewModel by viewModel()
    val currentUser = FirebaseAuth.getInstance().currentUser
    lateinit var critiquing: String
    val postsAdapter = PostsAdapter()


    companion object {
        const val KEY: String = "KEY2"


        //lateinit var param: String

        fun newInstance(text: String): PostDialogFragment {
            val args = Bundle()
            val postDialogFragment = PostDialogFragment()
            args.putString(KEY, text)
            postDialogFragment.arguments = args
            return postDialogFragment

        }

    }


    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {


        critiquing = arguments?.getString(KEY)!!

        Log.d("*******8", critiquing)

        val builder = AlertDialog.Builder(activity)

        val layout = LinearLayout(activity)
        val parms = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        )
        layout.orientation = LinearLayout.VERTICAL
        layout.layoutParams = parms

        layout.gravity = Gravity.CLIP_VERTICAL
        layout.setPadding(2, 2, 2, 2)

        val tv = TextView(activity)
        tv.text = "Text View title"
        tv.setPadding(40, 40, 40, 40)
        tv.gravity = Gravity.CENTER
        tv.textSize = 20f

        val et = EditText(activity)
        var etString = ""

        et.addTextChangedListener(object: TextWatcher{
            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
            }

            override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
            }

            override fun afterTextChanged(p0: Editable?) {

                Log.d("etText", et.text.toString())

                etString = et.text.toString()
            }

        })
        val tv1 = TextView(activity)
        tv1.text = "Input Student ID"

        val tv1Params = LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT
        )
        tv1Params.bottomMargin = 5
        //layout.addView(tv1, tv1Params)
        layout.addView(
            et,
            LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT
            )
        )
        //param = arguments?.getString(KEY)!!
        builder.setTitle("New Post")
        builder.setView(layout).setPositiveButton("Done", object: DialogInterface.OnClickListener{
            override fun onClick(p0: DialogInterface?, p1: Int) {

                activity?.let {
                    displayUserViewModel.displayUserPostsLiveData.observe(it, {
                        val post : MutableMap<String, Any> = mutableMapOf()

                        val posts : MutableList<String> = mutableListOf()

                        posts.add(etString)

                        post["critic"] = currentUser?.email!!
                        post["posts"] = posts

                        if(it.document(critiquing).collection("posts").document().equals(null)) {

                            it.document(critiquing).collection("posts")
                                .document(currentUser?.email!!).set(post)
                        }


                        it.document(critiquing).collection("posts")
                            .document(currentUser?.email!!).update("posts", FieldValue.arrayUnion(etString))

                    })

                }

                activity!!.finish()
                activity!!.overridePendingTransition( 0, 0);
                startActivity(activity!!.intent)
                  //trying to clear the list here once being refreshed
                postsAdapter.postsMutableList.clear()
                activity!!.overridePendingTransition( 0, 0);

            }

        })

        builder.setNegativeButton("Cancel", object:DialogInterface.OnClickListener{
            override fun onClick(p0: DialogInterface?, p1: Int) {
                dismiss()
            }

        })

        return builder.create()
    }

}

覆盖那些通常可以解决此类问题:

  • getItemId: return long 唯一标识每个项目。例如,您可以为每个新项目对象或某些 hashcode 使用自动递增变量 id。这应该与 setHasStableIds(true) 配对。 (默认情况下 returns RecyclerView.NO_ID 值是 -1)。

这有助于确保 RecyclerView 例如正确识别项目,知道何时必须调用 OnBindViewHoldergetAdapterPosition().

上的 return 精确值
  • getItemCount: return 具有当前项目数的 int。通常是数据所在的 Array.size()。 (默认情况下 returns 0)。
  • getItemType:如果有多种视图类型,return每个类型的ViewHolder(通常以0开头)的唯一编号。 (默认情况下 returns 0)。