Select single column value as livedata - android

As the title indicates I'm trying to select a single column from my database as livedata. But I'm getting the following error message.
error: Not sure how to convert a Cursor to this method's return type (androidx.lifecycle.LiveData<java.lang.Float>)
I'm tracking the phones location and insert location objects into a table called LocationEntity.
My entity looks as the following
data class LocationEntity(
...
val speed: Float,
...
#PrimaryKey(autoGenerate = true) val id: Long = 0
)
My DAO looks as the following
private const val ACCURACY_THRESHOLD = 50
private const val speedSql = """
SELECT speed
FROM LocationEntity
WHERE runId = :runId AND accuracy < $ACCURACY_THRESHOLD
ORDER BY dateTime
DESC LIMIT 1
"""
#Dao
interface LocationDao {
...
#Query(speedSql)
suspend fun speed(runId: Long): LiveData<Float>
}
Any clue about what I'm doing wrong?

suspend and LiveData doesn't work together. The below works.
#Dao
interface LocationDao {
...
#Query(speedSql)
fun speed(runId: Long): LiveData<Float>
}

Actually I think LiveData works out of the box, there's no reason to use Coroutines when returning LiveData.
When using LiveData it already handles it on a background thread. When NOT using LiveData then in that case you can use Coroutines (and maybe eventually Coroutines Channels) or RxJava etc.
You may find something about insert data by using livedata in google codelab
The most interesting part is the code below
#Dao
interface WordDao {
#Query("SELECT * from word_table ORDER BY word ASC")
fun getAllWords(): LiveData<List<Word>>
#Insert
suspend fun insert(word: Word)
#Query("DELETE FROM word_table")
fun deleteAll()
}
class WordRepository(private val wordDao: WordDao) {
val allWords: LiveData<List<Word>> = wordDao.getAllWords()
#WorkerThread
suspend fun insert(word: Word) {
wordDao.insert(word)
}
}

Related

DAO when to use suspend function android

I am following DAO tutorial on Android developers here:
https://developer.android.com/codelabs/android-room-with-a-view-kotlin#5
They say:
By default, all queries must be executed on a separate thread.
Room has Kotlin coroutines support. This allows your queries to be annotated with the suspend modifier and then called from a coroutine or from another suspension function.
Dao interface is as follows:
#Dao
interface WordDao {
#Query("SELECT * FROM word_table ORDER BY word ASC")
fun getAlphabetizedWords(): List<Word>
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(word: Word)
#Query("DELETE FROM word_table")
suspend fun deleteAll()
}
Why getAlphabetizedWords() is not defined as suspend function?
In coroutines, a flow is a type that can emit multiple values sequentially, as opposed to suspend functions that return only a single value. For example, you can use a flow to receive live updates from a database.
#Dao
interface WordDao {
// The flow always holds/caches latest version of data. Notifies its observers when the
// data has changed.
#Query("SELECT * FROM word_table ORDER BY word ASC")
fun getAlphabetizedWords(): Flow<List<Word>>
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(word: Word)
#Query("DELETE FROM word_table")
suspend fun deleteAll()
}
you can see source code in Github.

Kotlin: Room DB using SUM query in non-activity class/calculations

