how to query a room database by row android kotlin - android

I am new to Room and it's throwing me through a loop. I am trying to take a specific row of a specific data class in my Room database and compile into a list and turn this list into a String so I can do stuff with it. I can sort of do this by printing the contents of the Room database table to the console, but that's it.
I'll show you my code that I have
this is my data class.
code.kt
#Parcelize
#Entity(tableName = "code_table")
data class code(
#PrimaryKey(autoGenerate = true)
val id: Int,
val code: String //this is the item I want to isolate
): Parcelable
a snippet of my ViewModel.kt that I use.
val readAllData: LiveData<List<code>>
private val repository: respository
init {
val Dao = database.getDatabase(application).dao()
repository = respository(Dao)
readAllData = repository.readAllData
}
a snippet of my repository.kt that I use.
val readAllData: LiveData<List<code>> = dao.readAllData()
a snippet of my Dao.kt that I use.
#Query("SELECT * FROM code_table ORDER BY id ASC")
fun readAllData(): LiveData<List<code>>
How I read the Room database table
private fun dbread(){
mUserViewModel = ViewModelProvider(this).get(ViewModel::class.java)
mUserViewModel.readAllData.observe(viewLifecycleOwner, Observer { user ->
var abc = user
println(abc)
})
}
this is what it outputs
I/System.out: [code(id=2, code=textA), code(id=3, code=textB), code(id=4, code=textC)]
I am not hell-bent on only querying the code row but I need to aggregate all the data in the code row into a string or JSON array. so, in this case, that would be the textA, textB and textC (I'm worried I haven't made myself clear)
I have also tried doing the following in the Dao
#Query("SELECT code FROM code_table")
fun readdb(): LiveData<List<code>>//I've tried this with lot different types within the Parentheses
this makes the build fail: it says the following
:app:kaptDebugKotlin 1 error
java.lang.reflect.InvocationTargetException (no error message)
I would guesstimate that this error is because the SQL syntax is off but I don't think it is.
I have also tried messing around with the ViewModel and repository to see if I can get them to only output just the code row but to no avail. I'm surprised I'm the first to post about this.
thank you for your time.

You can obtain you desidered result just by using SQL.
In Android (and with Room) you're using a SQLite database, so the most important thing is writing some SQL that is compliant with SQLite.
Then you can select the concatenation of all the values in your table, just by asking to the database to do it. You can achieve it with this:
#Query("SELECT GROUP_CONCAT(code) FROM code_table")
fun readConcatenatedCode(): LiveData<String>
The returned value will be a LiveData of String, and the String will contain all the values, concatenated.

Related

Kotlin Room SUM operation

I need to perform column sum operation
But I don't understand what exactly I need to insert into fun getSumItem(): Flow<Cursor>
this is my Dao
#Dao
interface Dao {
#Insert
fun InsertItem(item: Income)
#Query("SELECT SUM(sum) AS value FROM income")
fun getSumItem(): Flow<Cursor>
}
this is my table
#Entity(tableName = "income")
data class Income(
#PrimaryKey(autoGenerate = true)
var id: Int? = null,
#ColumnInfo(name = "date")
var date: String,
#ColumnInfo(name = "type")
var type: String,
#ColumnInfo(name = "sum")
var sum: Float,
)
I did not find a similar example and any detailed information on the Internet.
when I use Cursor i get this 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). - android.database.Cursor
all other options that I found on the Internet also gave errors
When you say SELECT SUM(sum) AS value FROM income the output that Room handles will be a Cursor with a single column named value. Room does not know how to extract this value and build a Cursor object from that single value.
Room expects to extract data from the underlying Cursor that is generated by the SQLite API, which Room is a wrapper to/around, and deliver the data according to the object that the function returns (and thus must be able to ascertain how to build the resultant object).
As Room extracts a single value then you simply need to retrieve the value returned as a Float.
fun getSumItem(): Flow<Float>

Why would this query work in the Database Inspector section of the App Inspection in Android Studio, but not within a Room Query?

