Android Room Persistence Library: Upsert - android

Android's Room persistence library graciously includes the #Insert and #Update annotations that work for objects or collections. I however have a use case (push notifications containing a model) that would require an UPSERT as the data may or may not exist in the database.
Sqlite doesn't have upsert natively, and workarounds are described in this SO question. Given the solutions there, how would one apply them to Room?
To be more specific, how can I implement an insert or update in Room that would not break any foreign key constraints? Using insert with onConflict=REPLACE will cause the onDelete for any foreign key to that row to be called. In my case onDelete causes a cascade, and reinserting a row will cause rows in other tables with the foreign key to be deleted. This is NOT the intended behavior.

EDIT:
as #Tunji_D mentioned,
Room officially supports #Upsert from version 2.5.0-alpha03. (release note)
please check his answer for more details.
OLD ANSWER:
Perhaps you can make your BaseDao like this.
secure the upsert operation with #Transaction,
and try to update only if insertion is failed.
#Dao
public abstract class BaseDao<T> {
/**
* Insert an object in the database.
*
* #param obj the object to be inserted.
* #return The SQLite row id
*/
#Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract long insert(T obj);
/**
* Insert an array of objects in the database.
*
* #param obj the objects to be inserted.
* #return The SQLite row ids
*/
#Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract List<Long> insert(List<T> obj);
/**
* Update an object from the database.
*
* #param obj the object to be updated
*/
#Update
public abstract void update(T obj);
/**
* Update an array of objects from the database.
*
* #param obj the object to be updated
*/
#Update
public abstract void update(List<T> obj);
/**
* Delete an object from the database
*
* #param obj the object to be deleted
*/
#Delete
public abstract void delete(T obj);
#Transaction
public void upsert(T obj) {
long id = insert(obj);
if (id == -1) {
update(obj);
}
}
#Transaction
public void upsert(List<T> objList) {
List<Long> insertResult = insert(objList);
List<T> updateList = new ArrayList<>();
for (int i = 0; i < insertResult.size(); i++) {
if (insertResult.get(i) == -1) {
updateList.add(objList.get(i));
}
}
if (!updateList.isEmpty()) {
update(updateList);
}
}
}

