One to many relationship in Room Database - android

There is an event in my project and there are timelines in this event. Each event has its own timelines. I want to list the timelines of each event with the codes I have written, but I cannot access it via the adapter.
My entities;
Event:
#Parcelize
#Entity(tableName = "event_table")
data class Event(
#PrimaryKey(autoGenerate = true)
val eventId: Int,
val eventName: String,
val eventCategory: String
): Parcelable
Timeline:
#Parcelize
#Entity(tableName = "timeline_table")
data class Timeline(
#PrimaryKey(autoGenerate = true)
val TimeLineId: Int,
val TimeLineAccomplish: String,
val TimeLineDetails: String,
val TimeLineDate: String,
val eventCreatorId: Int
): Parcelable
EventAndTimeline:
data class EventAndTimeline(
#Embedded val event: Event,
#Relation(
parentColumn = "eventId",
entityColumn = "eventCreatorId"
)
val timeline: List<Timeline>
)
Database:
#Database(entities = [Timeline::class, Event::class], version = 1, exportSchema = false)
abstract class TimeLineDatabase: RoomDatabase() {
abstract fun timelineDao(): TimelineDao
abstract fun eventDao(): EventDao
companion object{
#Volatile
private var INSTANCE: TimeLineDatabase? = null
fun getDatabase(context: Context): TimeLineDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
TimeLineDatabase::class.java,
"timeline_database"
)
.build()
INSTANCE = instance
return instance
}
}
}
}
Dao:
#Dao
interface TimelineDao{
#Transaction
#Query("SELECT * FROM event_table")
fun getEventsWithTimelines(): LiveData<List<EventAndTimeline>>
}
Repository:
class TimelineRepository(private val timelineDao: TimelineDao) {
val readAllData: LiveData<List<EventAndTimeline>> = timelineDao.getEventsWithTimelines()
}
ViewModel:
class TimeLineViewModel(application: Application): AndroidViewModel(application) {
val readAllData: LiveData<List<EventAndTimeline>>
private val timelineRepository: TimelineRepository
init {
val timelineDao = TimeLineDatabase.getDatabase(application).timelineDao()
timelineRepository = TimelineRepository(timelineDao)
readAllData = timelineRepository.readAllData
}
Finally: In my List adapter i want to list timelines of event. When i didnt have a relation i reach all timelines with:
holder.itemView.findViewById<TextView>(R.id.timelineDetails_txt).text
currentItem.TimeLineDetails
but now I can't see timelines variables(Like TimeLineDetails, TimeLineAccomplish etc.).

You need to write a query like the below example.
interface UserBookDao {
#Query(
"SELECT user.name AS userName, book.name AS bookName " +
"FROM user, book " +
"WHERE user.id = book.user_id"
)
fun loadUserAndBookNames(): LiveData<List<UserBook>>
// You can also define this class in a separate file.
data class UserBook(val userName: String?, val bookName: String?)
}
you can check more examples in below link.
https://developer.android.com/training/data-storage/room/relationships

Related

How to get specific value from room database by id?

