Search code examples
androidandroid-recyclerviewandroid-dialogfragment

Recycler View with duplicate rows after refreshing activity


After doing some research i found out that i need to clear the list that i am using each time i refresh the activity, after trying it to refresh the list at different places, the result remains the same and i have decided to give my brain a break and ask for help. The refreshing takes place on the PostDialogFragment class

i have circled the recycler view causing the issue

enter image description here

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

}

Solution

  • Overriding those usually solves this kind of issues:

    • getItemId: return long that uniquely identifies every item. For example, you could use an autoincrementing variable id for each new item object, or some hashcode. This should be paired with setHasStableIds(true). (By default it returns RecyclerView.NO_ID which value is -1).

    This helps to make sure RecyclerView e.g. identifies items properly, knows when OnBindViewHolder must be called or to return precise values on getAdapterPosition().

    • getItemCount: return an int with current number of items. Usually the Array.size() where the data is. (By default it returns 0).
    • getItemType: If there's multiple view types, return a unique number for each type of ViewHolder (usually starting with 0). (By default it returns 0).