I have a table within my local SQLite database, the Class for it is as follows:
#Entity(tableName = "table_bp_reading")
class BPReading(
var systolicValue: Int = 120,
var diastolicValue: Int = 80,
var pulseValue: Int = 72,
var timeStamp: String = getDateTimeStamp(),
#PrimaryKey(autoGenerate = true) var pId: Int = 0
) {
...
The relevant part of the Dao looks as such:
...
#Query("SELECT * FROM table_bp_reading WHERE timeStamp BETWEEN :startDate AND :endDate")
fun getReadingsByDateRange(startDate: String, endDate: String): Flow<List<BPReading>>
...
The database inspector displays a table with
the following entries.
Running the following query within the Database Inspector:
SELECT * FROM table_bp_reading WHERE timeStamp BETWEEN '2021-09-18 00:00:00' AND '2021-09-18 23:59:59'
Gives the following result.
However, when I try to observe the data using the following:
bpReadingViewModel.bpReadingsByDate("2021-09-18 00:00:00", "2021-09-18 23:59:59").observe(viewLifecycleOwner, {
bpReading ->
bpReading.let {
bpReadingsByDate = it
if(it.isNotEmpty()) bindDBDataToScatterChart()
}
})
No data is displayed on my end, the query results in an empty list.
Note, however, that if instead of using the full timestamp with time, I just use a date string such as "2021-09-18":
bpReadingViewModel.bpReadingsByDate("2021-09-18", "2021-09-18").observe(viewLifecycleOwner, {
bpReading ->
bpReading.let {
bpReadingsByDate = it
if(it.isNotEmpty()) bindDBDataToScatterChart()
}
})
And I also change the query from the Dao given previously to the following:
#Query("SELECT * FROM table_bp_reading WHERE DATE(timeStamp) BETWEEN :startDate AND :endDate")
fun getReadingsByDateRange(startDate: String, endDate: String): Flow<List<BPReading>>
It works and it returns the 3 entries for that day as it is meant to. However, once I change the date range in the above, to filter between 2021-09-18 and 2021-09-19, it stops working again.
Please help, I am so confused by what is going on here.
EDIT:
Here's the relevant call from Repository:
fun readingsByDateRange(s: String, e: String): Flow<List<BPReading>> =
bpReadingDao.getReadingsByDateRange(e, s)
And the relevant call from the ViewModel for the class:
fun bpReadingsByDate(s: String, e: String): LiveData<List<BPReading>> =
repository.readingsByDateRange(s, e).asLiveData()
Which is why it is indeed bpReadingsByDate, and not getReadingsByDateRane. Sorry, this is my mistake for not being consistent with naming the function across files.
The query is fine (tested), from your comments and edited question, you have swapped the start and end timestamps which will normally result in nothing being selected.
So you should change bpReadingDao.getReadingsByDateRange(e, s)to be bpReadingDao.getReadingsByDateRange(s, e).

Android Room - best way to retrieve a record to be updated

I have an app which has a session end routine. I want to update the session with the end date/time and potentially other information when the End Session button is clicked. I have a dao, a repository, and a ViewModel.
I thought the best way to do this would be to get the record, update the fields in the object, and save the object back to Room.
I don't exactly know the best way to go about this. Here is the code I am working with:
In the Dao:
#Query("SELECT * FROM Session WHERE id=:id")
Single<Session> getSessionById(int id);
In the repository:
public Single<Session> getSessionById(int sessionId) {
return sessionDao.getSessionById(sessionId);
}
In the ViewModel:
public void endSession () {
Single<Session> session = sessionRepository.getSessionById(sessionId);
//????? session.doOnSuccess()
//get current date/time
Date date = Calendar.getInstance().getTime();
//set the end date
session.setEndTime(date);
//update the session
sessionRepository.update(session);
}
I would love any advice on the range of options. I had started using a plain object, but got errors related to running the query on the main thread and wanted to avoid that. I don't need an observable/flowable object. I understand Async is to be deprecated in Android 11. So I thought Single would work.
Any advice on my choice to use Single or code would be really helpful.
UPDATE:
I just need a simple example in Java of pulling a single record from and the main part is the functionality in the ViewModel (what does the doOnSuccess look like and optionally on error as well).
If you just want to update without retrieving the whole record, writing a custom query is the option that I go with:
#Query("UPDATE table_name SET column_name = :value WHERE id = :id")
fun update(id: Int, value: String): Completable
In repository:
fun update(id: Int, value: String): Completable
In ViewModel or UseCase:
update().subscribeOn(...).observeOn(...).subscribe()
If you want to avoid using RxJava:
#Query("UPDATE table_name SET column_name = value WHERE id = :id")
fun update(id: Int, value: String): Boolean
And use Executors to run transactions on a background thread.
Executors.newSingleThreadExecutor().execute {
repository.update()
}
If you want to perform both retrieving and updating you can use #Update and #Query to retrieve the recorded inside your ViewModel or UseCase (interactor) and push the update toward Room.
RxJava Single Example:
#Query("SELECT * FROM table_name")
fun getAll(): Single<List<Model>>
Repository:
fun getAll(): Single<List<Model>> = modelDao.getAll()
ViewModel or UseCase:
val statusLiveData = MutableLive<Strig>()
modelRepository.getAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ statusLiveData.value = "Success" }, { statusLiveData.value = "Failed" })

