android-recyclerviewandroid-roomandroid-mvvmandroid-room-relation

How to avoid duplicate data in roomdatabase?


I am creating an app for my students. I am a teacher and hobby developer. Therefore, I need help. With three fragments, I successively create the name of the class, and the data of the student(first Name, last Name,..) then I attribute evaluation data to him. Everything works correctly. Except when I post all my students in the class in different groups. I end up with a student of the same name each time I validate the assessment for a new student. So over time, I'm going to end up with the same student 30 times in this display. I don't understand where the problem comes from. thank you for guiding me.

here is my code to add a student:

class AddFragment : androidx.fragment.app.Fragment() {

    private lateinit var  mUserViewModel: UserViewModel
    private lateinit var mEvalViewModel : EvalViewModel
    private lateinit var mUserEvalViewModel: UserEvalViewModel
    private val args by navArgs<AddFragmentArgs>()
    //Pour accéder au fab
    private var _binding: FragmentAddBinding? = null
    private val binding get() = _binding!!


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentAddBinding.inflate(inflater, container,false)
        val view = binding.root
        mUserViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        mEvalViewModel = ViewModelProvider(this).get(EvalViewModel::class.java)
        mUserEvalViewModel = ViewModelProvider(this).get(UserEvalViewModel::class.java)

        binding.addNameGroupEt.setText(args.currentGru.nameGroupG)
        binding.btnAddFragmentAdd.setOnClickListener {
            insertDataToDatabase()
            soundClic()
        }

        return view
    }

    private fun insertDataToDatabase() {
        val idUser = 0
        val firstName = binding.addFirstNameEt.text.toString()
        val lastName = binding.addLastNameEt.text.toString()
        val nbTeam = binding.addNumberTeamEt.text.toString()
        val gru_id_reference = args.currentGru.idGroup

        if(inputCheck(firstName, lastName, nbTeam)){
            //Create User OBject
            val user = User(idUser, args.currentGru.nameGroupG,  firstName, lastName, nbTeam, gru_id_reference)
            //Add Data to Database
            mUserViewModel.addUser(user)
            val action = AddFragmentDirections.actionAddFragmentToAddEvalFragment(user)
            findNavController().navigate(action)
            Toast.makeText(requireContext(), "Ajout réussi !", Toast.LENGTH_SHORT).show()
        }else{
            Toast.makeText(requireContext(), "Veuillez remplir tous les champs, SVP !", Toast.LENGTH_LONG).show()
        }
    }

    private fun inputCheck(firstName: String, lastName: String, nbTeam: String): Boolean{
        return !( TextUtils.isEmpty(firstName) && TextUtils.isEmpty(lastName) && TextUtils.isEmpty(nbTeam))
    }

    private fun soundClic() {
        val mediaPlayer: MediaPlayer = MediaPlayer.create(context, R.raw.select_click)
        mediaPlayer.start()
    }
}

Then i arrive on my code to add Evaluation for my student:

here the code to addEvalFragment:

class AddEvalFragment  : androidx.fragment.app.Fragment() {

    private lateinit var  mUserViewModel: UserViewModel
    private lateinit var mEvalViewModel : EvalViewModel
    private lateinit var mUserEvalViewModel: UserEvalViewModel
    private val args by navArgs<AddEvalFragmentArgs>()
    private var _binding: FragmentAddEvalBinding? = null
    private val binding get() = _binding!!


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentAddEvalBinding.inflate(inflater, container,false)
        val view = binding.root
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val adapter = AddEvalAdapter()
        val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerview)
        recyclerView?.adapter = adapter
        recyclerView?.layoutManager = LinearLayoutManager(requireContext())

        mUserViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        mEvalViewModel = ViewModelProvider(this).get(EvalViewModel::class.java)
        mUserEvalViewModel = ViewModelProvider(this).get(UserEvalViewModel::class.java)

        mUserViewModel.getOneUser(args.currentUser.firstName, args.currentUser.lastName, args.currentUser.nameGroup)?.observe(viewLifecycleOwner, Observer {
            user ->
            user.let {
                adapter.setDataOneUser(user)
            }
        })
    }
}

