Null Pointer Exception on LiveData using Room database [duplicate] - android

This question already has answers here:
java.lang.IllegalArgumentException : Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull
(7 answers)
Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter convertView
(4 answers)
java.lang.IllegalArgumentException: Parameter specified as non-null is null for Kotlin and WebView
(3 answers)
Kotlin - IllegalArgumentException in overridden method
(1 answer)
Closed 2 years ago.
I have an app that fetchs data from API using retrofit with coroutines. (Deferred)
I´m trying to implement a Room cache database for this, but I´ve just tried to use LiveData to fetch the data from Repository (not success). Tried to .alowMainThreadQueries (not success).
The message is:
Attempt to invoke...... on a null object reference.
The main problem is: When i launch the App again, it fetches the data from the database and displays it correctly.
What am i doing wrong?
This is my DAO
#Dao
interface VarietiesDao{
#Query("select * from pokevariationsentity where id = :id")
fun getVariety(id: Int): LiveData<PokeVariationsEntity>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(pokes: PokeVariationsEntity) // will pass the pokes
}
private lateinit var INSTANCE : PokeVarietiesDatabase
fun getVarietiesDatabase(context: Context): PokeVarietiesDatabase {
synchronized(PokeVarietiesDatabase::class.java){
if(!::INSTANCE.isInitialized){
INSTANCE = Room.databaseBuilder(context.applicationContext,
PokeVarietiesDatabase::class.java,
"odaps2l.db").build() // name
}
}
return INSTANCE
}
Repo:
class VarietiesRepositoryImpl(private val database: PokeVarietiesDatabase, private val id: String) {
val varieties: LiveData<PokeVariety>
get() = Transformations.map(database.varietiesDao.getVariety(Integer.parseInt(id))){
it.asDomainModelFromVariations()
}
suspend fun refreshVarieties(id: String) {
withContext(Dispatchers.IO) {
val pokeList = PokeApi.retrofitService.getVariations(Integer.parseInt(id)).await()
database.varietiesDao.insertAll(pokeList.asDatabaseModel())
}
}
}
Actually i thought that the problem was the TypeConverter, but once it runs okay on the second time, that´s i realized that is nothing about it.
UPDATE
I just alowed .aloMainThreadQueries
and tried to do it as Sync mode. returning an Object instead of LiveData.
Surrounded the message Parameter specified as non-null is null: method
`kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter $this$asDomainModelFromVariations`
At the first time I click on the item, and the second time it Loads perfectly. The Same happens with livedata.

Related

What will happen if the record searched by ID doesn't exist when I use Room?

The Code A is from the official sample project.
The fun getTopicEntity will return a flow based the parameter topicId, I wonder whether the app will crash if the record based input topicId doesn't exist.
In my mind, fun getTopicEntity(topicId: String): Flow<TopicEntity> requires to return Flow<TopicEntity>, Flow<TopicEntity> will not be generated if the record doesn't exist, so the app will crash. Is it right?
Code A
#Dao
interface TopicDao {
#Query(
value = """
SELECT * FROM topics
WHERE id = :topicId
"""
)
fun getTopicEntity(topicId: String): Flow<TopicEntity>
...
}
class OfflineFirstTopicsRepository #Inject constructor(
private val topicDao: TopicDao,
private val network: NiaNetworkDataSource,
) : TopicsRepository {
override fun getTopic(id: String): Flow<Topic> =
topicDao.getTopicEntity(id).map { it.asExternalModel() }
...
}
If Room can't find the queried id (TopicEntry doesn't exist), than it throws a NullPointerException. From the official docs:
Keep nullability in mind when choosing a return type, as it affects
how the query method handles empty tables:
When the return type is Flow, querying an empty table throws a null
pointer exception.
When the return type is Flow<T?>, querying an empty table emits a null
value.
When the return type is Flow<List>, querying an empty table emits
an empty list.
So if you think it is possible that it returns null, you should make the return type nullable fun getTopicEntity(topicId: String): Flow<TopicEntity?> and make a null check or use something like topicDao.getTopicEntity(id).filterNotNull().map { it.asExternalModel() }