For more elegant way to do that I would suggest two options:
Checking for return value from insert operation with IGNORE as a OnConflictStrategy (if it equals to -1 then it means row wasn't inserted):
#Insert(onConflict = OnConflictStrategy.IGNORE)
long insert(Entity entity);
#Update(onConflict = OnConflictStrategy.IGNORE)
void update(Entity entity);
#Transaction
public void upsert(Entity entity) {
long id = insert(entity);
if (id == -1) {
update(entity);
}
}
Handling exception from insert operation with FAIL as a OnConflictStrategy:
#Insert(onConflict = OnConflictStrategy.FAIL)
void insert(Entity entity);
#Update(onConflict = OnConflictStrategy.FAIL)
void update(Entity entity);
#Transaction
public void upsert(Entity entity) {
try {
insert(entity);
} catch (SQLiteConstraintException exception) {
update(entity);
}
}

EDIT:
Starting in version 2.5.0-alpha03, Room now has support for an #Upsert annotation.
An example of its use can be seen in this pull request in the "Now in Android" sample app.
OLD ANSWER:
I could not find a SQLite query that would insert or update without causing unwanted changes to my foreign key, so instead I opted to insert first, ignoring conflicts if they occurred, and updating immediately afterwards, again ignoring conflicts.
The insert and update methods are protected so external classes see and use the upsert method only. Keep in mind that this isn't a true upsert as if any of the MyEntity POJOS have null fields, they will overwrite what may currently be in the database. This is not a caveat for me, but it may be for your application.
#Insert(onConflict = OnConflictStrategy.IGNORE)
protected abstract void insert(List<MyEntity> entities);
#Update(onConflict = OnConflictStrategy.IGNORE)
protected abstract void update(List<MyEntity> entities);
#Transaction
public void upsert(List<MyEntity> entities) {
insert(models);
update(models);
}

If the table has more than one column, you can use
#Insert(onConflict = OnConflictStrategy.REPLACE)
to replace a row.
Reference - Go to tips Android Room Codelab

This is the code in Kotlin:
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(entity: Entity): Long
#Update(onConflict = OnConflictStrategy.REPLACE)
fun update(entity: Entity)
#Transaction
fun upsert(entity: Entity) {
val id = insert(entity)
if (id == -1L) {
update(entity)
}
}

Just an update for how to do this with Kotlin retaining data of the model (Maybe to use it in a counter as in example):
//Your Dao must be an abstract class instead of an interface (optional database constructor variable)
#Dao
abstract class ModelDao(val database: AppDatabase) {
#Insert(onConflict = OnConflictStrategy.FAIL)
abstract fun insertModel(model: Model)
//Do a custom update retaining previous data of the model
//(I use constants for tables and column names)
#Query("UPDATE $MODEL_TABLE SET $COUNT=$COUNT+1 WHERE $ID = :modelId")
abstract fun updateModel(modelId: Long)
//Declare your upsert function open
open fun upsert(model: Model) {
try {
insertModel(model)
}catch (exception: SQLiteConstraintException) {
updateModel(model.id)
}
}
}
You can also use #Transaction and database constructor variable for more complex transactions using database.openHelper.writableDatabase.execSQL("SQL STATEMENT")

I found an interesting reading about it here.
It is the "same" as posted on https://stackoverflow.com/a/50736568/4744263. But, if you want an idiomatic and clean Kotlin version, here you go:
#Transaction
open fun insertOrUpdate(objList: List<T>) = insert(objList)
.withIndex()
.filter { it.value == -1L }
.forEach { update(objList[it.index]) }
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(obj: List<T>): List<Long>
#Update
abstract fun update(obj: T)

Alternatively to make UPSERT manually in loop like it's suggested in #yeonseok.seo post, we may use UPSERT feature provided by Sqlite v.3.24.0 in Android Room.
Nowadays, this feature is supported by Android 11 and 12 with default Sqlite version 3.28.0 and 3.32.2 respectively. If you need it in versions prior Android 11 you can replace default Sqlite with custom Sqlite project like this https://github.com/requery/sqlite-android (or built your own) to have this and other features that are available in latest Sqlite versions, but not available in Android Sqlite provided by default.
If you have Sqlite version starting from 3.24.0 on device, you can use UPSERT in Android Room like this:
#Query("INSERT INTO Person (name, phone) VALUES (:name, :phone) ON CONFLICT (name) DO UPDATE SET phone=excluded.phone")
fun upsert(name: String, phone: String)

Another approach I can think of is to get the entity via DAO by query, and then perform any desired updates.
This may be less efficient compared to the other solutions in this thread in terms of runtime because of having to retrieve the full entity, but allows much more flexibility in terms of operations allowed such as on what fields/variable to update.
For example :
private void upsert(EntityA entityA) {
EntityA existingEntityA = getEntityA("query1","query2");
if (existingEntityA == null) {
insert(entityA);
} else {
entityA.setParam(existingEntityA.getParam());
update(entityA);
}
}

Here is a way to use a real UPSERT clause in Room library.
The main advantage of this method is that you can update rows for which you don't know their ID.
Setup Android SQLite support library in your project to use modern SQLite features on all devices:
Inherit your daos from BasicDao.
Probably, you want to add in your BasicEntity: abstract fun toMap(): Map<String, Any?>
Use UPSERT in your Dao:
#Transaction
private suspend fun upsert(entity: SomeEntity): Map<String, Any?> {
return upsert(
SomeEntity.TABLE_NAME,
entity.toMap(),
setOf(SomeEntity.SOME_UNIQUE_KEY),
setOf(SomeEntity.ID),
)
}
// An entity has been created. You will get ID.
val rawEntity = someDao.upsert(SomeEntity(0, "name", "key-1"))
// An entity has been updated. You will get ID too, despite you didn't know it before, just by unique constraint!
val rawEntity = someDao.upsert(SomeEntity(0, "new name", "key-1"))
BasicDao:
import android.database.Cursor
import androidx.room.*
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
abstract class BasicDao(open val database: RoomDatabase) {
/**
* Upsert all fields of the entity except those specified in [onConflict] and [excludedColumns].
*
* Usually, you don't want to update PK, you can exclude it in [excludedColumns].
*
* [UPSERT](https://www.sqlite.org/lang_UPSERT.html) syntax supported since version 3.24.0 (2018-06-04).
* [RETURNING](https://www.sqlite.org/lang_returning.html) syntax supported since version 3.35.0 (2021-03-12).
*/
protected suspend fun upsert(
table: String,
entity: Map<String, Any?>,
onConflict: Set<String>,
excludedColumns: Set<String> = setOf(),
returning: Set<String> = setOf("*")
): Map<String, Any?> {
val updatableColumns = entity.keys
.filter { it !in onConflict && it !in excludedColumns }
.map { "`${it}`=excluded.`${it}`" }
// build sql
val comma = ", "
val placeholders = entity.map { "?" }.joinToString(comma)
val returnings = returning.joinToString(comma) { if (it == "*") it else "`${it}`" }
val sql = "INSERT INTO `${table}` VALUES (${placeholders})" +
" ON CONFLICT(${onConflict.joinToString(comma)}) DO UPDATE SET" +
" ${updatableColumns.joinToString(comma)}" +
" RETURNING $returnings"
val query: SupportSQLiteQuery = SimpleSQLiteQuery(sql, entity.values.toTypedArray())
val cursor: Cursor = database.openHelper.writableDatabase.query(query)
return getCursorResult(cursor).first()
}
protected fun getCursorResult(cursor: Cursor, isClose: Boolean = true): List<Map<String, Any?>> {
val result = mutableListOf<Map<String, Any?>>()
while (cursor.moveToNext()) {
result.add(cursor.columnNames.mapIndexed { index, columnName ->
val columnValue = if (cursor.isNull(index)) null else cursor.getString(index)
columnName to columnValue
}.toMap())
}
if (isClose) {
cursor.close()
}
return result
}
}
Entity example:
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
#Entity(
tableName = SomeEntity.TABLE_NAME,
indices = [Index(value = [SomeEntity.SOME_UNIQUE_KEY], unique = true)]
)
data class SomeEntity(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = ID)
val id: Long,
#ColumnInfo(name = NAME)
val name: String,
#ColumnInfo(name = SOME_UNIQUE_KEY)
val someUniqueKey: String,
) {
companion object {
const val TABLE_NAME = "some_table"
const val ID = "id"
const val NAME = "name"
const val SOME_UNIQUE_KEY = "some_unique_key"
}
fun toMap(): Map<String, Any?> {
return mapOf(
ID to if (id == 0L) null else id,
NAME to name,
SOME_UNIQUE_KEY to someUniqueKey
)
}
}

