Android Room Bug in Kotlin - android

I think the is a semantic issue in Room while working in Kotlin.
Simple DAO query in Room can be obtained by
#Query("SELECT * FROM Users WHERE id = :id")
fun getUser(id: Int): User
Although, I defined return type as User, not User?, this query can still returns null, when there is no User with the given id.
So whenever you call this function, you definitely need to check for null return, as below
val user = userDao.getUser("someid")
if (user != null){
return user
}
else {
return DEFAULT_USER
}
But since we defined return type of getUser as User, #kotlin compiler suggest that null check is redundant.
Am I missing something? Can anyone give some feedback on this?

There is no semantic issue. If Your function can return a null, it should be defined in the function return type. So, your function signature should be like this
fun getUser(id: Int): User?
This means the function can return null and now your null checks should work fine.

Apparently it's already tracked issue in Google Issue Tracker
And as a respond they developers say
Status: Won't Fix (Intended Behavior)
this is not about generated code
though, you are the person who creates that Query method so you should
declare it as nullable.
So, if we know that our code can return null, we should declare it as Nullable as
#Query("SELECT * FROM Users WHERE id = :id")
fun getUser(id: Int): User?

Related

Room DAO keeps returning NULL

In room, I have a dao to something like this:
#Dao
interface FacultyDao {
#Query("select * from faculty")
fun getAll(): LiveData<List<Faculty>>
...
}
And inside the repository, I'm simply calling this method and logging it:
class FacultyRepository(application: Application) {
private val facultyDao: FacultyDao
init {
val db: AppDB = AppDB.getInstance(application)
facultyDao = db.facultyDao()
}
fun getAllFaculty(): LiveData<List<Faculty>> {
val v = facultyDao.getAll()
Log.d("muaxx", v.value.toString())
return v
}
...
}
But the thing is it's returning me null, but when I ran that query in inspector it worked. Am I missing something?
LiveData doesn’t immediately have an initial value. Room queries the database and gets the result on a background thread. Then on the next loop of the main thread, the LiveData’s value will be set to this retrieved value. You are logging value too early. The initial value is going to appear some time in the future, after this function has already returned.
Normally you should only be getting a LiveData value through observing it.
Directly checking the value should usually only be done when you are managing a MutableLiveData and are using the previous value to help determine the next value that you are going to post.
Live data gives us real-time data. Therefore, for the first time, you still don't have some in yourself. And it is waiting for the response of the database. If you want to see some of the live data, you must observe it so that after receiving the information, the observer will be called and the information will be logged.

Why did I get the error 'Entities and POJOs must have a usable public constructor' when adding a (faulty) Room database query to my DAO?

I was recently working on an Android app and ran into the error Entities and POJOs must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type) when adding Query's to my Room database repository class.
However, this took me quite a while to spot, because the error I got when building was, to my knowledge, quite unrelated. When searching SO for this question, I found a lot of answers which list something along the likes of 'Make sure you annotate your variables with #ColumnInfo' or 'Move your #Ignore tags to the body of the function' or 'Make an empty constructor like it says' but none of these were applicable to me or fixed the problem.
The weird thing was, my App ran fine if I just removed these three queries from my program, so it couldn't have anything to do with the constructor message.
Eventually, I noticed that my database Query was wrong (see comment in below code). This took me much longer than it should have though, since I had no clue my Query was causing a seemingly unrelated buildlog error.
I'm mostly posting this question here so that someone else might find it if they find themselves in a similar situation (ENSURE YOUR QUERIES ARE CORRECT!!), but I am also wondering if I'm understanding this error message wrong, or if it's actually a faulty error message.
Can someone explain to me why a faulty quert would result into said error message?
DAO containing faulty queries:
#Dao
interface GameItemDAO {
#Query("SELECT * FROM gameItemTable")
suspend fun getAllGameItems(): List<GameItem>
#Insert
suspend fun insertGameItem(gameItem: GameItem)
#Delete
suspend fun deleteGameItem(gameItem: GameItem)
#Query("DELETE FROM gameItemTable")
suspend fun deleteAllGameItems()
#Query("SELECT * FROM gameItemTable ORDER BY ID DESC LIMIT 1")
suspend fun getLastGameItem(): GameItem?
// These functions were faulty, and should have listed SELECT COUNT (*) FROM...
#Query("SELECT * FROM gameItemTable WHERE OUTCOME=0")
suspend fun getWinCount(): Int
#Query("SELECT * FROM gameItemTable WHERE OUTCOME=1")
suspend fun getLossCount(): Int
#Query("SELECT * FROM gameItemTable WHERE OUTCOME=2")
suspend fun getDrawCount(): Int
}
#Entity class:
#Entity(tableName = "gameItemTable")
data class GameItem(
#ColumnInfo(name = "date")
var date: Date,
#ColumnInfo(name = "moveComputer")
var moveComputer: MoveType,
#ColumnInfo(name = "movePlayer")
var movePlayer: MoveType,
#ColumnInfo(name = "outcome")
var outcome: OutcomeType,
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
var id: Long? = null
)
The error message is quite correct. Please note that when the return type of a query is not a collection/array/list of some type, Room will return the first item found, though it will retrieve all the data internally so it's not a good practice to depend on that behavior.
In your faulty case, you're asking Room to fetch * which means that it is expecting to return an object of a type with properties matching the 5 columns of the table. Also, it needs to know how to build it, either using a constructor with parameters matching the five columns or an empty constructor with setters for the five columns. Of course, Int doesn't match.

