刷新后具有重复行的 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
例如正确识别项目,知道何时必须调用 OnBindViewHolder
或 getAdapterPosition()
.
上的 return 精确值
getItemCount
: return 具有当前项目数的 int
。通常是数据所在的 Array.size()
。 (默认情况下 returns 0
)。
getItemType
:如果有多种视图类型,return每个类型的ViewHolder
(通常以0
开头)的唯一编号。 (默认情况下 returns 0
)。
经过一些研究后,我发现每次刷新 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
: returnlong
唯一标识每个项目。例如,您可以为每个新项目对象或某些hashcode
使用自动递增变量id
。这应该与setHasStableIds(true)
配对。 (默认情况下 returnsRecyclerView.NO_ID
值是-1
)。
这有助于确保 RecyclerView
例如正确识别项目,知道何时必须调用 OnBindViewHolder
或 getAdapterPosition()
.
getItemCount
: return 具有当前项目数的int
。通常是数据所在的Array.size()
。 (默认情况下 returns0
)。getItemType
:如果有多种视图类型,return每个类型的ViewHolder
(通常以0
开头)的唯一编号。 (默认情况下 returns0
)。