LiveData not updating the UI (using ViewModel and Room Database)

782 views Asked by At

so I'm using LiveData and ViewModel to set the function of getting and insert the data, and I'm using the Room database to save the data.

after I inserting the data to the database my RecyclerView not updating the data.

RecyclerAdapterTransaksi.kt

class RecyclerAdapterTransaksi(var context: Context, private val listener: (Transaksi) -> Unit) :
RecyclerView.Adapter<RecyclerAdapterTransaksi.TransaksiViewHolder>() {

private var listTransaksi = arrayListOf<Transaksi>()

fun setListTransaksi(listTransaksi: ArrayList<Transaksi>) {
    this.listTransaksi = listTransaksi
    notifyDataSetChanged()
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TransaksiViewHolder {
    context = parent.context

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

    return TransaksiViewHolder(binding)
}

override fun onBindViewHolder(holder: TransaksiViewHolder, position: Int) {
    holder.bindItem(listTransaksi[position], listener)
}

override fun getItemCount(): Int = listTransaksi.size

class TransaksiViewHolder(private val binding: ListItemBinding) :
    RecyclerView.ViewHolder(binding.root) {
    @SuppressLint("SimpleDateFormat")
    fun bindItem(transaksi: Transaksi, listener: (Transaksi) -> Unit) {
        binding.textViewNamaTransaksi.text = transaksi.namaTransaksi
        binding.textViewJmlTransaksi.text = transaksi.total.toString()
        binding.textViewTglTransaksi.text = SimpleDateFormat("dd MMM yyyy").format(transaksi.tglTransaksi!!)
        itemView.setOnClickListener {
            listener(transaksi)
        }

    }
}
}

The fragment that shows data from the database using RecyclerView

PemasukkanFragment.kt

class PemasukkanFragment : Fragment() {

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

    private val viewModel: TransaksiViewModel by lazy {
        val dataSource = UwangkuDatabase.getInstance(requireContext()).transactionDao
        val factory = TransaksiViewModelFactory(dataSource)
        ViewModelProvider(this, factory).get(TransaksiViewModel::class.java)
    }


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentPemasukkanBinding.inflate(inflater, container, false)

        binding.fabPemasukkan.setOnClickListener {
            val intent = Intent(requireContext(), TambahDataActivity::class.java)
            intent.putExtra(Constant.TIPE_TRANSAKSI, Constant.PEMASUKKAN)
            intent.putExtra(Constant.TIPE_AKSI, Constant.AKSI_TAMBAH)
            startActivity(intent)
        }
        getDataPemasukkan()
        return binding.root
    }


    private fun getDataPemasukkan() {
        binding.recyclerViewPemasukan.setHasFixedSize(true)
        binding.recyclerViewPemasukan.layoutManager = LinearLayoutManager(requireContext())

        val adapter = RecyclerAdapterTransaksi(requireContext()) {
            //action when item clicked
           showKonfirmasiEdit(it)
        }
        binding.recyclerViewPemasukan.adapter = adapter

        viewModel.getData(Date(), Constant.PEMASUKKAN)
        viewModel.dataTransaksi.observe(viewLifecycleOwner, Observer {
            adapter.setListTransaksi(it as ArrayList<Transaksi>)
        })
    }

    private fun showKonfirmasiEdit(transaksi: Transaksi){
        val intent = Intent(requireContext(),TambahDataActivity::class.java)

        val builder = AlertDialog.Builder(requireContext())
            .setMessage(R.string.edit_message)
            .setPositiveButton(R.string.edit){_,_ ->
                intent.putExtra(Constant.DATA_PEMASUKKAN, transaksi)
                intent.putExtra(Constant.TIPE_TRANSAKSI, Constant.PEMASUKKAN)
                intent.putExtra(Constant.TIPE_AKSI, Constant.AKSI_EDIT)
                startActivity(intent)
            }
            .setNegativeButton(R.string.batal){dialog, _ ->
                dialog.cancel()
            }
        builder.show()
    }

}

The ViewModel class that used LiveData

TransaksiViewModel.kt

class TransaksiViewModel(private val dao: TransaksiDao) : ViewModel() {