am i understanding ?.let wrong? (PrimaryKey error when either updating or inserting into room db)

this function below
in goes the dictWord
if it exists it increases the amount property by one updates in the db and returns out of the uiscope - out of the function
and when the getDictWord return a null it goes to creation a newDictWord and adds that into the db and leaves the coroutine scope and then the function
no?
because i do get " android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: DictWord.name (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)"
from time to time from that
fun addOrIncreaseDictWordAmount(dictWord: String) {
uiScope.launch {
userDatabaseDao.getDictWord(dictWord)?.let {
it.amount += 1
userDatabaseDao.updateDictWord(it)
return#launch
}
// adds the strain to the AddNew dropDown list
val newDictWord = DictWord(name = dictWord)
userDatabaseDao.insertDictWord(newDictWord)
}
In regards to your initial question regarding the ?.let syntax...
This syntax will work with a nullable value (in your case the return of getDictWord(dictWord).
If that value is not null you will be passed into the attached block, and the value will be passed into the block as it, and guaranteed not-null for the rest of the closure.
If the value is null, it will skip that closure completely.
As for the intermittent error you are receiving, and based on your provided information, I am thinking your solution to that would be involved with the answer given here: UNIQUE constraint failed
If you are able to provide more involved logs and information I would be happy to edit and improve this answer to get your issue fixed.

Android Room library with Kotlin Flow toList() doesn't work

I made a simple example app with using Room and Flows:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val build = Room.databaseBuilder(this, FinanceDatabase::class.java, "database.db")
.fallbackToDestructiveMigration()
.build()
GlobalScope.launch {
build.currencyDao().addCurrency(CurrencyLocalEntity(1))
val toList = build.currencyDao().getAllCurrencies().toList()
Log.d("test", "list - $toList")
}
}
}
#Entity(tableName = "currency")
data class CurrencyLocalEntity(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "currencyId")
var id: Int
) {
constructor() : this(-1)
}
#Dao
interface CurrencyDao {
#Query("SELECT * FROM currency")
fun getAllCurrencies(): Flow<CurrencyLocalEntity>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addCurrency(currency: CurrencyLocalEntity)
}
#Database(entities = [CurrencyLocalEntity::class], version = 1)
abstract class FinanceDatabase : RoomDatabase() {
abstract fun currencyDao(): CurrencyDao
}
I want to use toList() function as in code above but something gets wrong and even Log doesn't print. At the same time using collect() works fine and gives me all records.
Can anybody explain to me what is wrong? Thanks.
There are a couple things wrong here but I'll address the main issue.
Flows returned by room emit the result of the query everytime the database is modified. (This might be scoped to table changes instead of the whole database).
Since the database can change at any point in the future, the Flow will (more or less) never complete because a change can always happen.
Your calling toList() on the returned Flow will suspend forever, since the Flow never completes. This conceptually makes sense since Room cannot give you the list of every change that will happen, without waiting for it to happen.
With this, I'm sure you know why collect gives you the records and toList() doesn't.
What you probably want here is this.
#Query("SELECT * FROM currency")
fun getAllCurrencies(): Flow<List<CurrencyLocalEntity>>
With this you can get the first result of the query with Flow<...>.first().
Flow in Room is for observing Changes in table.
Whenever any changes are made to the table, independent of which row is changed, the query will be re-triggered and the Flow will emit again.
However, this behavior of the database also means that if we update an unrelated row, our Flow will emit again, with the same result. Because SQLite database triggers only allow notifications at table level and not at row level, Room can’t know what exactly has changed in the table data
Make sure that the same doa object you are using for retrieving the list, is used for updating the database.
other than that converting flow to livedata is done using asLivedata extension function
For me below solution works for updating the view with database table changes.
Solution: Same Dao Object should be used when we insert details into the room database and get information from DB.
If you are using a dagger hilt then
#Singleton annotation will work.
I hope this will solve your problem.
**getAllCurrencies()** function should be suspend.
Please check the syntax to collect List from Flow:
suspend fun <T> Flow<T>.toList(
destination: MutableList<T> = ArrayList()
): List<T> (source)
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/to-list.html