#Upsert is now available in room Version 2.5.0-beta01
check out the release notes

Should be possible with this sort of statement:
INSERT INTO table_name (a, b) VALUES (1, 2) ON CONFLICT UPDATE SET a = 1, b = 2

If you have legacy code: some entities in Java and BaseDao as Interface (where you cannot add a function body) or you too lazy for replacing all implements with extends for Java-children.
Note: It works only in Kotlin code. I'm sure that you write new code in Kotlin, I'm right? :)
Finally a lazy solution is to add two Kotlin Extension functions:
fun <T> BaseDao<T>.upsert(entityItem: T) {
if (insert(entityItem) == -1L) {
update(entityItem)
}
}
fun <T> BaseDao<T>.upsert(entityItems: List<T>) {
val insertResults = insert(entityItems)
val itemsToUpdate = arrayListOf<T>()
insertResults.forEachIndexed { index, result ->
if (result == -1L) {
itemsToUpdate.add(entityItems[index])
}
}
if (itemsToUpdate.isNotEmpty()) {
update(itemsToUpdate)
}
}

Related

How to get the id of inserted row in android room databse in Kotlin?

I am trying to get the user ID from the newest user. How can I make the insert method spit the ID when the ID is autogenerated?
in Model
#PrimaryKey(autoGenerate = true)
val userId: Int
in Dao
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun addUserWithLong(user: User): LiveData<Long>
in Repository
fun addUserWitLong(user: User): LiveData<Long> {
return userDao.addUserWithLong(user)
}
in ViewModel
fun addUserWithLong(user: User): LiveData<Long> {
return repository.addUserWitLong(user)
}
in Fragment
val id: Long? = userViewModel.addUserWithLong(user).value
I have read in the docs that #Insert returns Long as the row ID but I do not know how to program it. Now the error is "Not sure how handle insert method return type." Is there some way to make with LiveData and not with Rxjava. That is without the need to download more dependecies.
As per the documentation here
If the #Insert method receives a single parameter, it can return a
long value, which is the new rowId for the inserted item. If the
parameter is an array or a collection, then the method should return
an array or a collection of long values instead, with each value as
the rowId for one of the inserted items. To learn more about returning
rowId values, see the reference documentation for the #Insert
annotation, as well as the SQLite documentation for rowid tables
So you can use it like
#Insert(onConflict = OnConflictStrategy.REPLACE)
long addUserWithLong(user: User)
or if you are inserting a list
#Insert(onConflict = OnConflictStrategy.REPLACE)
long[] addUserWithLong(user: List<User>)
Edit-1
After checking answers from this post.
No, you can't. I wrote an answer to the issue. The reason is, that
LiveData is used to notify for changes. Insert, Update, Delete won't
trigger a change.
I just created a test project and successfully received Id of last inserted item in activity. Here is my implementation.
Dao
#Insert
suspend fun addUser(user: Users): Long
Repo
suspend fun insertUser(context: Context, users: Users): Long {
val db = AppDatabase.getInstance(context)
val dao = db.userDao()
return dao.addUser(users)
}
ViewModel
fun addUser(context: Context, users: Users) = liveData {
//you can also emit your customized object here.
emit("Inserting...")
try {
val userRepo = UsersRepo()
val response = userRepo.insertUser(context, users)
emit(response)
} catch (e: Exception) {
e.printStackTrace()
emit(e.message)
}
}
Activity
viewModel.addUser(applicationContext, user).observe(this, Observer { userId ->
Log.d("MainActivity", "Inserted User Id is $userId")
})
Check test application here.

