Room 在使用实时数据时返回 Null 值但 returns 未使用 Livedata 包装时的正确值
Room returning Null value while using Live Data but returns Proper value when its not wrapped with Livedata
我正在使用以下 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>
}
我的数据库有 3 列 groceryId(Long - autogenerated)、groceryName(String) 和 groceryStatus(Int - 1/0)。
当我在不使用 LiveData 的情况下使用 getItemsBasedOnStatus(status: Int) 时,我能够检索数据。但是当它被 LiveData 包裹时,我得到了 null。
另一个问题是当我从数据库中获取项目列表时没有用 LiveData 包装并分配给 ViewModel 中的 MutableLiveData,然后分配的 MutableLiveData 显示空值。我看到很多关于这个的问题,但没有答案。
为我的 viewModel 和 Fragment 添加代码
视图模型
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()
}
}
片段
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)
}
}
}
如有任何帮助,我们将不胜感激。
这很可能是与 相同的问题(阅读链接的答案)。由于 LiveData 的异步工作方式,当您调用它时它将 return null。 LiveData 旨在与观察者结合使用,一旦观察对象发生变化就会被触发。
Observer 可以像这样
database.getItemsBasedOnStatus(status).observe(viewLifecycleOwner, Observer { groceryList->
// Do cool grocery stuff here
})
如果你想在没有 viewLifecycleOwner 的 ViewModel 中检索数据,则可以使用 "observeForever()",但你必须明确删除 Observer,请参阅此 。
同样的问题和答案也在这个
我正在使用以下 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>
}
我的数据库有 3 列 groceryId(Long - autogenerated)、groceryName(String) 和 groceryStatus(Int - 1/0)。
当我在不使用 LiveData 的情况下使用 getItemsBasedOnStatus(status: Int) 时,我能够检索数据。但是当它被 LiveData 包裹时,我得到了 null。
另一个问题是当我从数据库中获取项目列表时没有用 LiveData 包装并分配给 ViewModel 中的 MutableLiveData,然后分配的 MutableLiveData 显示空值。我看到很多关于这个的问题,但没有答案。
为我的 viewModel 和 Fragment 添加代码
视图模型
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()
}
}
片段
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)
}
}
}
如有任何帮助,我们将不胜感激。
这很可能是与
Observer 可以像这样
database.getItemsBasedOnStatus(status).observe(viewLifecycleOwner, Observer { groceryList->
// Do cool grocery stuff here
})
如果你想在没有 viewLifecycleOwner 的 ViewModel 中检索数据,则可以使用 "observeForever()",但你必须明确删除 Observer,请参阅此
同样的问题和答案也在这个