I have a simple room database with LiveData, i want to get (cartPriceI: Double) values by id to use it within specific methods.
CartItemsDatabase.kt
#Parcelize
#Entity(tableName = "cart_table")
data class CartItemsDatabase(
#PrimaryKey(autoGenerate = true) var cid: Int,
#ColumnInfo(name = "titleD") var cartTitle: String?,
#ColumnInfo(name = "imageD") var cartImage: Bitmap?,
#ColumnInfo(name = "priceLD") var cartPriceL: Double?,
#ColumnInfo(name = "priceID") var cartPriceI: Double?,
#ColumnInfo(name = "itemNumD") var cartNum: Int?,
#ColumnInfo(name = "descriptionD") var cartDes: String?): Parcelable
CartItemsDAO.kt
#Dao
interface CartItemsDAO {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addItem(cartItemsDatabase: CartItemsDatabase)
#Update
suspend fun updateCart(cartItemsDatabase: CartItemsDatabase)
#Query("SELECT * FROM cart_table ORDER BY cid ASC")
fun readAllData(): LiveData<List<CartItemsDatabase>>
#Delete
fun delete(cartItems: CartItemsDatabase)
}
CartRepository.kt
class CartRepository(private val cartItemsDAO: CartItemsDAO) {
val readAllData: LiveData<List<CartItemsDatabase>> = cartItemsDAO.readAllData()
suspend fun addItem(cartItemsDatabase: CartItemsDatabase){
cartItemsDAO.addItem(cartItemsDatabase)
}
suspend fun updateCart(cartItemsDatabase: CartItemsDatabase){
cartItemsDAO.updateCart(cartItemsDatabase)
}
}
CartViewModel.kt
class CartViewModel(application: Application): AndroidViewModel(application) {
val readAllData: LiveData<List<CartItemsDatabase>>
private val repository: CartRepository
init {
val cartDao = AppDatabase.getDatabase(application).cartDao()
repository = CartRepository(cartDao)
readAllData = repository.readAllData
}
fun addItem(cartItemsDatabase: CartItemsDatabase){
viewModelScope.launch(Dispatchers.IO){
repository.addItem(cartItemsDatabase)
}
}
fun updateCart(cartItemsDatabase: CartItemsDatabase){
viewModelScope.launch(Dispatchers.IO) {
repository.updateCart(cartItemsDatabase)
}
}
}
AppDatabase.kt
#Database(entities = [CartItemsDatabase::class], version = 1, exportSchema = false)
#TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun cartDao(): CartItemsDAO
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
val tempInstance = INSTANCE
if (tempInstance != null){
return tempInstance
}
synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"cart_database"
).build()
INSTANCE = instance
return instance
}
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"cartItems_database"
)
.build()
}
}
}
I'm a beginner, i want to know if there is possible way to do that and retrieve specific values from database (ex. cartPriceI in Double) by id!
You can query the database by the given id and return it asynchronously either with a suspend fun or LiveData/Flow
In the CartItemsDAO:
#Query("SELECT cartPriceI FROM cart_table WHERE cid = :id")
suspend fun getPrice(id: Int): Double
#Query("SELECT cartPriceI FROM cart_table WHERE cid = :id")
fun getPrice(id: Int): LiveData<Double>
You can get a specific model by SELECT with WHERE statement
#Query("SELECT * FROM cart_table WHERE cid == : cid")
fun getCartItemsDatabase(cid: String): LiveData<CartItemsDatabase?>
Or you can Return a subset of a table's columns
#Query("SELECT priceID FROM cart_table WHERE cid == : cid")
fun getCartItemPrice(cid: String): LiveData<Double>

How can I add data into Room database in a manner where it is appended to the top row

Image for reference
I am facing a situation whenever I add a new article into the database it will add the new article to the bottom of the RecyclerView, how can I implement it in such a way that it updates the top row instead?
Is it something got to do with the Adapter or do I have to tweak the Doa?
Room Database Implementation
**//ViewModel**
fun saveArticle(article: Article) = viewModelScope.launch {
newsRepository.upsert(article)
}
**
//NewsRepository**
suspend fun upsert(article: Article) = db.getArticleDao().upsert(article)
**//Implementation of the Database**
#Database(
entities = [Article::class],
version = 1
)
#TypeConverters(Converters::class)
abstract class ArticleDatabase : RoomDatabase() {
abstract fun getArticleDao(): ArticleDao
companion object {
#Volatile
private var instance: ArticleDatabase? = null
private val LOCK = Any()
operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
instance ?: createDatabase(context).also { instance = it }
}
private fun createDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
ArticleDatabase::class.java,
"article_db.db"
).build()
}
}
**//DOA**
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(article: Article): Long //returns the id's of the article
**//Entity**
#Entity(
tableName = "articles", indices = [Index(value = ["url","title"], unique = true)]
)
#Parcelize
data class Article(
#PrimaryKey(autoGenerate = true)
var id: Int? =null,
val author: String?,
val description: String?,
val source: Source?,
val title: String?,
val url: String?,
val urlToImage: String?,
val publishedAt: String?,
val content: String?
): Parcelable {
override fun hashCode(): Int {
var result = id.hashCode()
if(url.isNullOrEmpty()){
result = 31 * result + url.hashCode()
}
return result
}
}
Expecting the top row to be updated instead of the bottom row, the Room Database docs did not talk much about changing the way the article is inserted, tried to reference other people's work, couldn't find the solution

processing room database information

