How to store Strings that can be queried in Room? - android

I want to store Strings (uids) in my Room DB. I want these uids to be able to be:
Queried (check to see if a uid exists in my list of strings)
Added to (add a uid to the list of strings)
What is the most efficient way to achieve this?
Here is my skeleton to give you some context, though my understanding of Room is very basic so there will be mistakes:
#Entity(tableName = "user_data")
data class UserData(
#PrimaryKey(autoGenerate = true) val uid: Int,
#ColumnInfo(name = "matched_users") var matchedUsers: Set<String>
)
#Dao
interface UserDataDao {
// Check to see if the uid is in matchedUsers Set/List
#Query("SELECT * FROM user_data WHERE :matchId IN matched_users")
fun matchedBefore(matchId: String): Boolean
// Add uid to Set/List
#Insert(onConflict = OnConflictStrategy.ABORT)
fun addMatchUid(uid: String)
}
Any suggestions appreciated.

According to your context, it seems you want to build one-to-many relationship, to do so you can follow the bellow codes. If not let me know what exactly you want.
#Entity(tableName = "user_data")
data class UserData(
#PrimaryKey(autoGenerate = true) val uid: Int,
#ColumnInfo(name = "name") var name: String
)
#Entity(tableName = "user_matcher")
data class UserMatcher(
#PrimaryKey(autoGenerate = true) val uid: Int,
#ColumnInfo(name = "userId") var userId: Int
)
#Dao
interface UserMatcherDao {
// Check to see if the uid is in matchedUsers Set/List
#Query("SELECT m.id as mid, u.id as uid, u.name FROM user_matcher m INNER JOIN user_data u ON m.userId = u.id WHERE m.id = :matchId")
fun getMatchedUsers(matchId: Int): List<UserData>
}

Related

ROOM database entity modeling