and the code of my addEvalAdapter:

class AddEvalAdapter: RecyclerView.Adapter<AddEvalAdapter.MyViewHolder>() {

    private lateinit var context: Context
    var userList = emptyList<User>()

    class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        context = parent.context
        return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.custom_row_add_eval, parent, false))
    }

    override fun getItemCount(): Int {
        return userList.size
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val currentItem = userList[position]

        holder.itemView.findViewById<TextView>(R.id.firstName_txt_Add_Eval).text = currentItem.firstName
        holder.itemView.findViewById<TextView>(R.id.lastName_txt_Add_Eval).text = currentItem.lastName
        holder.itemView.findViewById<TextView>(R.id.numeroTeam_txt_Add_Eval).text = currentItem.nbTeam

        holder.itemView.findViewById<Button>(R.id.btn_start_eval_Add_Eval).setOnClickListener {
            val eval = Eval(
                idEval = currentItem.id, note_seize = "0", note_douze = "0",
                note_relation = "0", note_trajets = "0", note_moteur = "0", note_emotion = "0", note_afl2 = "0", note_afl3 = "0",
                note_sur_vingt = "0", user_id_reference = currentItem.id)
            EvalViewModel(application = Application()).addEval(eval)
            Toast.makeText(context, "Bravo !", Toast.LENGTH_SHORT).show()
            soundClic()
            val action =
                AddEvalFragmentDirections.actionAddEvalFragmentToListMultipleTeam(Gru(currentItem.gru_id_reference, currentItem.nameGroup))
            holder.itemView.findNavController().navigate(action)
            soundClic()
        }
    }



    private fun soundClic() {
        val mediaPlayer: MediaPlayer = MediaPlayer.create(context, R.raw.select_click)
        mediaPlayer.start()
    }

    fun setDataOneUser(user: List<User>){
        this.userList = user
        notifyDataSetChanged()
    }
}

And my code to display the students. It's here who see multiple of the same student.

Here the code:

class MultipleAdapter: RecyclerView.Adapter<MultipleAdapter.MyViewHolder>() {
    var userList = emptyList<User>()

    private lateinit var context: Context

    class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        context = parent.context
        return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.custom_row_multiple, parent, false))
    }

    override fun getItemCount(): Int {
        return userList.size
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val currentItem = userList[position]

        holder.itemView.findViewById<Button>(R.id.btn_start_eval_multiple).isVisible = true
        holder.itemView.findViewById<TextView>(R.id.firstName_txt_multiple).text = currentItem.firstName
        holder.itemView.findViewById<TextView>(R.id.lastName_txt_multiple).text = currentItem.lastName

            holder.itemView.findViewById<Button>(R.id.btn_start_eval_multiple).setOnClickListener {
                val action = MultipleFragmentDirections.actionMultipleFragmentToEvalFragment(User(currentItem.id, currentItem.nameGroup, currentItem.firstName, currentItem.lastName, currentItem.nbTeam, currentItem.gru_id_reference))
                holder.itemView.findNavController().navigate(action)
                soundClic()
            }
    }

    private fun soundClic() {
        val mediaPlayer: MediaPlayer = MediaPlayer.create(context, R.raw.select_click)
        mediaPlayer.start()
    }

    fun setDataMultiple(user: List<User>){
        this.userList = user
        notifyDataSetChanged()
    }
}

Here my query of to display my student on the multiplefragment:

@Query("SELECT * FROM user_table JOIN eval_table WHERE gru_id_reference LIKE :gru_id AND nbTeam LIKE :nbTeam  ORDER BY lastName ASC")
    fun retrieveUserWithNameGroup( gru_id: Int, nbTeam: String): LiveData<List<User>> 

Solution

  • I don't understand where the problem comes from.

    I believe that your issue is that the query SELECT * FROM user_table JOIN eval_table WHERE gru_id_reference LIKE :gru_id AND nbTeam LIKE :nbTeam ORDER BY lastName ASC will result in the cartesian product, that is a row for every combination of user and eval.

    So if user X has 10 evals then there will be 10 rows and hence 10 of that User in the List and so on.