    private var _dataTransaksi = MutableLiveData<List<Transaksi>>()
    val dataTransaksi: LiveData<List<Transaksi>>
        get() = _dataTransaksi

    private suspend fun getDataTransaksi(tgl : Date, tipe : String) = withContext(Dispatchers.IO) {
        dao.getData(tgl, tipe)
    }

    fun getData(tgl : Date, tipe : String) {
        viewModelScope.launch {
            _dataTransaksi.postValue(getDataTransaksi(tgl, tipe))
        }
    }

    fun inserDataTransaksi(transaksi: Transaksi) {
        viewModelScope.launch {
            insertTransaksi(transaksi)
        }
    }

    private suspend fun insertTransaksi(transaksi: Transaksi){
        withContext(Dispatchers.IO) {
            dao.insertDataTransaksi(transaksi)
        }
    }

    fun updateDataTransaksi(transaksi: Transaksi){
        viewModelScope.launch {
            updateTransaksi(transaksi)
        }
    }

    private suspend fun updateTransaksi(transaksi: Transaksi){
        withContext(Dispatchers.IO){
            dao.updateDataTransaksi(transaksi)
        }
    }
}

The DAO

TransakiDao.kt

@Dao
interface TransaksiDao {
    @Insert
    fun insertDataTransaksi(transaksi: Transaksi)

    @Query("SELECT * FROM transaksi WHERE datetime(tanggal/1000,'unixepoch','start of month') = datetime(:tgl/1000,'unixepoch','start of month') AND jenis = :tipe ORDER BY id DESC")
    fun getData(tgl : Date, tipe : String): List<Transaksi>

    @Update
    fun updateDataTransaksi(transaksi: Transaksi)
}

The activity for inserting data to database

TambahDataActivity.kt

class TambahDataActivity : AppCompatActivity() {

    private lateinit var binding: ActivityTambahDataBinding

    private val viewModel: TransaksiViewModel by lazy {
        val dataSource = UwangkuDatabase.getInstance(this).transactionDao
        val factory = TransaksiViewModelFactory(dataSource)
        ViewModelProvider(this, factory).get(TransaksiViewModel::class.java)
    }

    @SuppressLint("SimpleDateFormat")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTambahDataBinding.inflate(layoutInflater)
        setContentView(binding.root)
        supportActionBar!!.setDisplayHomeAsUpEnabled(true)