ok so im quite new to using room, ive looked in a lot of places but find it hard to find my answer, alot of places ive looked around seem to have outdated information.
I have setup a room database, i have all the information for it set up as well as a viewmodel to send the infomration from. However i can not for the life of me figure out how to process the information in to android compose.
here is what i have done,any feedback would be good.
#Entity
data class Notes(
#PrimaryKey val uid: Int?,
#ColumnInfo(name = "header") val header: String?,
#ColumnInfo(name = "note") val note: String?
)
#Dao
interface NotesDao {
#Query("SELECT * FROM notes")
fun getAll(): LiveData<List<Notes>>
#Insert (onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(notes: Notes)
#Delete
suspend fun delete(notes: Notes)
}
#Database(entities = [Notes::class], version = 1)
abstract class NotesAppDatabase : RoomDatabase() {
abstract fun notesDao(): NotesDao
companion object {
#Volatile
private var INSTANCE: NotesAppDatabase? = null
fun getDatabase(context: Context): NotesAppDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
NotesAppDatabase::class.java,
"items_database"
)
// Wipes and rebuilds instead of migrating if no Migration object.
// Migration is not part of this codelab.
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
// return instance
instance
}
}
}
}
class NotesRepository (private val notesDao: NotesDao) {
val readAllData: LiveData<List<Notes>> = notesDao.getAll()
suspend fun addNote(note: Notes){
notesDao.insert(note)
}
}
class NotesRepoViewModel (application: Application): AndroidViewModel(application) {
private val repo: NotesRepository
private val allNotes: LiveData<List<Notes>>
init {
val dao = NotesAppDatabase.getDatabase(application).notesDao()
repo = NotesRepository(dao)
allNotes = repo.readAllData
}
fun notes(notes: Notes){
viewModelScope.launch {
repo.addNote(notes)
}
}
}

How to set data in Model?

