cascade delete in android room database KOTLIN - android

There are a bunch of questions like this in StackOverflow but most of that arent about room database, so I had to ask a new question.
I have an app that uses room database and that has near 4 tables and a big relationship between those tables, so for instance when I delete a user in user list fragment, that user delete(only userName and some personal info) but the user's TRANSACTIONS and LOANS hadn't been deleted.
Someone told me I have to use Cascade delete but I didn't find much info about it.
My User class model:
#Entity(tableName = "user_info")
data class UserInfo(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "user_id")
var userId: Long =0L,
#ColumnInfo(name = "full_name")
var fullName:String?,
#ColumnInfo(name= "account_id")
var accountId: String?,
#ColumnInfo(name = "mobile_number")
var mobileNumber:String?,
#ColumnInfo(name = "phone_number")
var phoneNumber:String?,
#ColumnInfo(name = "date_of_creation")
var dateOfCreation:String?,
#ColumnInfo(name = "address")
var address:String?,
)
Transactions model class:
#Entity(tableName = "transactions")
data class Transactions(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "trans_id")
var transId: Long = 0L,
#ColumnInfo(name = "user_id")
var userId: Long?,
#ColumnInfo(name = "create_date")
var createDate: String?,
#ColumnInfo(name = "bank_id")
var bankId: Long?,
#ColumnInfo(name = "description")
var description: String?,
#ColumnInfo(name = "increase")
var increase: String?,
#ColumnInfo(name = "decrease")
var decrease: String?,
#ColumnInfo(name = "loan_number")
var loanNumber: String?,
#ColumnInfo(name = "total")
var total: Long?,
#ColumnInfo(name = "type")
var type: String?
)
User DAO:
#Insert
suspend fun insert(ui: UserInfo): Long
#Update
suspend fun update(ui: UserInfo)
#Insert
suspend fun insertList(ui: MutableList<UserInfo>)
#Delete
suspend fun deleteUser(ui: UserInfo)
#Query("DELETE FROM user_info")
fun deleteAllUser()
#Query("SELECT user_info.user_id, user_info.full_name, transactions.total From user_info JOIN transactions ")
fun joinTable(): LiveData<List<UserAndMoney>>?
#Query("SELECT * from user_info WHERE user_id = :key")
fun get(key: Long): LiveData<UserInfo>?
#Query("SELECT * FROM user_info ORDER BY full_name DESC")
fun getAllUserInfo(): LiveData<List<UserInfo>>
#Query("SELECT * FROM user_info where full_name like '%' || :fullName || '%' ORDER BY full_name ASC")
fun searchUserName(fullName: String): LiveData<List<UserInfo>>
If It was not clear for you till now, let me makes it easy for you:
I need cascade delete that delets every thing about user and a record.

CASCADE is an option of a Foreign Key constraint. So you would need to define Foreign Key constraints. You define Foreign Key constraints in Room via the #Entity annotation.
As an example, as it would appear that a Transactions is related to a UserInfo via var userId: Long?, (column name user_id) you could have :-
#Entity(tableName = "transactions",
foreignKeys = [
ForeignKey(
entity = UserInfo::class,
parentColumns = ["user_id"],
childColumns = ["user_id"],
onDelete = ForeignKey.CASCADE, //<<<<<
onUpdate = ForeignKey.CASCADE // Optional
)
]
)
data class Transactions(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "trans_id")
var transId: Long = 0L,
#ColumnInfo(name = "user_id", index = true) // <<<<< best to have an index on the column, not required
var userId: Long?,
#ColumnInfo(name = "create_date")
var createDate: String?,
#ColumnInfo(name = "bank_id")
var bankId: Long?,
#ColumnInfo(name = "description")
var description: String?,
#ColumnInfo(name = "increase")
var increase: String?,
#ColumnInfo(name = "decrease")
var decrease: String?,
#ColumnInfo(name = "loan_number")
var loanNumber: String?,
#ColumnInfo(name = "total")
var total: Long?,
#ColumnInfo(name = "type")
var type: String?
)
Note
The constraint enforces referential integrity, that is a transaction can not be inserted/updated if the user_id value is not a value that exists in the user_id column of the user_info table.
The CASCADE onUpdate will cascade a change to the user_id value in the user_info table to the respective transactions.
Additional
Someone told me I have to use Cascade delete but I didn't find much info about it.
What you have been told is incorrect. You could replicate the functionality without ON DELETE CASCADE or without the Foreign Key constraint.
You could use
#Query("DELETE FROM transaction WHERE user_id=:userId")
fun cascadeDeletionsFromUser(userId: Long)
noting that if the Foreign Key constraint exists in the transactions table but didn't have an onDelete action specified, then the cascadeDeletionsFromUser function would have to be run before the user_info row is deleted. Otherwise the user_info row could not be deleted as the FK constraint would inhibit the deletion.
If you had an abstract class rather than interface then you could have:-
#Query("DELETE FROM user_info WHERE user_id=:userId")
abstract fun deleteUserById(userId: Long)
#Query("DELETE FROM transactions WHERE user_id=:userId")
abstract fun cascadeDeletionsFromUser(userId: Long)
#Transaction
#Query("")
fun deleteUserWithCascade(userId: Long) {
cascadeDeletionsFromUser(userId)
deleteUserById(userId)
}
and use the deleteUserWithCascade function to delete the transactions and user in one go.
It is more convenient to use ON DELETE CASCADE, and especially so if you have multiple depths of relationships (when it gets a little more complex ascertaining children)

