'ROOM: how to display one to many relation data into recyclerview using lifecycle

I've tried to display the join data from two entity into the recyclerview. However, when I add new data that has the same idBarang (FK) as the old data that has the same idBarang, recyclerview display the same data row.

Screenshot recylerview, Shows rows that contain the same data

This is the code snippet:

ENTITY

@Entity(
    foreignKeys = [
        ForeignKey(entity = Item::class, parentColumns = ["id"], childColumns = ["id_barang"])
    ],
    indices = [Index("id_barang")]
)
data class Purchaseorder(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    @ColumnInfo(name = "no_po")
    val noPO: String,
    @ColumnInfo(name = "id_barang")
    val idBarang: Int,
    @ColumnInfo(name = "tanggal_po")
    val tanggalPO: String,
    @ColumnInfo(name = "jumlah_po")
    val jumlahPO: Int,
    @ColumnInfo(name = "harga_po")
    val hargaPO: Double
)

@Entity
data class Item(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    @ColumnInfo(name = "kode_barang")
    val kodeBarang: String,
    @ColumnInfo(name = "nama")
    val namaBarang: String,
    @ColumnInfo(name = "min_stok")
    val minStok: Int,
    val stok: Int,
    val unit: String
)

data class PurchaseorderAndItem(
    @Embedded val item: Item,
    @Relation(
        parentColumn = "id",
        entityColumn = "id_barang"
    )
    val purchaseorder : List<Purchaseorder>,
//    val purchaseorder : Purchaseorder,
)
/**
 * Returns the passed in price in currency format.
 */
fun Purchaseorder.getFormattedPrice(): String =
    NumberFormat.getCurrencyInstance().format(hargaPO)

fun Purchaseorder.getUnitPrice(): String =
    NumberFormat.getCurrencyInstance().format(hargaPO/jumlahPO)

fun Purchaseorder.getLongDate() : String {
    val month = arrayOf("Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","November","Desember")
    val dateTemp = tanggalPO.split("/")


    return dateTemp[0] + " " +month[dateTemp[1].toInt()-1] + " " +dateTemp[2]
}

DAO

@Dao
interface PurchaseorderDao {
    @Transaction
    @Query("SELECT * from purchaseorder as po INNER JOIN item ON item.id = po.id_barang WHERE po.id = :id")
    fun retrievePurchaseorder(id: Int): Flow<PurchaseorderAndItem>

    @Transaction
    @Query("SELECT * FROM purchaseorder as po INNER JOIN item ON item.id = po.id_barang ORDER BY po.id DESC")
    fun getPurchaseorders(): Flow<List<PurchaseorderAndItem>>
}

VIEWMODEL

class PurchaseorderViewModel(private val purchaseorderDao: PurchaseorderDao) : ViewModel() {

    var allPurchaseorders: LiveData<List<PurchaseorderAndItem>> = purchaseorderDao.getPurchaseorders().asLiveData()

    fun retrievePurchaseorder(id: Int): LiveData<PurchaseorderAndItem> {
        return purchaseorderDao.retrievePurchaseorder(id).asLiveData()
    }