java.util.ConcurrentModificationException while inserting data into room database

I am trying to download data from firebase firestore and insert it into the room DB for some offline use and avoid time-lag using the MVVM architecture pattern but when I do that I get an java.util.ConcurrentModificationException error I am inserting the data into the room DB inside a coroutine.
My code
class HomeFragmentViewModel(application: Application): AndroidViewModel(application) {
private var mDatabase: AppDatabase = AppDatabase.getInstance(application)!!
private val postListRoom: MutableList<PostRoomEntity> = mutableListOf()
private val postList: LiveData<MutableList<PostRoomEntity>>? = getPostList2()
private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val db: FirebaseFirestore = FirebaseFirestore.getInstance()
private val myTAG: String = "MyTag"
#JvmName("getPostList")
fun getPostList(): LiveData<MutableList<PostRoomEntity>>? {
return postList
}
#JvmName("getPostList2")
fun getPostList2(): LiveData<MutableList<PostRoomEntity>>? {
var postsDao: PostsDao? = null
Log.d(myTAG, "postDao getPost is " + postsDao?.getPosts())
return mDatabase.postsDao()?.getPosts()
// return postList
}
fun loadDataPost() {
val list2 = mutableListOf<PostRoomEntity>()
db.collection("Posts")
.addSnapshotListener { snapshots, e ->
if (e != null) {
Log.w(myTAG, "listen:error", e)
return#addSnapshotListener
}
for (dc in snapshots!!.documentChanges) {
when (dc.type) {
DocumentChange.Type.ADDED -> {
dc.document.toObject(PostRoomEntity::class.java).let {
list2.add(it)
}
postListRoom.addAll(list2)
viewModelScope.launch(Dispatchers.IO) {
mDatabase.postsDao()?.insertPost(postListRoom)
}
// mDatabase.let { saveDataRoom(postListRoom, it) }
}
DocumentChange.Type.MODIFIED -> {
}
DocumentChange.Type.REMOVED -> {
Log.d(myTAG, "Removed city: ${dc.document.data}")
}
}
}
}
}
}
PostsDao
#Dao
interface PostsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPost(PostEntity: MutableList<PostRoomEntity>)
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllPosts(PostEntity :List<PostRoomEntity>)
#Query("Select * from PostRoomEntity")
fun getPosts(): LiveData<MutableList<PostRoomEntity>>
// #Query("SELECT * FROM notes WHERE id= :id")
// open fun getNoteById(id: Int): NoteEntity?
}
It is very error-prone to use MutableLists with asynchronous tasks or to expose them to outside functions. You are doing both, and this can result in them being modified from two different places in code simultaneously, which can cause a ConcurrentModificationException.
You should use read-only Lists to eliminate this risk. For example, use vars of type List instead of vals of type MutableList.
Some other issues with your code:
You are adding the whole contents of the list to the main list on each step of iteration, so the last item is added once, the second-to-last item is added twice, and so on. You are also inserting that whole exploded list in your local database on each step of iteration, so it is even more exponentially multiplied with redundancies. If you are just trying to update your local database with changes, you should only be inserting a single row at a time anyway.
Unnecessary nullability used in a few places. There's no reason for the DAO or your LiveData to ever be null.
Unnecessary intermediate variables that serve no purpose. Like you create a variable var postsDao: PostsDao? = null and log the null value and never use it.
Redundant and non-idiomatic getters for properties you could expose as public directly.
Redundant backing property for the value that's already held in a LiveData.
You can make your DAO functions suspend so you don't have to worry about which dispatchers you're using to call them.
There's no reason for the DAO to have an insert overload for a MutableList instead of a List. I think the parameter should just be a single item.
You can have a single coroutine iterate the list of changes instead of launching separate coroutines to handle each individual change.
I also recommend not mixing Hungarian and non-Hungarian member names. Actually I don't recommend using Hungarian naming at all, but it's a matter of preference.
And it's a little confusing that you have two databases, but there is nothing about their names to distinguish them.
Fixing these problems, your code will look like this, but there might be other issues because I can't test it or see what it's hooked up to. Also, I don't use Firebase, but I feel like there must be a more robust way of keeping your local database in sync with Firestore than trying to make individual changes with a listener.
class HomeFragmentViewModel(application: Application): AndroidViewModel(application) {
private val localDatabase: AppDatabase = AppDatabase.getInstance(application)!!
private val mutablePostList = MutableLiveData<List<PostRoomEntity>>()
val postList: LiveData<List<PostRoomEntity>> = mutablePostList
private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val firestore: FirebaseFirestore = FirebaseFirestore.getInstance()
private val myTAG: String = "MyTag"
fun loadDataPost() {
db.collection("Posts")
.addSnapshotListener { snapshot, e ->
if (e != null) {
Log.w(myTAG, "listen:error", e)
return#addSnapshotListener
}
mutablePostList.value = snapshot!!.documents.map {
it.toObject(PostRoomEntity::class.java)
}
viewModelScope.launch {
for (dc in snapshot!!.documentChanges) {
when (dc.type) {
DocumentChange.Type.ADDED -> {
val newPost = dc.document.toObject(PostRoomEntity::class.java)
localDatabase.postsDao().insertPost(newPost)
}
DocumentChange.Type.MODIFIED -> {
}
DocumentChange.Type.REMOVED -> {
Log.d(myTAG, "Removed city: ${dc.document.data}")
}
}
}
saveDataRoom(postListRoom, localDatabase) // don't know what this does
}
}
}
}
#Dao
interface PostsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPost(postEntity: PostRoomEntity)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllPosts(postEntity: List<PostRoomEntity>)
#Query("Select * from PostRoomEntity")
fun getPosts(): LiveData<MutableList<PostRoomEntity>>
// #Query("SELECT * FROM notes WHERE id= :id")
// open fun getNoteById(id: Int): NoteEntity?
}