Related

Duh! Manually supply ID to Dao in Entity class

My entity:
#Entity(tableName = "accounts")
data class Account(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "user_id")
val id: Int,
#ColumnInfo(name = "first_name")
val firstName: String,
#ColumnInfo(name = "last_name")
val lastName: String?,
#ColumnInfo(name = "email")
val email: String
)
I am doing this:
fun register(email: String, name: String) {
return dbRepository.createAccount(Account(firstName = name, email = email))
}
Dao:
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertAccount(account: Account) : Long
Problem: Requires me to add an ID via parameter to the Account object, annoying. Because I have annotated that user_id is #PrimaryKey(autoGenerate = true) and shouldn't be manually supplied.
You need to set id as var and give it 0 as default value:
#Entity(tableName = "accounts")
data class Account(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "user_id")
var id: Int = 0,
#ColumnInfo(name = "first_name")
val firstName: String,
#ColumnInfo(name = "last_name")
val lastName: String?,
#ColumnInfo(name = "email")
val email: String
)
The default value is required to be able to create Account without providing the id because autoGenerate = true is not enough for the compiler to know that id is not required, and changing val to var because room in the background is going to parse that data and change that id so it must be mutable.
Note: This is room documentation for autoGenerate: if the field type is long or int (or its TypeConverter converts it to a long or int), Insert methods treat 0 as not-set while inserting the item.
If the field's type is Integer or Long (or its TypeConverter converts it to an Integer or a Long), Insert methods treat null as not-set while inserting the item.
So that's why the default value should be exactly 0 or null because room treats 0 and null as not-set and replace it with autoGenerated value.

Update all column values to boolean in room android?

