Room database loses data - android

I'm working on an Android project with Kotlin language and I'm using Room database from android architecture components. These are all my Room database stuff. I'm trying to let the user save information about a book and it works fine while I'm in the app, but when I restart the app, everything has been deleted. I need to store data in memory and because of that I'm using inMemoryDatabaseBuilder but it doesn't seem to work. Any help is appreciated.
Dependencies:
dependencies {
def room_version = "1.1.1"
implementation "android.arch.persistence.room:runtime:$room_version"
kapt "android.arch.persistence.room:compiler:$room_version"
}
apply plugin: 'kotlin-kapt'
Entity:
#Entity(tableName = "tblBooks")
data class BookData(#PrimaryKey(autoGenerate = true) var id: Long?,
#ColumnInfo(name = "name") var name: String,
#ColumnInfo(name = "author") var author: String,
#ColumnInfo(name = "translator") var translator: String,
#ColumnInfo(name = "publisher") var publisher: String,
#ColumnInfo(name = "publication_year") var publicationYear: Int?,
#ColumnInfo(name = "price") var price: Int?,
#ColumnInfo(name = "description") var description: String,
#ColumnInfo(name = "category") var category: String,
#ColumnInfo(name = "shelf_number") var shelfNumber: Int?,
#ColumnInfo(name = "front_cover") var frontCover: String?,
#ColumnInfo(name = "back_cover") var backCover: String?
){
constructor():this(null, "", "", "", "", 0, 0, "", "",
0, "", "")
}
Dao:
#Dao
interface BookDataDao {
#Query("SELECT * FROM tblBooks")
fun getAll(): List<BookData>
#Insert(onConflict = REPLACE)
fun insert(bookData: BookData)
}
Database:
#Database(entities = [BookData::class], version = 1)
abstract class BookDatabase: RoomDatabase(){
abstract fun bookDataDao(): BookDataDao
companion object {
private var INSTANCE: BookDatabase? = null
fun getInstance(context: Context): BookDatabase? {
if (INSTANCE == null){
synchronized(BookDatabase::class){
INSTANCE =
Room.inMemoryDatabaseBuilder(context.applicationContext,
BookDatabase::class.java).build()
}
}
return INSTANCE
}
}
}
Usage:
inserting data:
val database = BookDatabase.getInstance(this) !!
database.bookDataDao().insert(bookData)
retrieving data:
val database = BookDatabase.getInstance(this) !!
val booksList = database.bookDataDao().getAll()

If you are only storing on memory, it is normal your database does not persist between launches of the app. According to the documentation:
Information stored in an in memory database disappears when the process is killed
Meaning everytime you kill the application, the database is closed as well. If you want it to be persistant you should use standard databaseBuilder instead, it will be put on the device storage.

Related

Roomdb - Update #Embedded object within an Entity

I faced a problem when updating values of #Embedded object within an Entity.
Here is my Entity class:
#Entity
data class ReplyData(
#PrimaryKey val id: String,
#ColumnInfo(name = "sequence") val sequence: Int?,
#Embedded(prefix = "from") val from: From? <--- I want to update this #Embedded object within entity
)
#Entity
data class From(
#ColumnInfo(name = "id") val id: String? = "",
#ColumnInfo(name = "name") val name: String? = "",
#ColumnInfo(name = "avatar") val avatar: String? = "",
#ColumnInfo(name = "kind") val kind: String? = ""
)
I want to update these 3 values in 1 shot instead of updating them one-by-one with this query below.
#Query("UPDATE replydata.from SET name = :name, avatar = :avatar, kind = :kind WHERE id = :id")
fun updateReplyData(id: String, name: String, avatar: String, kind: String)
How can I achieve that without affecting the original entity (ReplyData)?
I tried this solution, but it is not working at all:
#Query("SELECT * FROM message WHERE id = :id")
suspend fun getReplyMessage(id: String): Message
#Update(entity = From::class)
suspend fun updateReply(msg: Message)
suspend fun updatePinMessage(id: String, from: From) {
val msg = getReplyMessage(id)
msg.from?.avatar = from.avatar
msg.from?.name = from.name
msg.from?.kind= from.kind
updateReply(msg)
}
Some notes:
#Embedded just shares own fields in the parent.
For instance the data table columns are:
[id | sequence | fromid | fromname | fromavatar | fromkind ]
NB: Better to use "from_" instead "from"
you can update these fields directly in your queries.
Maybe so late but ...
You need create support class
#Entity
data class ReplyData(
#PrimaryKey val id: String,
#ColumnInfo(name = "sequence") val sequence: Int?,
#Embedded(prefix = "from") val from: FromItem <- here
)
#Parcelize
data class FromItem(val item: From)
data class From(
val id: String? = "",
val name: String? = "",
val avatar: String? = "",
val kind: String? = ""
)
and update from
#Query("Update ReplyData set fromitem = :item where id = :id")
fun update(id: Long, item: From)
P.S:
I didn't check this code, maybe it has some errors

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.