LiveData + ViewModel + Room: Exposing a LiveData returned by query which changes over time (Through a fts search)

I have an FTS query in my DAO which I'd like to use to provide search in my App. The activity passes the query to view model each time the search text is changed.
The problem is that, Room returns a LiveData every single time the query is executed while I'd like to get same LiveData object updated when I run the query.
I was thinking about copying data from the LiveData which room returns into my dataSet (see the code below). Would it be a good approach? (And if yes, how would I actually do that?)
Here's my work so far:
In my Activity:
override fun onCreate(savedInstanceState: Bundle?) {
//....
wordViewModel = ViewModelProviders.of(this).get(WordMinimalViewModel::class.java)
wordViewModel.dataSet.observe(this, Observer {
it?.let {mRecyclerAdapter.setWords(it)}
})
}
/* This is called everytime the text in search box is changed */
override fun onQueryTextChange(query: String?): Boolean {
//Change query on the view model
wordViewModel.searchWord(query)
return true
}
ViewModel:
private val repository :WordRepository =
WordRepository(WordDatabase.getInstance(application).wordDao())
//This is observed by MainActivity
val dataSet :LiveData<List<WordMinimal>> = repository.allWordsMinimal
//Called when search query is changed in activity
//This should reflect changes to 'dataSet'
fun searchWord(query :String?) {
if (query == null || query.isEmpty()) {
//Add all known words to dataSet, to make it like it was at the time of initiating this object
//I'm willing to copy repository.allWordsMinimal into dataSet here
} else {
val results = repository.searchWord(query)
//Copy 'results' into dataSet
}
}
}
Repository:
//Queries all words from database
val allWordsMinimal: LiveData<List<WordMinimal>> =
wordDao.getAllWords()
//Queries for word on Room using Fts
fun searchWord(query: String) :LiveData<List<WordMinimal>> =
wordDao.search("*$query*")
//Returns the model for complete word (including the definition for word)
fun getCompleteWordById(id: Int): LiveData<Word> =
wordDao.getWordById(id)
}
DAO:
interface WordDao {
/* Loads all words from the database */
#Query("SELECT rowid, word FROM entriesFts")
fun getAllWords() : LiveData<List<WordMinimal>>
/* FTS search query */
#Query("SELECT rowid, word FROM entriesFts WHERE word MATCH :query")
fun search(query :String) :LiveData<List<WordMinimal>>
/* For definition lookup */
#Query("SELECT * FROM entries WHERE id=:id")
fun getWordById(id :Int) :LiveData<Word>
}
val dataSet :LiveData<List<WordMinimal>>
val searchQuery = MutableLiveData<String>()
init {
dataSet = Transformations.switchMap(searchQuery) { query ->
if (query == null || query.length == 0) {
//return WordRepository.getAllWords()
} else {
//return WordRepository.search(query)
}
}
}
fun setSearchQuery(searchedText: String) {
searchQuery.value = searchedText
}