Room does not update livedata of list from a subset

I have a database that has a table with multiple columns, and I wish to only load 3 when showing my list and before the user selects one item to show it.
In order to do this I have created the following dao
#Dao
interface ReportDao {
#Query("SELECT id, name, description, created FROM reports ORDER BY created DESC")
fun loadShortReports(): LiveData<List<ReportItemDO>>
#Insert(onConflict = OnConflictStrategy.FAIL)
fun saveReports(reports: List<ReportDO>): Completable
}
this is my report entity:
#Entity(tableName = "reports")
data class ReportDO(
#PrimaryKey val id: UUID
, val name: String
, val description: String
.......)
and this is the minified entity for the list
data class ReportItemDO(
override val id: UUID
, override val name: String
, override val description: String
, #ColumnInfo(name = "created") override val date: Date)
: ReportItemVO
(the ReportItemVO interface is what the list expects)
When the application loads, everything loads correctly
but in order to test this I created a simple button that adds a random ReportDO item to the database using this code:
repository.addReport(
report
).subscribeOn(schedulers.subscribeScheduler)
.observeOn(schedulers.observeScheduler)
.subscribe {
Log.i(MainViewModel.TAG, "success")
}
which in turn calls
reportDao.saveReports(listof(report))
but the list does not seem to get updated automatically
When I close and restart the application the list is updated
I am assuming this is because I'm only taking a subset of the entity so Room does not know it should trigger the update
Is there a way to make Room understand that the LiveData it created was updated and trigger the loading process again?
Also , because I wish to add a refresh button, is there a way to send Room a message to manually reload the data?

Android room inserting with a query

I know inserting data into SQlite room library can be done through #Insert annotation, but I (out of curiosity) tried inserting values through SQL statement but Android studio showed me error stating column names can't be resolved. Is it a way of forcing developer to use #Insert annotation or if I am doing something wrong here? Thanks!
Please review following screenshot -
If you omit the column list that is being highlighted and thus provide values for all columns e.g. :-
#Query("INSERT INTO TestTable VALUES(null,:a,:b)")
List<TestTable> getall(String a, String b, String columna, String columnb);
Android Studio doesn't complain but then you get a compiler error of :-
error: INSERT query type is not supported yet. You can use:SELECT, DELETE, UPDATE
So perhaps better sooner than later.
#Entity
data class ModulesRoom(
#PrimaryKey(autoGenerate = false)
var id: Int = 0,
var nav: String = "",
var name: String = "",
var imageurl: String = ""
)
for List Object
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveEmployees(modules: List<ModulesRoom>)
for single obbjet
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveEmployees(modules: ModulesRoom)
Ussage
val mModules = Modules()
mModules.id = values
mModules.nav = values
saveEmployees(mModules)
What I am doing is calling getOpenHelper().getWritableDatabase() on the RoomDatabase.
Then you get the SupportSQLiteDatabase object that is essentially the non-Room way.
From that you can call execSQL() or use insert() with table name and ContentValues.

Categories

Resources