I'm relatively new to Kotlin and I'm working on a project for school. I've gotten stuck on something I can't figure out for a couple days now, either because I'm not just understanding how it works or I just don't know what to search for. I'm building an app for simple budget tracking, and using Room DB to allow the user to enter and store their expenses. I've gotten most of the app built and working, and have the DB, a DAO, a Repository and a ViewModel. I've successfully written a Query that returns the sum through a LiveData<Double>. I've managed to get this sum value to display through both a Toast message and in a TextView in the MainActivity (but the TV doesn't update on load, only after launching the activity for modifying the DB entries for the first time).
If it's possible, I want to be able to take this sum and store it inside a separate class I've written for calculation functions, and have it update whenever a user enters or deletes something from the DB. Or preferably, have the non-activity class call this sum whenever the class's relevant functions are called. I don't seem to understand how to get this value from anywhere but the MainActivity. Everything I've searched and read has sections of code which I think I understand, such as observeForever which require an application parameter, or they're over my head because it's just code snippets which I can't wrap my head around how they fit together.
Here is what I have so far:
My Entity:
#Entity(tableName = "expenses")
data class Expenses (
#PrimaryKey(autoGenerate = true)
val id: Int,
val expDesc: String,
val expAmount: Double
)
My DAO:
#Dao
interface Dao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addData(expenses: Expenses)
#Query("SELECT * FROM expenses ORDER BY id ASC")
fun readAllData(): LiveData<List<Expenses>>
#Query("SELECT SUM(expAmount) as expenseSum FROM expenses")
fun getExpenseSum(): LiveData<Double>
#Update
suspend fun updateExpense(expenses: Expenses)
#Delete
suspend fun deleteData(expenses: Expenses)
#Query("DELETE FROM expenses")
suspend fun deleteAllData()
}
My Database:
#Database(entities = [Expenses::class], version = 1, exportSchema = false)
abstract class Database:RoomDatabase() {
abstract fun dao(): Dao
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): Dao?
}
companion object{
#Volatile
private var INSTANCE: com.example.finalproject.roomDB.Database? = null
fun getDatabase(context: Context): com.example.finalproject.roomDB.Database {
val instance = INSTANCE
if(instance != null){
return instance
}
synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
com.example.finalproject.roomDB.Database::class.java,
"expenses").build()
INSTANCE = instance
return instance
}
}
}
}
My Repository:
class Repository(private val dao: Dao) {
val readAllData: LiveData<List<Expenses>> = dao.readAllData()
val getExpenseSum: LiveData<Double> = dao.getExpenseSum()
suspend fun addData(expenses: Expenses){
dao.addData(expenses)
}
suspend fun updateData(expenses: Expenses){
dao.updateExpense(expenses)
}
suspend fun deleteData(expenses: Expenses){
dao.deleteData(expenses)
}
suspend fun deleteAllData(){
dao.deleteAllData()
}
}
My ViewModel:
class ViewModel(application: Application): AndroidViewModel(application) {
val readAllData: LiveData<List<Expenses>>
val getExpenseSum: LiveData<Double>
private val repository: Repository
init{
val dao = Database.getDatabase(application).dao()
repository = Repository(dao)
readAllData = repository.readAllData
getExpenseSum = repository.getExpenseSum
}
fun addData(expenses: Expenses){
viewModelScope.launch(Dispatchers.IO) {
repository.addData(expenses)
}
}
fun updateData(expenses: Expenses){
viewModelScope.launch(Dispatchers.IO) { repository.updateData(expenses) }
}
fun deleteData(expenses: Expenses){
viewModelScope.launch(Dispatchers.IO) { repository.deleteData(expenses) }
}
fun deleteAllData(){
viewModelScope.launch(Dispatchers.IO) { repository.deleteAllData() }
}
}
My currently relevant part of the MainActivity:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: ViewModel
var sumTotal: Double = 0.0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val expenseViewButton = findViewById<Button>(R.id.expViewButton)
val incomeViewButton = findViewById<Button>(R.id.incViewButton)
val goalsViewButton = findViewById<Button>(R.id.goalsViewButton)
val expectedExpAmtView = findViewById<TextView>(R.id.expectedExpAmt)
viewModel = ViewModelProvider.AndroidViewModelFactory(application).create(ViewModel::class.java)
//This observer successfully casts the LiveData<Double> to a Double and updates whenever changed
val sumObserver = Observer<Double> { expSumDbl -> sumTotal = expSumDbl }
viewModel.getExpenseSum.observe(this, sumObserver)
expectedExpAmtView.text = getString(R.string.monthly_expected_ExpAmt, sumTotal.toString())
expenseViewButton.setOnClickListener{
val myIntent: Intent = Intent(this, ExpenseActivity::class.java)
startActivity(myIntent)
}
incomeViewButton.setOnClickListener{
val myIntent: Intent = Intent(this, IncomeActivity::class.java)
startActivity(myIntent)
}
//This successfully displays the correct sum whenever the button is pressed
goalsViewButton.setOnClickListener{
Toast.makeText(this#MainActivity, sumTotal.toString(), Toast.LENGTH_SHORT).show()
}
}
}
So sumTotal is the value from MainActivity that I'd like to get in a different non-activity class* for calculations that won't affect the DB at all, only text views. I'd also like the TextView that is being updated to always be up-to-date, including when the app launches. If anyone can help me figure out what I am doing wrong and/or need to do differently I'd really appreciate it.
*Specifically at the moment, I have a budget class which handles getting things like income, entered into editText fields, and calculating how much that translates into on a monthly basis. I'd like to take the sum from the DB entries and subtract it from whatever that total becomes in the budget class and return the result. I might want to do other (undecided) things later with the sum, which is why I want to store it in a variable.
The value of sumTotal in MainActivity actually comes from Repository.getExpenseSum, so instead of sharing that variable to another class, it might be easier to call the repository method again from the other class. Actually, I wouldn't even recommend having that variable sumTotal in your Activity, it's better to rely on ViewModel only, but that's a different topic.
You interface Dao contains the method:
fun getExpenseSum(): LiveData<Double>
which returns a LiveData. If you want to observe a LiveData, you need to have a LifecycleOwner so the LiveData knows when it should start and stop emitting updates (or you can observeForever, but you need to know yourself when to stop observing it).
In your budget class, assuming it's not a Fragment/Activity, you won't have a LifecycleOwner, that's why one suggestion for you issue is to create another method in Dao:
fun getExpenseSum(): Double
Notice the lack of LiveData. That method will return a double whenever you call it, synchronously, but it needs to be executed on a background thread. You can call that method in your budget class and get that value there.
Lastly, I don't think you should be calling those DB methods in some "regular" classes, you should pass those variables when creating an instance of the budget class. It's much easier to deal with LiveData/background thread when you're on the standard Android classes, and just pass the values to other classes that need it, instead of making them query the repository themselves.