There must be a better way of doing this. I want to create a database table with all my clothing and have subcategories of clothing, like outerwear, dresses, shoes, etc. They all will have the same attributes (Id, name, image, about, price). Couldn't I create one table? I believe this is a One-to-Many relationship.
#Serializable
#Entity(tableName = CLOTHING_DATABASE_TABLE)
data class Clothing(
#PrimaryKey(autoGenerate = false)
val id: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = POPULAR_DATABASE_TABLE)
data class Popular(
#PrimaryKey(autoGenerate = false)
val popularId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = OUTERWEAR_DATABASE_TABLE)
data class Outerwear(
#PrimaryKey(autoGenerate = false)
val outerwearId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = TOPS_DATABASE_TABLE)
data class Tops(
#PrimaryKey(autoGenerate = false)
val topsId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = SWIMWEAR_DATABASE_TABLE)
data class Swimwear(
#PrimaryKey(autoGenerate = false)
val swimwearId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = SHOES_DATABASE_TABLE)
data class Shoes(
#PrimaryKey(autoGenerate = false)
val shoesId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = BUNDLES_DATABASE_TABLE)
data class Bundles(
#PrimaryKey(autoGenerate = false)
val bundlesId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = DRESSES_DATABASE_TABLE)
data class Dresses(
#PrimaryKey(autoGenerate = false)
val dressesId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = PAJAMAS_DATABASE_TABLE)
data class Pajamas(
#PrimaryKey(autoGenerate = false)
val pajamasId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
#Serializable
#Entity(tableName = ACCESSORIES_DATABASE_TABLE)
data class Accessories(
#PrimaryKey(autoGenerate = false)
val accessoriesId: Int,
val name: String,
val image: String,
val about: String,
val price: String
)
You would typically have either 2 or 3 tables (3 for a many-many i.e. an item of clothing could have multiple sub-categories).
For one-many have a clothing table which has a column for the sub-category that references(relates) to the single sub-category and a sub-category table that is referenced according to a unique column (the primary key).
For the many-many you have the clothing table (without the column to reference the single sub-category), the sub-category table and then a third table that has two columns, one for the reference to the clothing and the other for the reference to the sub-category with the primary key being a composite of both.
So you could have:-
#Entity(tableName = CLOTHING_DATABASE_TABLE)
data class Clothing(
#PrimaryKey(autoGenerate = false)
val id: Long, /* should really be Long as */
val subCategoryReference: Long, /*<<<<< ADDED for the 1 subcategory */
val name: String,
val image: String,
val about: String,
val price: String
)
and :-
#Entity(tableName = SUBCATEGORY_DATABASE_TABLE)
data class SubCategory(
#PrimaryKey
val id: Long?,
val subCategoryName: String
)
to enforce referential integrity you could add a foreign key constraint to the subCategoryReference column of the clothing table.
If you wanted a many-many, allowing a clothing to have multiple sub-categories then you could have the third table as :-
#Entity(
tableName = CLOTHING_SUBCATEGORY_MAP_DATABASE_TABLE,
primaryKeys = ["clothingMap","subcategoryMap"],
)
data class ClothingSubCategoryMap(
val clothingMap: Long,
#ColumnInfo(index = true)
val subcategoryMap: Long
)
Of course you could have a single clothing table and just have a column for the sub-category. However this would be considered as not being normalised as you would be duplicating the sub-category throughout.
Example 1-many (i.e. using the 2 tables Clothing and SubCategory)
As you would very likely want to retrieve clothing along with it's sub-category then you would have a POJO that uses the #Embedded and #Relation annotations e.g.
data class ClothingWithSingleSubCategory (
#Embedded
val clothing: Clothing,
#Relation(
entity = SubCategory::class,
parentColumn = "subCategoryReference",
entityColumn = "id"
)
val subCategory: SubCategory
)
You could then have the following as an #Dao annotated class :-
#Dao
interface AllDao {
#Insert(onConflict = IGNORE)
fun insert(clothing: Clothing): Long
#Insert(onConflict = IGNORE)
fun insert(subCategory: SubCategory): Long
#Transaction
#Query("SELECT * FROM clothing")
fun getAllClothingWithSubCategory(): List<ClothingWithSingleSubCategory>
}
With a suitable #Database annotated class you could then have something like the following in an activity:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
val sc_popular = dao.insert(SubCategory(null,"Popular"))
val sc_outerwear = dao.insert(SubCategory(null,"OuterWear"))
val sc_tops = dao.insert(SubCategory(null,"Tops"))
val sc_swimwear = dao.insert(SubCategory(100,"Swimwear"))
val sc_shoes = dao.insert(SubCategory(null,"Shoes"))
val sc_dresses = dao.insert(SubCategory(null,"Dresses"))
val sc_pyjamas = dao.insert(SubCategory(null,"Pyjamas"))
dao.insert(Clothing(100200300400,sc_popular,"Jeans","jeans_image","blah","100.55"))
dao.insert(Clothing(100200300500,sc_outerwear,"Anorak","anorak_image","blah","214.55"))
for (cwsc: ClothingWithSingleSubCategory in dao.getAllClothingWithSubCategory()) {
Log.d("DBINFO","Name = ${cwsc.clothing.name} Price is ${cwsc.clothing.price} Sub-Category is ${cwsc.subCategory.subCategoryName}")
}
}
}
When run the log would then include:-
D/DBINFO: Name = Jeans Price is 100.55 Sub-Category is Popular
D/DBINFO: Name = Anorak Price is 214.55 Sub-Category is OuterWear
Example many-many
Like the 1-many you will want a POJO BUT one that has a List of sub-categories obtained via the mapping table. This uses the #Embeded annotation and the #Relation annotation but extended to include the associateBy to inform Room about the intermediate table. So you could have:-
data class ClothingWithListOfSubCategories(
#Embedded
val clothing: Clothing,
#Relation(
entity = SubCategory::class,
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(
value = ClothingSubCategoryMap::class,
parentColumn = "clothingMap",
entityColumn = "subcategoryMap"
)
)
val subCategories: List<SubCategory>
)
The you could have the following in an #Dao annotated class:-
/* ADDED for many-many */
#Insert(onConflict = IGNORE)
fun insert(clothingSubCategoryMap: ClothingSubCategoryMap): Long
#Transaction
#Query("SELECT * FROM clothing")
fun getAllClothingWithSubCategories(): List<ClothingWithListOfSubCategories>
and if the activity were extended to include :-
/* Added for many-many */
/* Note utilises clothing and sub-categories above */
dao.insert(ClothingSubCategoryMap(jeans,sc_popular))
dao.insert(ClothingSubCategoryMap(jeans,sc_swimwear))
dao.insert(ClothingSubCategoryMap(jeans,sc_shoes))
dao.insert(ClothingSubCategoryMap(anorak,sc_popular))
dao.insert(ClothingSubCategoryMap(anorak,sc_outerwear))
for(cwlsc: ClothingWithListOfSubCategories in dao.getAllClothingWithSubCategories()) {
Log.d("DBINFO","Name = ${cwlsc.clothing.name} Price is ${cwlsc.clothing.price} it is in ${cwlsc.subCategories.size} sub-categories. They are:-")
for(sc: SubCategory in cwlsc.subCategories) {
Log.d("DBINFO","\t${sc.subCategoryName}")
}
}
The the log would also include :-
D/DBINFO: Name = Jeans Price is 100.55 it is in 3 sub-categories. They are:-
D/DBINFO: Popular
D/DBINFO: Swimwear
D/DBINFO: Shoes
D/DBINFO: Name = Anorak Price is 214.55 it is in 2 sub-categories. They are:-
D/DBINFO: Popular
D/DBINFO: OuterWear

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: Resolve foreign key in query