I have one entity "drinks" which have [id;name;thumb] and I`m using these entities for 2 response calls. One response returns me a NonAlcohol list of drinks, another AlcoholList, I'm using Room for caching the data. But when I run the app, I saw that my lists merged, after some thought, I found a solution to this problem, I added one Boolean field to my entity "alcoholStatus".But I can't understand how to set the data into this variable correctly using this AccessDataStrategy. I'm new to Android, and this is my learning project. Please give me the right way how to solve this problem.
https://github.com/YaroslavSulyma/LetsDrink/tree/master/app/src/main/java/com/example/letsdrink
Thanks a lot!
Entity
#Entity(tableName = "drinks")
data class DrinksModel(
#SerializedName("strDrink")
val strDrink: String,
#SerializedName("strDrinkThumb")
val strDrinkThumb: String?,
#SerializedName("idDrink")
#PrimaryKey
val idDrink: Int,
var alcohol: Boolean
)
DataAccessStrategyCode
fun <T, A> performGetOperation(
databaseQuery: () -> LiveData<T>,
networkCall: suspend () -> Resource<A>,
saveCallResult: suspend (A) -> Unit
): LiveData<Resource<T>> =
liveData(Dispatchers.IO) {
emit(Resource.loading())
val source = databaseQuery.invoke().map { Resource.success(it) }
emitSource(source)
val responseStatus = networkCall.invoke()
if (responseStatus.status == SUCCESS) {
saveCallResult(responseStatus.data!!)
} else if (responseStatus.status == ERROR) {
emit(Resource.error(responseStatus.message!!))
emitSource(source)
}
}
Resource
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
enum class Status {
SUCCESS,
ERROR,
LOADING
}
companion object {
fun <T> success(data: T): Resource<T> {
return Resource(Status.SUCCESS, data, null)
}
fun <T> error(message: String, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, message)
}
fun <T> loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
Repository
class CocktailsRepository #Inject constructor(
private val remoteDataSource: CocktailsRemoteDataSource,
private val localDataSource: CocktailsDao
) {
fun getAlcoholicCocktails() = performGetOperation(
databaseQuery = { localDataSource.getAlcoholicCocktails() },
networkCall = { remoteDataSource.getAllAlcoholicCocktails()},
saveCallResult = { localDataSource.insertAllDrinks(it.drinks) }
)
fun getNonAlcoholicCocktails() = performGetOperation(
databaseQuery = { localDataSource.getNonAlcoholicCocktails() },
networkCall = { remoteDataSource.getAllNonAlcoholicCocktails() },
saveCallResult = { localDataSource.insertAllDrinks(it.drinks) }
)
}
DAO
#Dao
interface CocktailsDao {
#Query("SELECT * FROM drinks WHERE alcohol = 'true'")
fun getAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Query("SELECT * FROM drinks WHERE alcohol = 'false'")
fun getNonAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllDrinks(drinks: List<DrinksModel>)
}
RemoteDataSource
class CocktailsRemoteDataSource #Inject constructor(private val iCocktailApisService: ICocktailApisService) :
BaseDataSource() {
suspend fun getAllAlcoholicCocktails() =
getResult { iCocktailApisService.allAlcoholicAndNonAlcoholicCocktails("Alcoholic") }
suspend fun getAllNonAlcoholicCocktails() =
getResult { iCocktailApisService.allAlcoholicAndNonAlcoholicCocktails("Non_Alcoholic") }
}
First: I strongly recommend that you define separate data classes for your remote and local model classes and do the mapping between them when needed, for example:
Remote data model:
data class DrinkRemoteModel(
#SerializedName("idDrink")
val idDrink: Int,
#SerializedName("strDrink")
val strDrink: String,
#SerializedName("strDrinkThumb")
val strDrinkThumb: String?,
#SerializedName("alcohol")
var alcohol: Boolean
)
Local data model:
#Entity(tableName = "drinks")
data class DrinkLocalModel(
#PrimaryKey
#ColumnInfo(name = "idDrink")
val idDrink: Int,
#ColumnInfo(name = "strDrink")
val strDrink: String,
#ColumnInfo(name = "strDrinkThumb")
val strDrinkThumb: String?,
#ColumnInfo(name = "alcohol")
var alcohol: Boolean
)
Back to your implementation: I think what causing the problem is that Room maps Boolean fields in your entity to an integer column, 1 for true, and 0 for false, so try changing your querys in your DAO like following:
#Dao
interface CocktailsDao {
#Query("SELECT * FROM drinks WHERE alcohol = 1")
fun getAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Query("SELECT * FROM drinks WHERE alcohol = 0")
fun getNonAlcoholicCocktails(): LiveData<List<DrinksModel>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllDrinks(drinks: List<DrinksModel>)
}
Alternatively: you can substitute your getAlcoholicCocktails and getNonAlcoholicCocktails with one DAO function, like this:
#Query("SELECT * FROM drinks WHERE alcohol = :isAlcoholic")
fun getCocktails(isAlcoholic : Boolean = true): LiveData<List<DrinksModel>>

No record save in room database

After inserting data into RoomDB when I fetch it using mindValleyDao.getCategories().value It returns null
DatabaseClass
#Database(entities = arrayOf(CategoryBO::class), version = 1, exportSchema = false)
abstract class MindValleyDatabase : RoomDatabase(){
abstract fun mindValleyDao(): MindValleyDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
private var INSTANCE: MindValleyDatabase? = null
fun getDatabase(context: Context): MindValleyDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
MindValleyDatabase::class.java,
"mindvalley_database"
).allowMainThreadQueries()
.fallbackToDestructiveMigration().build()
INSTANCE = instance
return instance
}
}
}
}
CategoryBO.kt
#Entity(tableName = "CategoryEntity")
data class CategoryBO( #PrimaryKey(autoGenerate = true) val id:Int, val name:String)
Doa
#Dao
interface MindValleyDao {
#Query("SELECT * from CategoryEntity ORDER BY id ASC")
fun getCategories(): LiveData<List<CategoryBO>>
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(categoryBO: CategoryBO)
//suspend fun insert(categoryBO: CategoryBO)
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(categoryBOList: List<CategoryBO>)
}
I am testing it by inserting Category and fetching list of categories like
class MindValleyViewModelNew #Inject constructor() : BaseViewModel() {
var categoryList: MutableLiveData<List<CategoryBO>> = MutableLiveData()
private lateinit var mindValleyDao:MindValleyDao
fun loadDatabase(mContext:Context){
mindValleyDao = MindValleyDatabase.getDatabase(mContext).mindValleyDao()
GlobalScope.launch(Dispatchers.IO) {
mindValleyDao.insert(CategoryBO(0,"first item"))
val cats = mindValleyDao.getCategories().value
categoryList.postValue(cats)
}
}
}
mindValleyDao.getCategories() has return type is LiveData, that's why it query value async, you shouldn't call .value
LiveData type in Room should only use for observe,
If you want to get value, modify your code to fun getCategories(): List<CategoryBO> instead

Categories

Resources