        val getAksi = intent.getStringExtra(Constant.TIPE_AKSI)
        val getJenisTransaksi = intent.getStringExtra(Constant.TIPE_TRANSAKSI)
        if(getJenisTransaksi == Constant.PEMASUKKAN){
            val dataPemasukan = intent.getParcelableExtra<Transaksi>(Constant.DATA_PEMASUKKAN)
            if(dataPemasukan != null){
                getDataFromIntent(dataPemasukan)
            }
        } else if(getJenisTransaksi == Constant.PENGELUARAN){
            val dataPengeluaran = intent.getParcelableExtra<Transaksi>(Constant.DATA_PENGELUARAN)
            if(dataPengeluaran != null){
                getDataFromIntent(dataPengeluaran)
            }
        }
        if(getAksi == Constant.AKSI_EDIT){
            binding.buttonTambah.text = getString(R.string.edit)
            title = getString(R.string.edit_data)
        }
        binding.textViewTanggal.setOnClickListener { showDatePickerDialog() }
        binding.buttonTambah.setOnClickListener {
            if(getAksi == Constant.AKSI_TAMBAH){
                insertData()
            }else if(getAksi == Constant.AKSI_EDIT){
                updateData()
            }
        }
    }

    @SuppressLint("SimpleDateFormat")
    private fun insertData(){
        val namaTransaksi = binding.editTextKeterangan.text
        val jumlahTransaki = binding.editTextJumlah.text
        val getJenisTransaksi = intent.getStringExtra(Constant.TIPE_TRANSAKSI)
        val jenisTransaksi = if (getJenisTransaksi == Constant.PEMASUKKAN) {
            Constant.PEMASUKKAN
        } else {
            Constant.PENGELUARAN
        }
        val tanggalTransaksi =
            SimpleDateFormat("dd-MM-yyyy").parse(binding.textViewTanggal.text.toString())
        val transaksi = Transaksi(
            0,
            namaTransaksi.toString(),
            jenisTransaksi,
            jumlahTransaki.toString().toInt(),
            tanggalTransaksi
        )
        viewModel.inserDataTransaksi(transaksi).apply { finish() }
    }

    @SuppressLint("SimpleDateFormat")
    private fun updateData(){
        val namaTransaksi = binding.editTextKeterangan.text
        val jumlahTransaki = binding.editTextJumlah.text
        val getJenisTransaksi = intent.getStringExtra(Constant.TIPE_TRANSAKSI)
        val jenisTransaksi = if (getJenisTransaksi == Constant.PEMASUKKAN) {
            Constant.PEMASUKKAN
        } else {
            Constant.PENGELUARAN
        }
        val tanggalTransaksi =
            SimpleDateFormat("dd-MM-yyyy").parse(binding.textViewTanggal.text.toString())
        if(getJenisTransaksi == Constant.PEMASUKKAN){
            val dataPemasukan = intent.getParcelableExtra<Transaksi>(Constant.DATA_PEMASUKKAN)!!
            val transaksi = Transaksi(dataPemasukan.id,
                namaTransaksi.toString(),
                jenisTransaksi,
                jumlahTransaki.toString().toInt(),
                tanggalTransaksi
            )
            viewModel.updateDataTransaksi(transaksi)
        } else if(getJenisTransaksi == Constant.PENGELUARAN){
            val dataPengeluaran = intent.getParcelableExtra<Transaksi>(Constant.DATA_PENGELUARAN)!!
            val transaksi = Transaksi(dataPengeluaran.id,
                namaTransaksi.toString(),
                jenisTransaksi,
                jumlahTransaki.toString().toInt(),
                tanggalTransaksi
            )
            viewModel.updateDataTransaksi(transaksi)
        }
    }

    @SuppressLint("SimpleDateFormat")
    private fun getDataFromIntent(data : Transaksi?){
        if(data != null){
            binding.editTextKeterangan.setText(data.namaTransaksi)
            binding.editTextJumlah.setText(data.total.toString())
            binding.textViewTanggal.text = SimpleDateFormat("dd-MM-yyyy").format(data.tglTransaksi!!)
        }

    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> finish()
        }
        return true
    }

    @SuppressLint("SetTextI18n")
    private fun showDatePickerDialog() {
        val calendar = Calendar.getInstance()
        val tahun = calendar.get(Calendar.YEAR)
        val bulan = calendar.get(Calendar.MONTH)
        val hari = calendar.get(Calendar.DAY_OF_MONTH)
        val datePicker = DatePickerDialog(
            this,
            DatePickerDialog.OnDateSetListener { view, _year, monthOfYear, dayOfMonth ->
                binding.textViewTanggal.text = "$dayOfMonth-${monthOfYear + 1}-$_year "
            },
            tahun,
            bulan,
            hari
        )
        datePicker.show()
    }

I don't know what's the problem with this, I get stuck for 2 days.

Thanks in advance.

1

There are 1 answers

0
Trung Đoan On

you need to check your database instance if you are using singleton implementation. I faced same with your issue then I found the bug in my code and my solution is changed singleton from below code`

companion object {
    private var INSTANCE: PokemonDatabase? = null

    fun getDatabase(context: Context): PokemonDatabase {
        return INSTANCE ?: Room.databaseBuilder(
            context.applicationContext,
            PokemonDatabase::class.java,
            "pokemon_db"
        ).build()
    }

`

to below code

companion object {
    @Volatile
    private var INSTANCE: PokemonDatabase? = null
    fun getDatabase(context: Context): PokemonDatabase {
        return INSTANCE ?: synchronized(this) {
            val instance = Room.databaseBuilder(
                context.applicationContext,
                PokemonDatabase::class.java,
                "pokemon_db"
            ).build()
            INSTANCE = instance
            instance//this return instance
        }
    }
}