Moshi and room - mapping relationships

I have that Json that I would like to map with Moshi and store with Room
{
"name": "My Group",
"members": [
{
"id": "119075",
"invitedUser": {
"id": 97375,
"email": "xxx#gmail.com"
},
"inviting_user": {
"id": 323915,
"email": "yyy#gmail.com"
}
},
{
"id": "395387",
"invitedUser": {
"id": 323915,
"email": "aaa#gmail.com"
},
"inviting_user": {
"id": 323915,
"email": "bbb",
}
}
]
}
I prepared my models
#Entity(tableName = "groups")
data class Group(
#PrimaryKey
val id: Long,
val members: List<Member>
)
#Entity(tableName = "members")
data class Member(
#PrimaryKey
val id: Long,
#Json(name = "invited_user")
#ColumnInfo(name = "invited_user")
val invitedUser: User,
#Json(name = "inviting_user")
#ColumnInfo(name = "inviting_user")
val invitingUser: User
)
#Entity(tableName = "users")
data class User(
#PrimaryKey
val id: Int,
val email: String
)
And currently, I have error: Cannot figure out how to save this field into database.
I read this https://developer.android.com/training/data-storage/room/relationships. However, if I will model relationships like in documentation I don't know how to let Moshi map the relations? Have you found the simplest solution for that problem?
You have 2 options in my opinion.
You split the group and users in to individual tables and insert them separately.
You use TypeConverters to store the members as a field of group.
Your implementation is going to be dependent on your use-case.
Finally, I stored it by using TypeConverters
private val membersType = Types.newParameterizedType(List::class.java, Member::class.java)
private val membersAdapter = moshi.adapter<List<Member>>(membersType)
#TypeConverter
fun stringToMembers(string: String): List<Member> {
return membersAdapter.fromJson(string).orEmpty()
}
#TypeConverter
fun membersToString(members: List<Member>): String {
return membersAdapter.toJson(members)
}
And that are my models
#TypeConverters(Converters::class)
#Entity(tableName = "groups")
data class Group(
#PrimaryKey
val id: Long,
val name: String
) {
companion object {
data class Member(
val id: Long,
val invitedUser: User,
val invitingUser: User
)
data class User(
val id: Long,
val email: String
)
}
}
Does it look good for you?
Probably cleaner would be to have only ids and store somewhere else users, but I like that this solution is so simple.
You use TypeConverters to store the members as a field of group.
I believe this is the Implementation you need.
open class UserRequestConverter {
private val moshi = Moshi.Builder().build()
#TypeConverter
fun fromJson(string: String): User? {
if (TextUtils.isEmpty(string))
return null
val jsonAdapter = moshi.adapter(User::class.java)
return jsonAdapter.fromJson(string)
}
#TypeConverter
fun toJson(user: User): String {
val jsonAdapter = moshi.adapter(User::class.java)
return jsonAdapter.toJson(user)
}
}
#Entity(tableName = "members")
data class Member(
#PrimaryKey
val id: Long,
#Json(name = "invited_user")
#ColumnInfo(name = "invited_user")
#TypeConverters(UserRequestConverter::class)
val invitedUser: User,
#Json(name = "inviting_user")
#ColumnInfo(name = "inviting_user")
#TypeConverters(UserRequestConverter::class)
val invitingUser: User
)

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.