Android Room Query returning null

I am trying to get the values from a DB via Room but it always returns null.
It should retrieve the data from DB BalancesCat.
Any help? Thanks!
This is the DAO
#Query("SELECT * FROM BalancesCat")
suspend fun getAllBalances(): List<BalancesCat>
Repository
suspend fun getAllBalancesCat(): List<BalancesCat>? {
var balancesCat: List<BalancesCat>? = null
withContext(Dispatchers.IO){
balancesCat = balancesCatDao.getAllBalances()
}
return balancesCat
}
ViewModel
fun getAllBalancesCat(): List<BalancesCat>? {
var balancesCat: List<BalancesCat>? = null
viewModelScope.launch {
balancesCat = repository.getAllBalancesCat()
}
return balancesCat
}
and the Fragment where I want to retrieve the data
balancesCatViewModel = ViewModelProvider(requireActivity(),
BalancesCatViewModelFactory(requireActivity().application)).
get(BalancesCatViewModel::class.java)
allBalancesCat = balancesCatViewModel.getAllBalancesCat()
var allBalancesCatNew: BalancesCat
val currentDate1 = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
val dateCurrent1 = Date.valueOf(currentDate1)
allBalancesCat?.forEach {
if(it.date != dateCurrent1){
it.date = dateCurrent1
allBalancesCatNew = it
balancesCatViewModel.update(allBalancesCatNew)
}
}
This isn't your problem, but I have to mention, your repository's getAllBalancesCat() function is needlessly complicated and doesn't need to return a nullable. Since balancesCatDao.getAllBalances() is a suspend function, it is pointless to wrap it in withContext(). You never need to specify a context to call a suspend function (unless the suspend function was incorrectly designed and has blocking code in it). It can be simplified to:
suspend fun getAllBalancesCat(): List<BalancesCat> = balancesCatDao.getAllBalances()
Your ViewModel function is incorrect and is guaranteed to always return null. It creates the variable balancesCat with initial value of null, launches a coroutine, and then returns the null balancesCat before the coroutine has even started. Coroutines on the ViewModel scope are added to the main thread looper's queue, but that puts them after the code that is currently running in the main thread, like the rest of this function.
The correct way for this ViewModel function to work is to also be a suspend function that returns a non-nullable List:
suspend fun getAllBalancesCat(): List<BalancesCat> = repository.getAllBalances()
And in your Fragment, launch a coroutine from the lifecycleScope to do all this work that partially involves calling suspend function(s).
I can't comment very much on the fragment code because it's not shown in context, but I see some possible code smells. Properties that should probably just be local vals in the function. The Fragment shouldn't need to get values from the ViewModel and then store them in properties, and if it does, then the Fragment's code gets more complicated because it has to check if the local property holds the up-to-date value or not, instead of just getting it from the source (the ViewModel).

Android: await() seems not to work using Room database