Android: Check if object is present in database using Room and RxJava

I'm using Room as ORM and here is my Dao interface:
#Dao
interface UserDao {
#Query(value = "SELECT * FROM User LIMIT 1")
fun get(): Single<User?>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun add(profile: User)
}
In other hand I have a method in Repository which should check if the user is login or not, here is the implementation code:
override fun isUserLogin(): Single<Boolean> = userDao
.get().async()
.onErrorReturn { null }
.map { it != null }
Room will throw the following exception if no row matches the query:
Query returned empty result set: SELECT * FROM User LIMIT 1
I want to return null in this case, but when I execute the code it throw an exception with the following messsage:
Value supplied was null
I can't use Optional because Dao is returning Single<User?> so onErrorReturn should return the same type.
How can I check if the user exists or not without changing Dao?
I think the proper way to do this, besides Android and Room, is to use COUNT in your query.
#Query("SELECT COUNT(*) from User")
fun usersCount() : Single<Int>
It will return the number of rows in the table, if it's 0 you know there is no user in the db, so.
override fun isUserLogin(): Single<Int> = userDao
.usersCount()
.async()
.map { it > 0 }
If you really want to do it the ugly way though:
#Query(value = "SELECT * FROM User LIMIT 1")
fun get(): Single<User>
Don't use a nullable type, it's pointless. Then map to Boolean and handle the exception:
override fun isUserLogin(): Single<Boolean> = userDao
.get()
.async()
.map { true }
.onErrorReturn { false }
Use Maybe, which accurately models the response expecting either 0 results, 1 result or an exception.
If you change your DAO to return a Flowable list of users you can then map this into a boolean value by checking the size of the list.
#Query(value = "SELECT * FROM User LIMIT 1")
fun get(): Flowable<List<User>>
e.g.
fun isUserLogin(): Single<Boolean> = userDao
.get()
.flatMapSingle { Single.just(it.size == 1) }
.onErrorReturn { false }
.first(false)

Value emit even if data isn't updated

I try yo use Room with Kotlin and RxJava 2.
I have an entity with a string primary key and I would like to be notified when values are updated but I receive new value from flowable even if the value isn't updated when I had whatever value.
Entity :
#Entity
class MarketSummaryModel(#PrimaryKey var marketName: String)...
Dao :
#Dao
interface MarketSummaryDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(marketSummaryModel: MarketSummaryModel)
#Query("select * from MarketSummaryModel where marketName = :marketName LIMIT 1")
fun getByName(marketName: String): Flowable<MarketSummaryModel>
}

Categories

Resources