Given the following data model (i.e. one customer can have many orders and one order can have many shipments), how do I get a list of all orders with their associated customer and shipments for a certain order date?
In Kotlin, I'd like to retrieve a list of type List<OrderWithCustomerAndShipments> with OrderWithCustomerAndShipments being a POJO like this:
data class OrderWithCustomerAndShipments(
val order: Order,
val category: Category,
val shipments: List<Shipment>
)
Approach
So far, I've got a method that returns a List<OrderWithShipments>.
Note: For brevity, I omit #TypeConverter
, #ForeignKey, #ColumnInfo, etc.
#Dao
interface Dao {
#Transaction
#Query("SELECT * FROM orders WHERE orderDate = :date")
fun getOrdersWithShipments(date: Date): LiveData<List<OrderWithShipments>>
}
data class OrderWithShipments(
#Embedded
val order: Order
#Relation(parentColumn = "id", entityColumn = "orderId")
val shipments = List<Shipment>
)
#Entity
data class Customer(
#PrimaryKey val id: Int,
val customerName: String
)
#Entity
data class Order(
#PrimaryKey val id: Int,
val customerId: Int,
val orderDate: Date,
)
#Entity
data class Order(
#PrimaryKey val id: Int,
val orderId: Int,
val shipmentDate: Date,
)
One would assume that it is easier to resolve an order's foreign key to the parent customer than to get all child shipments. However, my attempts to get the parent customer haven't been successful so far.
Have you tried approach below? You could use several #Relation in Room
#Dao
interface Dao {
#Transaction
#Query("SELECT * FROM orders WHERE orderDate = :date")
fun getOrdersWithCustomerAndShipments(date: Date): LiveData<List<OrderWithCustomerAndShipments>>
}
data class OrderWithCustomerAndShipments(
#Embedded
val order: Order
#Relation(parentColumn = "customerId", entityColumn = "id")
val customer: Customer
#Relation(parentColumn = "id", entityColumn = "orderId")
val shipments: List<Shipment>
)

DAO: How to use the return value from Insert

According to the documentation an #Insert function can return a long, which is the new rowId for the inserted item. How can I use the return value?
#Dao
interface TodoDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(todo: TodoItem): Long
}
Just to note the id for my #Entity is autogenerated.
#PrimaryKey(autoGenerate = true)
Here is the whole TodoItem entity.
#Entity(tableName = "todos")
#Parcelize
data class TodoItem(val text: String, val priority: Priority) : Parcelable {
#PrimaryKey(autoGenerate = true)
var todoId: Long = 0
}
If id onTodoItemis avar, you could assign the return value toid`, so now your entity has its generated primary key, for use with future DAO operations.
If you are going to use #Parcelize, ensure that all essential properties are in the data class constructor. As it stands, your todoId property would not get put into the Parcel.
#Entity(tableName = "todos")
#Parcelize
data class TodoItem(
val text: String,
val priority: Priority,
#PrimaryKey(autoGenerate = true) var todoId: Long = 0
) : Parcelable
Then, given an entity named entity and a DAO named dao:
entity.todoId = dao.insert(entity)

Room Query not working

I'm trying to use Room with LiveData in my project. In my app, I have the authors table. The data is inserted fine but when I'm trying to read something from the table it did not give me records. I also see the database with SQLite Opener software. It shows me all the data.
Below is my Authors Entity.
#Entity(tableName = "authors")
data class AuthorModel(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id") val id: Long = 0,
#ColumnInfo(name = "name") var name: String
)
And this is my Author Dao interface.
#Dao
interface AuthorDao {
#Query(value = "SELECT * FROM authors")
fun allAuthors(): LiveData<List<AuthorModel>>
#Query(value = "SELECT * FROM authors WHERE id = :authorId")
fun authorWithId(authorId: Long): LiveData<AuthorModel?>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(author: AuthorModel): Long
}
And last, this is my RoomDatabase class.
#Database(entities = [AuthorModel::class], version = 1)
abstract class BookLibraryDatabase : RoomDatabase() {
abstract fun authorDao(): AuthorDao
}
Thank you for your time.
You have to observe LiveData. livedata.value will be null when you get first time.

Categories

Resources