I am working on an app to practise some calculations which saves each given answers and data about practise sessions into a Room database for tracking progress. There is a table that contains the answers and there is a table which contains the sessions, and each row in answer table needs to contain the id of the session in which the answer was given
I am using a Room database thus using coroutines when writing to database.
When the user clicks a button the answer is evaluated and saved, and also the session data is updated.
(Such as number of questions answered and the average score.)
To achieve this, I need to have the Id of the freshly created session data. So what I am trying is to call a method in the init{} block of the ViewModel which uses a Defferred and call await() to wait for the session data to be inserted and then get the last entry from the database and update the instance of SessionData that the view model holds and only when it is all done I enable the button thus we will not try to save any data before we know the current session id.
To test this out, I am using the Log.d() method to print out the current session id.
The problem is that I don't always get the right values. Sometimes I get the same id as previous session was, sometimes I get the correct one (so Logcat in Android Studio looks like: 33,33,35,36,38,38,40,41,42,...etc). However if I get all data from the database and check it out, all the ids are in the database, in correct order, no values are skipped.
For me it seems that await() doesn't actually make the app to wait, it seems to me that the reading of the database sometimes happenes before the writing is complete.
But I have no idea why.
In the ViewModel class:
SimpleConversionFragmentViewModel(
val conversionProperties: ConversionProperties,
val databaseDao: ConversionTaskDatabaseDao,
application: Application) : AndroidViewModel(application){
private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private lateinit var sessionData : SessionData
...
init{
startNewSession()
}
...
/**
* This method starts and gets the current session
*/
private fun startNewSession() {
uiScope.launch {
/** Initialize session data*/
sessionData = SessionData()
sessionData.taskCategory = conversionProperties.taskCategory
sessionData.taskType = conversionProperties.taskType
/**
* First insert the new session wait for it to be inserted and get the session inserted
* because we need it's ID
**/
val createNewSession = async { saveSessionDataSuspend() }
val getCurrentSessionData = async { getCurrentSessionSuspend() }
var new = createNewSession.await()
sessionData = getCurrentSessionData.await() ?: SessionData()
_isButtonEnabled.value = true //Only let user click when session id is received!!
Log.d("Coroutine", "${sessionData.sessionId}")
}
}
/**
* The suspend function to get the current session
*/
private suspend fun getCurrentSessionSuspend() : SessionData? {
return withContext(Dispatchers.IO){
var data = databaseDao.getLastSession()
data
}
}
/**
* The suspend function for saving session data
*/
private suspend fun saveSessionDataSuspend() : Boolean{
return withContext(Dispatchers.IO) {
databaseDao.insertSession(sessionData)
true
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
And here is some details from the ConversionTaskDatabaseDao class:
#Dao
interface ConversionTaskDatabaseDao {
#Insert(entity = SessionData::class)
fun insertSession(session: SessionData)
#Query("SELECT * FROM session_data_table ORDER BY session_id DESC LIMIT 1")
fun getLastSession() : SessionData?
...
}
Has anyone got any idea how to solve this?
My first attempt was actually to save the session data only once in the onCleared() method of the ViewModel, but because I have to call the viewModelJob.cancel() method to prevent memory leaks, the job is cancelled before saving is done. But I think it would be a more efficient way if I could save the data here only once.
Or is there a better way to achive what I am trying to do?
Thanks in advance for any help,
Best regards: Agoston
My thought is since you need to wait one suspend method for another and they are already inside coroutine (initiated with launch-builder), you don't need await and you can simplify this:
val createNewSession = async { saveSessionDataSuspend() }
val getCurrentSessionData = async { getCurrentSessionSuspend() }
var new = createNewSession.await()
sessionData = getCurrentSessionData.await() ?: SessionData()
to that:
var new = saveSessionDataSuspend()
sessionData = getCurrentSessionSuspend()
On the contrary when you use await you have no guarantee what method would be first

One-shot request single result in Room Android

I am practicing my android skills (beginner) by coding a grocery list app. I have two tables in my db, a shopping_item table (The items I want to buy) and a reference_item table (The items I know the category and the unit price). Each time I add a shopping item, there is an refId field referencing to the reference item id corresponding. It is a default value to a default reference item if the shopping item is not referenced yet.
I use a MVVM model. I then have a DAO, a repository, a viewModel and my fragments that display data.
When I add a new shopping item, I want to know if there is a corresponding reference item. I want to do the following Query:
#Query(value = "SELECT refId FROM reference_items WHERE reference_item_name = :refName")
suspend fun getRefItem(refName : String) : Int
It returns the id of the reference item corresponding as an Int or is null if it is not referenced yet. In my repository, I have a function like that:
suspend fun getRefItem(refName : String) = db.getShoppingDao().getRefItem(refName)
For now, I think I am doing alright. No mistake in sight I guess.
The problem begin when I try to implement my viewModel. What should I do? What about my fragment?
I have a addNewItem(name: String, amount: Int) function in my fragment to add the new item. I can find the reference item corresponding with the name provided.
I tried multiple things, using LiveData, suspend functions, mutableLiveData/LiveData, but I am getting lost right now. Every tutorials or examples use LiveData or Query all data from the db. I just want one Integer, one Time, no need of LiveData I think.
here is the complete solution. Hope this is useful for you.
DAO
#Query(value = "SELECT refId FROM reference_items WHERE reference_item_name = :refName")
suspend fun getRefItem(refName : String) : Int
Repository
// Specify return datatype as Int
suspend fun getRefItem(refName : String): Int = db.getShoppingDao().getRefItem(refName)
ViewModel
fun getRefItem(name: String): LiveData<Int> {
val result : MutableLiveData<Int>() <-- setup livedata to return as value
viewModelScope.lanuch {
result.postValue(repository.getRefItem(name))
}
return result <-- return livedata
}
Fragment
fun addNewItem(name: String, amount: Int) {
// setup viewModel observer
viewModel.getRefItem(name).observer { viewLifecycleOwner, { value ->
// GET YOUR INT VALUE HERE
Log.i("VALUE", value)
}
}
}

Realm db DynamicRealmObject not valid during migration

I've been working with Realm for well over a year so I'm not new to the whole migration flow but this has me scratching my head for several days:
During a data migration to a new schema version I need to create some objects and insert them to the DB, and later connect them to another type of objects.
First I create a map of the dynamic objects so I could connect them to the second type later:
val generatedStoreVisitTypes = mutableMapOf<String, DynamicRealmObject>()
Then I create and use the dynamicObjects:
fun migrateToVersion19(realm: DynamicRealm) {
// an extension method I created which adds the field if it doesn’t exist already, impl at the bottom
realm.schema.getOrCreate<RealmMetadata>()
// an extension method I created which adds the field if it doesn’t exist already, impl at the bottom
.safeAddRealmListField(RealmMetadata::storeVisitTypes, realm.schema)
.transform { metadata ->
// I use the string name of the property here and not reflection since this field is deleted during this migration
val currentStoreTaskList = metadata.getList("storeTasks")
currentStoreTaskList.forEach { storeTasks ->
// create an instance here and initialise it
val visitTypeTasks = realm.createObject(MetaVisitTypeTasks::class.java.simpleName)
visitTypeTasks[MetaVisitTypeTasks::visitTypeId.name] = "1"
val visitTasks = visitTypeTasks.getList(MetaVisitTypeTasks::visitTasks.name)
storeTasks.getList("storeTasks").forEach {
visitTasks.add(it)
}
// save the object to the map
generatedStoreVisitTypes[storeUid] = visitTypeTasks
}
}
.safeRemoveField("storeTasks")
realm.schema.getOrCreate<RealmStore>()
.safeAddRealmListField(RealmStore::visitTypes, realm.schema)
.transform {
val storeUid = it.getString(RealmStore::storeUid.name)
// crash here on the “add” method
it.getList(RealmStore::visitTypes.name).add(generatedStoreVisitTypes[storeUid])
}
}
}
private inline fun <reified T> RealmSchema.getOrCreate(): RealmObjectSchema {
return get(T::class.java.simpleName) ?: create(T::class.java.simpleName)
}
private inline fun <reified TClass : RealmObject, reified TListItem : RealmObject, reified TList : RealmList<TListItem>> RealmObjectSchema.safeAddRealmListField(addedField: KMutableProperty1<TClass, TList>, schema: RealmSchema): RealmObjectSchema {
val fieldName = addedField.name
val listItemObjectSchema = schema.get(TListItem::class.java.simpleName)
if (!hasField(fieldName)) {
return addRealmListField(fieldName, listItemObjectSchema)
}
return this
}
Calling the "add" method in the second “transform” method sometimes causes a -
“java.lang.IllegalStateException: Object is no longer valid to operate on. Was it deleted by another thread?”
I’m familiar with this error and usually know how to handle it but I can’t recreate it or understand how such a thing happens in this case.
Since we’re talking about migration, there shouldn’t be another thread that’s operating on the same schema - as the execution is synchronised, isn’t it?.
Also, we’re talking about an object that was just created. There’s no other context in which it’s referenced or used.
I don’t understand how this object could be deleted. What could cause such an error?

Categories

Resources