user19000614
user19000614

Reputation: 21

I’ve been using LiveData to load items, ensuring that any changes in the dB trigger updates, but it's not working as expected

I have created the Simple Project for Item to be Listed in one fragment and to do CRUD operation in another Fragment . I have used Live data ,Viewmodel and Sql lite instead to Room.

According to the Definition of Live Data :

"Instead of updating the UI every time the app data changes, your observer can update the UI every time there's a change."

But its not working

I have tried to just check the working live data by implementing delay in DB operations (For Example : When user enters the item details and tries to click the save button , user just returns to Item Listing Fragment and after delay of 3 Sec the item will be updated in the DB. After the updation the listview is not updated.)

Code :

Item Listing Fragment :

class ItemFragment : Fragment()

{

private lateinit var adapter : CustomCursorAdapter

private lateinit var databaseHelper : DatabaseHelper

private lateinit var binding : FragmentItemListBinding

private val itemViewModel : ItemViewModel by viewModels()

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

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

    updatedListView()

    setClickListeners()

}

private fun setUpToolBar()
{
    binding.toolbar.setTitle("Item Listing")
    binding.toolbar.setNavigationIcon(R.drawable.baseline_arrow_back_24)
    binding.toolbar.setNavigationOnClickListener {
        activity?.finish()
    }
}

private fun onListItemClick(position: Int)
{
    val cursor = adapter.cursor

    if(cursor.moveToPosition(position))
    {
        val item = Item(cursor)

        val addOrEditItemFragment = AddOrEditItemFragment().apply {

            arguments = Bundle().apply {

                putInt("itemId", item.itemId)
            }
        }

        val fragmentContainer = if(configuration.isDeviceInLandScapeMode(resources)) R.id.fragmentB else R.id.fragmentA

        navigateToAddOrEditFragment(fragmentContainer ,addOrEditItemFragment)
    }
}

private fun navigateToAddOrEditFragment(fragmentContainer : Int, addOrEditItemFragment:Fragment)
{
    parentFragmentManager.beginTransaction()
        .replace(fragmentContainer, addOrEditItemFragment,"addOrEditItemFragment")
        .addToBackStack("AddOrEditItemFragment")
        .commit()
}

private fun setClickListeners()
{

    binding.listView.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->

        onListItemClick(position)
    }

    if (configuration.isDeviceInLandScapeMode(resources))
    {
        binding.add.visibility = View.GONE
    }
    else
    {
        binding.add.setOnClickListener()
        {
            navigateToAddOrEditFragment(R.id.fragmentA,AddOrEditItemFragment())
        }
    }
}

private fun updatedListView()
{
    adapter = CustomCursorAdapter(requireContext(), null)

    binding.listView.adapter = adapter

    itemViewModel.itemsCursorLiveData.observe(viewLifecycleOwner) { cursor ->

        println("Cursor  :   "+ cursor)

        adapter.changeCursor(cursor)
    }

    itemViewModel.fetchItems()
    }

}

Item View Model :

class ItemViewModel(application: Application) : AndroidViewModel(application)
{
private val databaseHelper = DatabaseHelper.getInstance(application)

private val _itemsCursorLiveData = MutableLiveData<Cursor>()

val itemsCursorLiveData: LiveData<Cursor> get() = _itemsCursorLiveData


fun fetchItems()
{
    viewModelScope.launch(Dispatchers.IO)
    {
        val cursor = databaseHelper.getItems()

        withContext(Dispatchers.Main)
        {
            _itemsCursorLiveData.postValue(cursor)
        }
    }
  }
}

Add or Edit Item Fragment :

class AddOrEditItemFragment : Fragment()
{
private lateinit var newItem : Item
private lateinit var imageIntentListener : ActivityResultLauncher<Intent>
private val viewModel : ItemDetailsViewModel by viewModels()
private var binding : FragmentAddoredititemBinding ?= null

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

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

    setupItemDetails()

    setupObservers()

    handleDeviceBackButton()

    setOnButtonClickListeners()

    fragmentResultListeners()
}


private fun setupItemDetails()
{
    val itemId = arguments?.getInt("itemId") ?: -1
    if (itemId != -1)
    {
        setUpToolBar(true)
        viewModel.loadItem(itemId)

    }
    else {
        setUpToolBar(false)
        newItem = Item()
    }
}

private fun setupObservers()
{
    viewModel.item.observe(viewLifecycleOwner) { item ->

        newItem = item

        binding?.editName?.setText(newItem.name)
        binding?.editPrice?.setText(newItem.price)
        binding?.editSku?.setText(newItem.sku)
        binding?.editdesc?.setText(newItem.desc)

        if (newItem.imageResource != Uri.EMPTY)
        {
            binding?.imageView?.setImageURI(newItem.imageResource)
        }
    }
    
}