Obtain entity using coroutines api

What is the best way to use coroutines with LiveData for selecting some data from database using Room.
This is My Dao class with suspended selection
#Dao
interface UserDao {
#Query("SELECT * from user_table WHERE id =:id")
suspend fun getUser(id: Long): User
}
Inside of View Model class I load user with viewModelScope.
Does it correct way to obtain user entity ?
fun load(userId: Long, block: (User?) -> Unit) = viewModelScope.launch {
block(dao.getUser(userId))
}
According developer android mentioned
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
This chunk of code does not work
Your Room must return LiveData.
Use instead:
#Dao
interface UserDao {
#Query("SELECT * from user_table WHERE id =:id")
fun getUser(id: Long): LiveData<User>
}

Room: Conflicting Declarations

I would like to add value, date and details to the current pb. I am receiving an error 'conflicting declaration' in the database for pbInfo. How should I fix this error?
#Entity(tableName = "pb_table")
data class Pb(#PrimaryKey
val pb: String)
#Entity
data class PbInfo(#PrimaryKey
var value: Double,
var date: Int,
var details: String)
#Dao
interface PbInfoDao {
#Insert
fun update(vararg pbInfo: PbInfo): LongArray
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.pbDao(), database.pbInfo())
}
}
}
suspend fun populateDatabase(pbDao: PbDao, pbInfoDao: PbInfoDao) {
pbDao.deleteAll()
var pb = Pb("Squat")
pbDao.insert(pb)
var pbInfo = PbInfo(122.5, 28, "I was feeling on top form today!")
First of all, you have two Entities in a single class (possibly the conflict)
So, add separate class for separate Entity.
Then, in your RoomDatabase abstract class, add two Entity Classes like this (and also create separate Dao interface classes):
#Database(entities = [(Pb::class), (Pbinfo::class)] ,version = 2)
abstract class YourRoomDatabaseClass: RoomDatabase(){
...
abstract fun pbDao() : PbDao
abstract fun pbinfoDao(): PbinfoDao
...
}
This should solve the conflicting of Entity classes. I have a single database with two Entities just like this and running without any problems. (Please mind me because I don't know Kotlin Syntax)
Use this
#Insert(onConflict = OnConflictStrategy.REPLACE)
instead of
#Insert

How return completable on room transaction Android

I need your help please.
I have dao interface that save some configurations:
#Dao interface ConfigDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(config: Config)
#Update(onConflict = OnConflictStrategy.REPLACE)
fun update(config: Config)
#Query("select * from T_CONFIG where isSelected = :isSelected")
fun getConfig(isSelected: Boolean): Single<Config>
#Query("select * from t_config")
fun getConfigAll(): LiveData<MutableList<Config>>
#Query("update T_CONFIG set isSelected = :isSelected where idEnvironment = :id")
fun updateConfigById(id: String, isSelected: Boolean):Completable
#Transaction
fun updateConfigTransaction(configSelected: Config){
if (configSelected.idEnvironment == Environtment.Type.PRD.toString()){
updateConfigById(Environtment.Type.PRD.toString(), false)
updateConfigById(Environtment.Type.DEV.toString(), true)
}else{
updateConfigById(Environtment.Type.PRD.toString(), true)
updateConfigById(Environtment.Type.DEV.toString(), false)
}
}
}
I need to know when the transaction is complete with success or error.
I tried to implement Completable from io.reactivex but it's not possible.
Since Room 2.1.0
Additional Async Support: DAO methods annotated with #Insert, #Delete or #Update, along with #Query containing INSERT, DELETE or UPDATE statements, now support Rx return types Completable, Single, Maybe, and Guava's return type ListenableFuture, and they can also be suspend functions.
Source: https://developer.android.com/jetpack/androidx/releases/room#2.1.0
Older versions
Change the interface to an abstract class. You'll have to prefix all methods without implementation with abstract. Then:
abstract class ConfigDao(private val db: MyDatabase) {
private val scheduler = Schedulers.from(db.queryExecutor)
// Make sure the method is open so Room can generate the transaction handling code.
#Transaction
open fun updateConfigTransaction(configSelected: Config){
// ...
}
fun updateConfigTransactionAsync(configSelected: config): Completable {
return Completable
.fromAction { updateConfigTransaction(config) }
.subscribeOn(scheduler)
}
}
subscribeOn(db.queryExecutor) makes sure the query runs on the same thread as all other DAO methods returning RxJava types. Replace MyDatabase constructor parameter with whatever your database class is.

Categories

Resources