Return type is 'Unit?', which is not a subtype of overridden

Today while programming I found some odd behaviour in Kotlin. I could easily go around it, but I wonder if there is some reason to it or if it is a bug in Kotlin.
I have the following interface of a delegate which delegates the showing of a dialog to the Activity.
interface ViewModelDelegate {
fun showWarningDialog(textResource: Int)
}
I want to implement it as following in the Activity. Since I know I can only do it with a context and the Activity.getContext() may return null, I wrap the code in context?.let
override fun showWarningDialog(textResource: Int) = context?.let {
//show dialog
}
However this gives me a compile error:
Return type of 'showWarningDialog' is not a subtype of the return type of the overridden member 'public abstract fun showWarningDialog(textResource: Int): Unit defined in com.some.class.path'
Which really confused me, because I don't want to return anything. So since let returns whatever the function inside returns, I was wondering if I could fix it by writing a version of let which does not return anything.
fun <T, R> T.myLet(block: (T) -> R) {
let(block)
}
However this did not remove the compiler error. I found then that the mouseover text over the error gives more information (would be nice if the compiler did). It says:
Return type is 'Unit?', which is not a subtype of overridden
Now that tells me more about the problem. Because the function context?let call may not happen, it could return null. Now there are multiple ways to go around this. I could add ?: Unit too the end of the function call or I could define showWarningDialog to return Unit? which will allow me to call it just fine in most cases. However none of these solutions are desireable. I will probably just make a normal method and call the let inside of that instead of delegating the call to it. Costs me another level of indentation and an extra vertical line:
override fun showWarningDialog(textResource: Int) {
context?.let {
//show dialog
}
}
My question is, is this behaviour intended? Why or when would this be useful that a function that returns Unit cannot be delegated to an optional function call. I am very confused by this behaviour.
Single expression function
fun foo() = <expression>
by language design is equivalent to
fun foo(): <ReturnType> {
return <expression>
}
And because Unit? is not a not a subtype of Unit, you can't return it in from a function, which returns Unit. In this sense Unit just another type in the type system, it's not something magical. So it works just as it's supposed to work with any other type.
Why or when would this be useful that a function that returns Unit cannot be delegated to an optional function call.
So basically the question is why language designers did not created a special handling to accept Unit? from a function declaring Unit as a return type. I can think about a few reasons:
It requires to create this special handling in the compiler. Special cases lead to bugs, break slim language design and complicate documentation.
As it had to be a special case, it would be not really clear and predictable for programmers. Currently it works in the same way for all types, no special treatments. It makes the language predictable, you don't need to check the documentation for every type to see if it's treated specially.
It also adds some additional safety, so to make you notice that your expression can actually skip the calculation.
So trying to summarize, I would say making this case work does not add much of value but can potentially bring some issues. That's probably why they did not add it to the language.
lets discuss this case when you have return type for example String
interface someInterface{
fun somFun():String
}
class someClass : someInterface {
var someString:String? = null
override fun somFun()=someString?.let {
//not working
it
}
override fun somFun()=someString?.let {
//working
it
}?:""
}
so what we see that when parents return type is String you cannot return Strin? it is jus kotlins nullSafety ,
what is different when you don't have return type ? lets change the code above a little
interface someInterface{
fun somFun():String
fun unitFun()
}
class someClass : someInterface {
var someString:String? = null
override fun unitFun() {
//if it is possible to return null in here
}
override fun somFun()=someString?.let {
val someresult = unitFun().toString() //you will get crash
it
}?:""
}
now we have another function without return type (unitFun Unit)
so if you can return Unit? in your subclass it will cause a crash when you want to use the result of method because it is defined asUnit and you dont need any null checks.
generally it means Unit is also type and you need to keep it null safe .

Categories

Resources