private fun handleDeviceBackButton()
{
    val backPressedCallback = object : OnBackPressedCallback(true)
    {
        override fun handleOnBackPressed()
        {
            if((configuration.isDeviceInLandScapeMode(resources) && newItem.itemId != -1) || !configuration.isDeviceInLandScapeMode(resources))
            {
                showAlertDialogBox()
            }
            else
            {
                activity?.finish()
            }
        }
    }
    activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, backPressedCallback)
}

private fun setNavigationIcon()
{
    binding?.toolbar?.setNavigationIcon(R.drawable.baseline_arrow_back_24)
    binding?.toolbar?.setNavigationOnClickListener {

        showAlertDialogBox()
    }
}

private fun setUpToolBar(isEdit : Boolean)
{
    if(isEdit)
    {
        binding?.toolbar?.title = "Edit Item"
        binding?.delete?.visibility = View.VISIBLE
        setNavigationIcon()
    }
    else
    {
        binding?.toolbar?.title = "Add Item"
        if(!configuration.isDeviceInLandScapeMode(resources)) setNavigationIcon()
    }
}

private fun showAlertDialogBox()
{
    val builder = AlertDialog.Builder(context)
    builder.setMessage("Do you want to exit ?")
    builder.setCancelable(false)
    builder.setPositiveButton("Yes") { _, _ -> parentFragmentManager.popBackStack("AddOrEditItemFragment", FragmentManager.POP_BACK_STACK_INCLUSIVE) }
    builder.setNegativeButton("No") { dialog, _ -> dialog.cancel() }
    val alertDialog = builder.create()
    alertDialog.show()
}

private fun saveItem()
{
    if(fieldEmptyOrNot())
    {
        newItem.apply {
            name = binding?.editName?.text.toString()
            price = binding?.editPrice?.text.toString()
            sku = binding?.editSku?.text.toString()
            desc = binding?.editdesc?.text.toString()
        }

        println("BEFORE SAVING :   "+newItem)

        viewModel.saveItem(newItem)

        parentFragmentManager.popBackStack()

    }
}

private fun setOnButtonClickListeners()
{
    binding?.imageView?.setOnClickListener{

        chooseImageGallery()
    }

    binding?.save?.setOnClickListener {

        saveItem()
    }

    binding?.delete?.setOnClickListener{

        deleteItem()
    }
}

private fun fieldEmptyOrNot():Boolean
{
    if (binding?.editName?.text.toString().isBlank())
    {
        binding?.editName?.error = "Please enter a name"
        return false
    }

    if (binding?.editPrice?.text.toString().isBlank())
    {
        binding?.editPrice?.error = "Please enter a price"
        return false
    }

    if(binding?.editSku?.text.toString().isBlank())
    {
        binding?.editSku?.error = "Please enter a SKU"
        return false
    }

    return true
}

private fun chooseImageGallery()
{
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {

        addCategory(Intent.CATEGORY_OPENABLE)
        type = "image/*"
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
    imageIntentListener.launch(intent)
}

private fun fragmentResultListeners()
{
    imageIntentListener = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == RESULT_OK)
        {
            newItem.imageResource = result.data?.data as Uri
            activity?.contentResolver?.takePersistableUriPermission(newItem.imageResource, Intent.FLAG_GRANT_READ_URI_PERMISSION)
            binding?.imageView?.setImageURI(newItem.imageResource)
        }
    }
}

private fun deleteItem()
{
    newItem.let { viewModel.deleteItem(it.itemId) }
    
    parentFragmentManager.popBackStack()
}

}

Item View Details View Model :

ItemDetailsViewModel(application : Application) : AndroidViewModel(application)
{
private val _item = MutableLiveData<Item>()
val item: LiveData<Item> get() = _item


private val databaseHelper = DatabaseHelper.getInstance(application)

fun loadItem(itemId: Int)
{
    viewModelScope.launch(Dispatchers.IO)
    {
        val cursor = databaseHelper.getItemByID(itemId)

        if (cursor.moveToFirst())
        {
            val loadedItem = Item(cursor)

            withContext(Dispatchers.Main) {

                _item.value = loadedItem
            }
        }
    }
}

fun saveItem(item: Item)
{
    CoroutineScope(Dispatchers.IO).launch()
    {

        println("Thread  ${Thread.currentThread()}")

        delay(3000L)

        println("Running after delay")
        if (item.itemId != -1)
        {
            databaseHelper.updateItem(
                item.itemId, item.name, item.price, item.sku,
                item.imageResource, item.desc
            )
        }
        else
        {
            databaseHelper.addItems(
                item.name, item.price, item.sku,
                item.imageResource, item.desc
            )
        }
        
    }
}

override fun onCleared() {
    super.onCleared()
    println("ItemDetailViewModel ViewModel destroyed")
}

fun deleteItem(itemId: Int)
{
    viewModelScope.launch(Dispatchers.IO) {

        databaseHelper.deleteItemByID(itemId)
    }
}

}

Kindly go through the code and Help me to resolve this

Upvotes: 0

Views: 25

Answers (0)

Related Questions