I have database table as
#Entity
internal data class NotificationEntity(
#PrimaryKey #ColumnInfo(name = "notification_id") val notificationId: String,
val title: String,
val body: String?,
#ColumnInfo(name = "is_actionable") val isActionable: Boolean,
#ColumnInfo(name = "created_at") val createdAt: Instant,
#ColumnInfo(name = "is_read") val isRead: Boolean = false)
I want to update all the column values of isRead to true
I am using the following query but it isn't working.
#Query("UPDATE NotificationEntity SET is_read = 'true'")
suspend fun updateReadStatus()
Is it correct or not?
The boolean values are mapped to the integer internally. So you should use
#Query("UPDATE NotificationEntity SET is_read = 1")

Refresh items in Room database

I have an application that uses a Room database to store my "Friends" as accounts in an Account table
#Entity(tableName = "accounts")
data class Account(
#PrimaryKey
#ColumnInfo(name = "account_id")
val accountId: Int,
#ColumnInfo(name = "first_name", defaultValue = "")
var firstname: String,
#ColumnInfo(name = "last_name", defaultValue = "")
var lastname: String,
#ColumnInfo(name = "email", defaultValue = "")
var email: String,
#ColumnInfo(name = "status")
var status: Int,
#ColumnInfo(name = "role_id")
var roleId: Int,
#ColumnInfo(name = "lang", defaultValue = "")
var lang: String
) : Serializable
So when i refresh my accounts, there might be accounts that
will be deleted
will be inserted
will be updated
What is the most optimal way to identify what records need what action and how can i do it?
Let's say you have new accounts:
val newList: List<Account> = ... //
You can place in Dao next methods:
// To delete all accounts that are not included in new list
#Query("DELETE FROM account WHERE accountId NOT IN (: newIds)")
suspend fun deleteOutdatedAccounts(newIds: List<Int>)
// To insert/update all accounts from the new list
// Thanks to OnConflictStrategy.REPLACE strategy you get both insert and update
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUpdateAccounts(accounts: List<Account>)
// To do two methods above in single transaction
#Transaction
suspend fun refreshAccounts(newList List<Account>){
deleteOutdatedAccounts(newList.map{it.accountId})
insertUpdateAccounts(newList)
}
Afterwards you can call method refreshAccounts(newList) from your repository or ViewModel/Presenter.

Android Room FOREIGN KEY constraint failed (code 787) when insert new row

I followed this post Android Room FOREIGN KEY constraint failed (code 787). Unfortunately, it not works in my case.
I always get the error when trying to insert a new Note. Of course, there is a Topic existed in Database.
Do you guys have any idea?
Please take a look at my code below.
Topic:
#Entity(tableName = "topic")
data class Topic(#PrimaryKey var id: Long? = null,
#ColumnInfo(name = "name") var name: String,
#ColumnInfo(name = "note_count") var noteCount: Int = 0,
#ColumnInfo(name = "view_count") var viewCount: Long = 0) : Serializable {
#Ignore
var notes: List<Note>? = null
}
Note:
#Entity(tableName = "note",
foreignKeys = arrayOf(ForeignKey(entity = Topic::class,
parentColumns = arrayOf("id"), childColumns = arrayOf("topic_id"), onDelete = ForeignKey.CASCADE)))
#TypeConverters(TimestampConverter::class)
data class Note(#PrimaryKey(autoGenerate = true) var noteId: Long? = null,
#ColumnInfo(name = "topic_id") var topicId: Long? = null,
#ColumnInfo(name = "title") var title: String,
#ColumnInfo(name = "caption") var caption: String? = "",
#ColumnInfo(name = "created_at") var createdAt: Date? = null) : Serializable {
}
NoteDao:
#Dao
interface NoteDao {
#Query("SELECT * from note")
fun getAll(): LiveData<List<Note>>
#Insert(onConflict = REPLACE)
fun insert(note: Note)
#Query("DELETE from note")
fun deleteAll()
#Delete
fun delete(category: Note)
#Query("SELECT * FROM note WHERE title = :title COLLATE NOCASE LIMIT 1")
fun findNoteByTitle(title: String): Note?
#Query("SELECT * FROM note WHERE topic_id = :topicId")
fun findNotesByTopicId(topicId: Long): LiveData<List<Note>>
#Query("SELECT count(*) FROM note WHERE topic_id = :topicId")
fun countNotesByTopicId(topicId: Long): Long
#Update(onConflict = IGNORE)
fun update(category: Note)
}
I found the problem. Because I have been created 2 different Databases: TopicDataBase and NoteDataBase.
So I just need to remove 1 of them.
My bad >"<
(#PrimaryKey var id: Long? = null, looks like a problem. Primary key can't be null

android architecture components + retrofit

I've followed the GithubBrowserSample from Google to get started with Android Architecture Components and Retrofit. Everything works fine but I have troubles in my own data model because of foreign keys.
Let's say I have a place :
#Entity(tableName = "place",
foreignKeys = [
ForeignKey(entity = User::class, parentColumns = ["user_id"], childColumns = ["place_created_by_user_id"])
],
indices = [
Index(value = ["place_created_by_user_id"], name = "place_created_by_user_index")
])
data class Place(
#SerializedName("id")
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "place_id")
var id: Long,
#SerializedName("name")
#ColumnInfo(name = "place_name")
var name: String?,
#SerializedName("created_by_user_id")
#ColumnInfo(name = "place_created_by_user_id")
var createdByUserId: Long?,
)
And a user :
#Entity(tableName = "user")
data class User(
#SerializedName("id")
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "user_id")
var id: Long,
#SerializedName("first_name")
#ColumnInfo(name = "user_first_name")
var firstName: String,
#SerializedName("last_name")
#ColumnInfo(name = "user_last_name")
var lastName: String,
)
Following the sample of Google, the method to fetch the places in the repository is :
fun loadPlaces(): LiveData<Resource<List<Place>>> {
return object : NetworkBoundResource<List<Place>, List<Place>>(appExecutors) {
override fun saveCallResult(item: List<Place>) {
placeDao.insert(item)
}
override fun shouldFetch(data: List<Place>?): Boolean = true
override fun loadFromDb() = placeDao.getAll()
override fun createCall() = service.getPlaces()
override fun onFetchFailed() {
//repoListRateLimit.reset(owner)
}
}.asLiveData()
}
So normally, it would simply work (I tried with an entity without foreign key) but it failed because of the foreign constraint :
android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787)
Indeed, the user is not loaded yet.
So before placeDao.insert(item), I have to load each users to make sure the place will find his user. And it's the same for each entities and each foreign keys.
Any ideas of how can I achieve this following this architecture?
The point is when I call loadPlaces() in my ViewModel like this :
class PlacesViewModel(application: Application) : BaseViewModel(application) {
val places: LiveData<Resource<List<Place>>> = repository.loadPlaces()
}
The repository would intrinsically load the users attached to the places...
Thanks for your help.

Categories

Resources