Room Persistence: Error:Entities and Pojos must have a usable public constructor

I'm converting a project to Kotlin and I'm trying to make my model (which is also my entity) a data class
I intend to use Moshi to convert the JSON responses from the API
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String,
var overview: String,
var poster_path: String,
var backdrop_path: String,
var release_date: String,
var vote_average: Double,
var isFavorite: Int
)
I can't build the app cause of the following 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).
Cannot find setter for field.
The examples I found are not far from this
Ideas on how to solve it?
It's not a problem in your case, but for others, this error can occur if you have #Ignore params in your primary constructor, i.e. Room expects to have either:
parameterless constructor or
constructor with all fields not marked with #Ignore
for example:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String,
#Ignore var overview: String)
will not work. This will:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String)
Had a similar issue before.
First I've updated/added apply plugin: 'kotlin-kapt' to gradle.
Next, I've used it instead of annotationProcessor in gradle:
kapt "android.arch.persistence.room:compiler:1.0.0-alpha4"
Tha last thing was to create an immutable data class:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
val id : Int,
val title: String,
val overview: String,
val poster_path: String,
val backdrop_path: String,
val release_date: String,
val vote_average: Double,
val isFavorite: Int
)
UPDATE:
This solution works when you have classes for the model and classes for Database in the same Android Module. If you have model classes in Android Library module and the rest of the code in your main module, Room will NOT recognize them.
I had the same issue. You can move the #Ignore fields to class body. For example :
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String
){
//here
#Ignore var overview: String
}
you need to specify a secondary constructor like so:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String,
var overview: String,
var poster_path: String,
var backdrop_path: String,
var release_date: String,
var vote_average: Double,
var isFavorite: Int
) {
constructor() : this(0, "", "", "", "", "", 0.0, 0)
}
To expand on the answers provided by #evanchooly and #daneejela, you need a secondary constructor to be able to use #Ignore parameters in your primary constructor. This is so Room still has a constructor that it can use when instantiating your object. Per your example, if we ignore one of the fields:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int,
var title: String,
var overview: String,
var poster_path: String,
var backdrop_path: String,
#Ignore var release_date: String,
#Ignore var vote_average: Double,
#Ignore var isFavorite: Int
) {
constructor(id: Int, title: String, overview: String, poster_path: String, backdrop_path: String)
: this(id, title, overview, poster_path, backdrop_path, "", 0.0, 0)
}
What worked for me:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int? = 0,
var title: String? = "",
var overview: String? = "",
var poster_path: String? = "",
var backdrop_path: String? = "",
var release_date: String? = "",
var vote_average: Double? = 0.0,
var isFavorite: Int? = 0
)
Kotlin allows long as a parameter name, but this won't work when room generates java code.
Today I was having this problem. I used #Ignore, that is why I got the error. To solve this I created a secondary constructor. So my code looks something like this:
#Entity(tableName = "profile")
data class Profile(
#field:SerializedName("id") #PrimaryKey #ColumnInfo(name = "id") var id:Long,
#field:SerializedName("foo") #ColumnInfo(name = "foo") var foo:String?,
#field:SerializedName("bar") #Ignore var Bar:String?
){
constructor(id:Long, foo:String) : this(id, foo, null)
}
This worked for me.
For me all I had to do was to add a constructor to the data class with empty params sent to it like so:
#Entity(tableName = "posts")
data class JobPost(
#Ignore
#SerializedName("companyLogo")
var companyLogo: String,
#Ignore
#SerializedName("companyName")
var companyName: String,
#Ignore
#SerializedName("isAggregated")
var isAggregated: String,
#PrimaryKey(autoGenerate = false)
#SerializedName("jobID")
var jobID: String,
#Ignore
#SerializedName("jobTitle")
var jobTitle: String,
#Ignore
#SerializedName("postedOn")
var postedOn: String,
#Ignore
#SerializedName("region")
var region: String
) {
constructor() : this("","","","","","","")
}
I also had this issue, but i realized the problem was that i added the #Embedded annotation to a property that already had a type converter, so anyone having the same problem should check the property declarations for your model class carefully and make sure the #Embedded annotation is not on a property that has a type converter associated with it.
I spent an hour trying to figure this out with no success. This is what I found. I forgot to add the return type in one of my Queries
this resulted with the POJO error
#Query("SELECT userNote FROM CardObject WHERE identifier = :identifier")
suspend fun getUserNote(identifier: String)
No POJO error
#Query("SELECT userNote FROM CardObject WHERE identifier = :identifier")
suspend fun getUserNote(identifier: String): String
I think that a good option for resolve it is:
#Entity(tableName = "movies")
data class MovieKt(
#PrimaryKey
var id : Int = 0,
var title: String = "",
var overview: String = "",
var poster_path: String = "",
var backdrop_path: String = "",
var release_date: String = "",
var vote_average: Double = 0.0,
var isFavorite: Int = 0
)
For me, I was using 'lat' & 'long' as a variable name in the data(Entity) class for kotlin so renaming to latitude & longitude it worked.
Not working:
#Entity(tableName = "table_User")
data class User(#PrimaryKey var userId : Int, #ColumnInfo(name = "first_name")
var firstName: String
, #ColumnInfo(name = "last_name") var lastName: String
, #ColumnInfo(name = "password") var password: String
, #ColumnInfo(name = "dob") var dob: Long
, #ColumnInfo(name = "address") var address: String
, #ColumnInfo(name = "lat") var latitude: Double
, #ColumnInfo(name = "long") var longitude: Double) {
}
Working:
#Entity(tableName = "table_User")
data class User(#PrimaryKey var userId : Int, #ColumnInfo(name = "first_name")
var firstName: String
, #ColumnInfo(name = "last_name") var lastName: String
, #ColumnInfo(name = "password") var password: String
, #ColumnInfo(name = "dob") var dob: Long
, #ColumnInfo(name = "address") var address: String
, #ColumnInfo(name = "latitude") var latitude: Double
, #ColumnInfo(name = "longitude") var longitude: Double) {
}
I had this problem with an entity (all fields were properly-initialized vars like a lot of the answers here are suggesting) that included a list of related, non-primitive items like the OP in this SO question had. For example:
#Entity(tableName = "fruits")
data class CachedFruitEntity(
#PrimaryKey var id: Long = 0L,
#Embedded(prefix = "buyer_") var buyerEntity: CachedBuyerEntity? = null
#TypeConverters(VendorsConverter::class)
var vendorEntities: List<CachedVendorEntity?> = listOf()))
That is, it has an embedded field, and it took me a while to realize that what I actually needed was a type converter for the vendor entity list instead (the compiler wasn't throwing the usual Error:(58, 31) error: Cannot figure out how to save this field into database. You can consider adding a type converter for it. So my solution was very similar to this answer
This google architecture components github thread has more info on this misleading error, but not sure if the issue has been fixed yet.
Like it's said in the Room docs, you are required to make an empty public constructor. At the same time, if you want to declare other custom constructors, you must add #Ignore annotation.
#Entity
public class CartItem {
#PrimaryKey
public int product_id;
public int qty;
public CartItem() {
}
#Ignore
public CartItem(int product_id, int count) {
this.product_id = product_id;
this.qty = count;
}
}
It turned out to be a bug on the library
https://github.com/googlesamples/android-architecture-components/issues/49
https://issuetracker.google.com/issues/62851733
i found this is #Relation's projection bug!
not Kotlin language problem.
based google GithubBrowserSample java also happend error, but different error message.
below is my kotlin code:
data class UserWithCommunities(
#Embedded
var user: User = User(0, null),
#Relation(parentColumn = "id",
entityColumn = "users_id",
entity = CommunityUsers::class,
projection = arrayOf("communities_id")) // delete this line.
var communityIds: List<Int> = emptyList()
)
right:
data class UserWithCommunities(
#Embedded
var user: User = User(0, null),
#Relation(parentColumn = "id",
entityColumn = "users_id",
entity = CommunityUsers::class)
var communityList: List<CommunityUsers> = emptyList()
)
Same bug, much stranger solution: Do not return cursor using reactivex Maybe<Cursor> on your Dao. Flowable, Single, and Observable did not work either.
Simply bite the bullet and make the reactive call outside the Dao request.
Before:
#Dao
interface MyDao{
#Query("SELECT * FROM mydao")
fun getCursorAll(): Flowable<Cursor>
}
After:
#Dao
interface MyDao{
#Query("SELECT * FROM mydao")
fun getCursorAll(): Cursor
}
Meta:
Android Studio 3.2
Build #AI-181.5540.7.32.5014246, built on September 17, 2018
JRE: 1.8.0_152-release-1136-b06 x86_64
JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
macOS 10.12.6
Just add the below annotation to any constructor that causes the errors and add a new blank constructor.
#Ignore
With 2.1.0-alpha6, it turned out to be an invalid return type in Dao. Fixing the return type as expected fixed it.
Kotlin plugin doesn't pick up annotationProcessor dependencies, So use the latest version of Kotlin annotation processor - put this line at top of your module's level build.gradle file
apply plugin: 'kotlin-kapt'
like
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' // add this line
android {
compileSdkVersion 28
defaultConfig {
........
}
}
Don't forget to change the compileSdkVersion accordingly.
I had the same problem and the reason was because the type of data I was getting by query in dao , was not equal to the type of data I was returning.
The type of id in my database was String and I changed the dao from:
#Query("SELECT id FROM content_table")
fun getIds(): Flow<List<Int>>
To :
#Query("SELECT id FROM content_table")
fun getIds(): Flow<List<String>>
For this issue, I had the same problem.
Replace the Room Dependencies with that of the latest one present in the official docs
As stated in Room Database Entity:
Each entity must either have a no-arg constructor or a constructor
whose parameters match fields (based on type and name).
So adding an empty constructor and annotating your parameterized constructor with #Ignore will solve your problem. An example:
public class POJO {
long id;
String firstName;
#Ignore
String lastName;
public POJO() {
}
#Ignore
public POJO(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// getters and setters
// ...
}
make sure room database column name and field name in constructor are same
In my case I had the #Ignore Tags and 'kotlin-kapt' plugin but still this started to happen after updating to kotlin to version 1.5.0.
I ended up updating my room library from version 2.2.5 to 2.3.0 and the problem was fixed.
another problem with
#Entity
data class SomeEnity(
#PrimaryKey(autoGenerate = true)
val id: Long = 0,
val filed: SomeClass
)
**inline** class SomeClass
consider to remove inline class
If u use Java. Then my solution was to only ADD #Nonull in the constructor
constructor(#Nonull String,
I've been having this error for the longest time. And so I want to give tips to those who are facing the same problem, it may help you.
Add all these dependencies or choose the one you will be using : https://developer.android.com/jetpack/androidx/releases/lifecycle
Make sure while creating your Class (in my case Public Class Message) it implements Serializable (example)
Avoid naming your attr with capital in front, it will be hard for the DAO_impl to detect. If you want to then make sure the getter and setter is also capitalized.
In my case I was using datatype name long as a field name
#Entity(tableName = "MyLocations")
data class MyLocationModel(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
var name: String,
val stored: Boolean,
val lat: Double,
val long: Double
)
just changed long to lon worked for me
#Entity(tableName = "MyLocations")
data class MyLocationModel(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
var name: String,
val stored: Boolean,
val lat: Double,
val lon: Double
)

Categories

Resources