    ...

class PurchaseorderViewModelFactory(
    private val purchaseorderDao: PurchaseorderDao
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(PurchaseorderViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return PurchaseorderViewModel(purchaseorderDao) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

LIST FRAGMENT

class PurchaseorderListFragment : Fragment() {
    private val viewModel: PurchaseorderViewModel by activityViewModels {
        PurchaseorderViewModelFactory(
            (activity?.application as InventoryApplication).database.purchaseorderDao(),
        )
    }

    private var _binding: FragmentPurchaseorderListBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        _binding = FragmentPurchaseorderListBinding.inflate(inflater, container, false)
        return binding.root
    }



    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val adapter = PurchaseorderListAdapter {
            val action =
                PurchaseorderListFragmentDirections.actionPurchaseorderListFragmentToPurchaseorderDetailFragment(it.purchaseorder[0].id)
            this.findNavController().navigate(action)
        }

        binding.recyclerViewPurchaseorder.layoutManager = object : LinearLayoutManager(this.context) {
            override fun checkLayoutParams(lp: RecyclerView.LayoutParams): Boolean {
                lp.width = width
                return true
            }


        }
        binding.recyclerViewPurchaseorder.adapter = adapter
        viewModel.allPurchaseorders.observe(this.viewLifecycleOwner) { purchaseorder ->
            purchaseorder.let {
                adapter.submitList(it)
            }
        }


        binding.floatingActionButton.setOnClickListener {
            val action = PurchaseorderListFragmentDirections.actionPurchaseorderListFragmentToAddPurchaseorderFragment(
                getString(R.string.add_fragment_title)
            )
            this.findNavController().navigate(action)
        }
    }
}

LIST ADAPTER

class PurchaseorderListAdapter(private val onPurchaseorderClicked: (PurchaseorderAndItem) -> Unit) :
    ListAdapter<PurchaseorderAndItem, PurchaseorderListAdapter.PurchaseorderViewHolder>(DiffCallback) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PurchaseorderViewHolder {
        return PurchaseorderViewHolder(
            PurchaseorderListItemBinding.inflate(
                LayoutInflater.from(
                    parent.context
                )
            )
        )
    }

    override fun onBindViewHolder(holder: PurchaseorderViewHolder, position: Int) {
        val current = getItem(position)
        holder.itemView.setOnClickListener {
            onPurchaseorderClicked(current)
        }
        holder.bind(current)
    }

    class PurchaseorderViewHolder(private var binding: PurchaseorderListItemBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(purchaseorder: PurchaseorderAndItem) {

            val purchaseorders = purchaseorder.purchaseorder[0]
            val item = purchaseorder.item
            binding.rvNamaBarangNNoPO.text = binding.rvNamaBarangNNoPO.context.getString(R.string.kode_cate,item.namaBarang,purchaseorders.noPO)
            binding.rvHargaPO.text = purchaseorders.getFormattedPrice()
            binding.rvJumlahPO.text = purchaseorders.jumlahPO.toString()
            binding.rvTanggalPO.text = purchaseorders.tanggalPO

//            Log.i("ininya: ", purchaseorder)
        }
    }

    companion object {
        private val DiffCallback = object : DiffUtil.ItemCallback<PurchaseorderAndItem>() {

            override fun areItemsTheSame(
                oldItem: PurchaseorderAndItem,
                newItem: PurchaseorderAndItem,
            ): Boolean {
                return oldItem.purchaseorder[0].id == newItem.purchaseorder[0].id
            }

            override fun areContentsTheSame(
                oldItem: PurchaseorderAndItem,
                newItem: PurchaseorderAndItem,
            ): Boolean {
                return oldItem.purchaseorder[0].noPO == newItem.purchaseorder[0].noPO
            }

        }
    }
}

XML RECYCLER ROW

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:weightSum="5.7">
    <TextView
        android:id="@+id/rv_namaBarang_N_noPO"
        style="@style/Widget.Inventory.ListItemTextView"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_weight="2"
        android:fontFamily="sans-serif"
        tools:text="Terigu (P001)" />

    <TextView
        android:id="@+id/rv_tanggalPO"
        style="@style/Widget.Inventory.ListItemTextView"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_weight="1.2"
        android:layout_marginHorizontal="@dimen/margin_between_elements"
        android:fontFamily="sans-serif-medium"
        android:textAlignment="textStart"
        tools:text="21/12/2022" />

    <TextView
        android:id="@+id/rv_jumlahPO"
        style="@style/Widget.Inventory.ListItemTextView"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_weight="1"
        android:fontFamily="sans-serif-medium"
        android:textAlignment="textEnd"
        tools:text="100Kg" />
    <TextView
        android:id="@+id/rv_hargaPO"
        style="@style/Widget.Inventory.ListItemTextView"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_weight="1.5"
        android:fontFamily="sans-serif-medium"
        android:textAlignment="textEnd"
        tools:text="110.000.000" />
</LinearLayout>


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source