user2350138
user2350138

Reputation: 525

Room returning Null value while using Live Data but returns Proper value when its not wrapped with Livedata

I am using the following DAO

@Dao
interface GroceryListDao {

@Insert
fun insert(list: GroceryList)

@Update
fun update(list: GroceryList)

@Query("Delete from grocery_list_table")
fun clear()

@Query ("Select * from grocery_list_table")
fun getAllItems(): LiveData<List<GroceryList>>

@Query ("Select * from grocery_list_table where itemId = :item")
fun getItem(item: Long): GroceryList?

@Query ("Select * from grocery_list_table where item_status = :status")
fun getItemsBasedOnStatus(status: Int): List<GroceryList>


}

And my database has 3 columns groceryId(Long - autogenerated), groceryName(String) and groceryStatus(Int - 1/0).

When I am using getItemsBasedOnStatus(status: Int) without using LiveData I am able to retrieve the data. But when it is wrapped with LiveData I am getting null.

The other issue is when I get a list of items from a database without wrapping with LiveData and assigning to MutableLiveData in ViewModel, then the assigned MutableLiveData is displaying null values. I see lot of questions on this but no answer.

Adding code for my viewModel and Fragment

ViewModel

class GroceryListViewModel(
val database: GroceryListDao,
application: Application
) : AndroidViewModel(application) {

private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

var grocerylistItems = database.getAllItems()
var groceryItem = MutableLiveData<GroceryList>()
var groceryitems = MutableLiveData<List<GroceryList>>()

init {
    getAllItemsFromDatabase()
}

fun insertIntoDatabase(item: GroceryList) {
    uiScope.launch {
        insert(item)
    }
}

private suspend fun insert(item: GroceryList) {
    withContext(Dispatchers.IO) {
        database.insert(item)
    }
}

fun updateGrocerylist(itemId: Long, status: Int) {
    uiScope.launch {
        groceryItem.value = getItem(itemId)
        groceryItem.value?.itemStatus = status
        groceryItem.value?.let { update(it) }

    }
}

private suspend fun update(item: GroceryList) {
    withContext(Dispatchers.IO) {
        database.update(item)
    }
}


private suspend fun getItem(itemId: Long): GroceryList? {
    return withContext(Dispatchers.IO) {
        var item = database.getItem(itemId)
        item
    }
}

fun getAllItemsFromDatabase() {
    uiScope.launch {
        getAllItems()
    }
}

private suspend fun getAllItems() {
    withContext(Dispatchers.IO) {
        grocerylistItems = database.getAllItems()


    }
}

fun getPendingItemsFromDatabase(status: Int) {
    uiScope.launch {
        getPendingItems(status)
    }
}

private suspend fun getPendingItems(status: Int) {
    withContext(Dispatchers.IO) {
        val items = database.getItemsBasedOnStatus(status)
        groceryitems.postValue(items)
        Log.i("Grocery List", "Pending Items:" + items.size)

    }
}

fun getDoneItemsFromDatabase(status: Int) {
    uiScope.launch {
        getDoneItems(status)
    }
}

private suspend fun getDoneItems(status: Int) {
    withContext(Dispatchers.IO) {
        val items = database.getItemsBasedOnStatus(status)
        groceryitems.postValue(items)
        Log.i("Grocery List", "Done Items:" + items.size)
    }
}

fun clearAllItemsFromDatabase() {
    uiScope.launch {
        clearItems()
    }
}

private suspend fun clearItems() {
    withContext(Dispatchers.IO) {
        database.clear()
        getAllItemsFromDatabase()
    }
}

override fun onCleared() {
    super.onCleared()
    viewModelJob.cancel()
}

}

Fragment

class GroceryLIstFragment : Fragment() {

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

    val application = requireNotNull(this.activity).application
    val dataSource = GroceryDatabase.getInstance(application)?.groceryListDatabaseDao

    val viewModelFactory = dataSource?.let { GroceryListViewModelFactory(it, application) }

    val viewModel = viewModelFactory?.let {
        ViewModelProvider(
            this,
            it
        ).get(GroceryListViewModel::class.java)
    }

    viewModel?.grocerylistItems?.observe(this , Observer {
        binding.grocerylistView.removeAllViews() // is it correct ?
        for (item in it){
            Log.i("Grocery List","Grocery Id=" + item.itemId+" ,Grocery Name=" + item.itemName +", Grocery status="+item.itemStatus)
            addItemToScrollbar(item, binding, viewModel)
        }
    })


    binding.addGrocery.setOnClickListener {

        val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(view?.windowToken, 0)

        val item = binding.groceryitemField.text.toString()

        if (!item.isNullOrBlank()) {
            val newItem = GroceryList(itemName = item)
            viewModel?.insertIntoDatabase(newItem)
            if (viewModel != null) {
                addItemToScrollbar(newItem, binding,viewModel)
            }
            binding.groceryitemField.text.clear()
        }
    }

    binding.doneCheckbox.setOnClickListener {
        if (binding.doneCheckbox.isChecked)
            viewModel?.getDoneItemsFromDatabase(1)
        else
            viewModel?.getAllItemsFromDatabase()

    }

    binding.pendingCheckbox.setOnClickListener {
        if (binding.pendingCheckbox.isChecked) {
            viewModel?.getPendingItemsFromDatabase(0)
        }
        else
            viewModel?.getAllItemsFromDatabase()

    }

    binding.clearGrocery.setOnClickListener {
        viewModel?.clearAllItemsFromDatabase()
        binding.grocerylistView.removeAllViews()
    }

    return binding.root
}

private fun addItemToScrollbar(
    item: GroceryList,
    binding: FragmentGroceryLIstBinding,
    viewModel: GroceryListViewModel
) {
    val itemBox = AppCompatCheckBox(context)
    itemBox.text = item.itemName
    itemBox.isChecked = item.itemStatus == 1
    binding.grocerylistView.addView(itemBox)
    itemBox.setOnClickListener {
        val itemstatus: Int = if (itemBox.isChecked)
            1
        else {
            0
        }
        viewModel?.updateGrocerylist(item.itemId,itemstatus)

    }

}

}

enter image description here

Any help would be appreciated.

Upvotes: 0

Views: 1728

Answers (1)

P. Leibner
P. Leibner

Reputation: 473

This most likely the same issue as here (read the linked answer). Due to way asynchronous way LiveData is working, it will return null when you call it. LiveData is meant to be used in conjunction with Observers, that will be triggered once changes to observed subject occur.

An Observer can look like this

    database.getItemsBasedOnStatus(status).observe(viewLifecycleOwner, Observer  { groceryList->
    // Do cool grocery stuff here

    })

If you want to retrieve your data inside your ViewModel you do not have a viewLifecycleOwner, you can then use "observeForever()", but then you have to remove the Observer explicitly, see this answer.

Same issue and answer also in this post

Upvotes: 1

Related Questions