I am trying to update partial fields in my Android Room database.
I am using #Update with a partial class:
#Update(entity = BehaviorDataSent::class)
fun update(obj: BehaviorDataSentUpdate)
#Entity(tableName = "BehaviorDataSent")
data class BehaviorDataSent(#PrimaryKey val actionTime: Long, val netName: String? = null, val savedTime: Long = 0, val sentTime: Long? = null)
data class BehaviorDataSentUpdate(val actionTime: Long, val sentTime: Long )
However, I get an error saying that the update method must have a body.
What have I missed?
It would appear that the #Update is in an abstract class rather than an interface. As such it needs to be an abstract function (or have a body).
so :-
#Update(entity = BehaviorDataSent::class)
abstract fun update(obj: BehaviorDataSentUpdate)
or change abstract class .... to interface
and/or you could use :-
#Query("UPDATE behaviordatasent SET sentTime=:sentTime WHERE actionTime=:actionTime")
abstract fun update(sentTime: Long, actionTime: Long)
in which case the BehaviourDataSentUpdate class is not required.
if in an interface then abstract should not be coded.
i.e. in an interface abstract is implied and thus does not need to be coded.However, in an abstract class functions without bodies need to be abstract and thus you can also have non-abstract functions that then need a body.
Related
In room, it seems to be impossible to use annotation based setups in a generic class with variable based data or with provided classes - the result is that there is no workaround to define queries with Flow inside an abstract generic base class.
Is that really try?
Examples 1 - CAN BE SOLVED
Define a query which contains a table name that is defined by a class variable
#Dao
abstract class BaseDao<Item : IItem, ItemWithRef> {
abstract val tableName: String
// DOES NOT WORK - because table name is not compile-time constant
#Transaction
#Query("select * from ${tableName}")
abstract suspend fun loadAll(): List<ItemWithRef>
// SOLUTION
private val rawQueryLoadAll
get() = "SELECT * FROM $tableName"
#Transaction
#RawQuery
protected abstract suspend fun loadAll(query: SimpleSQLiteQuery): List<ItemWithRef>
suspend fun loadAll(): List<ItemWithRef> = loadAll(queryLoadAll)
}
Examples 2 - CAN NOT BE SOLVED?
Define flow queries which contains a table name that is defined by a class variable
Here the problem is, that #RawQuery needs to know the queries classes - can this somehow be solved as well?
#Dao
abstract class BaseDao<Item : IItem, ItemWithRef> {
abstract val tableName: String
// all 3 possibilities DO NOT WORK
// - because `#RawQuery` needs to know that it handles `ItemWithRef::class`
// - because the table name is not constant
// DOES NOT WORK
#Transaction
#Query("select * from ${tableName}")
abstract suspend fun flowAll(): Flow<List<ItemWithRef>>
// DOES NOT WORK
#Transaction
#RawQuery
protected abstract fun flowAll(query: SimpleSQLiteQuery): Flow<List<ItemWithRef>>
fun flowAll(): Flow<List<ItemWithRef>> = flowAll(queryLoadAll)
// DOES NOT WORK
#Transaction
#RawQuery(observedEntities = arrayOf(ItemWithRef::class))
protected abstract fun flowAll(query: SimpleSQLiteQuery): Flow<List<ItemWithRef>>
fun flowAll(): Flow<List<ItemWithRef>> = flowAll(queryLoadAll)
}
Question
I'm fine with the workaround for example 1 but is there any workaround to also define a Flow raw query in a base class somehow?
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.
I would like to understand whether variables inside a constructor should be private or public in Kotlin.
What is the significance of having access to modifiers inside the class constructor?
In the below code snippet, the variable service and query are private.
What is the use of keeping them private?
How does it help?
class GithubPagingSource(
private val service: GithubService,
private val query: String
) : PagingSource<Int, Repo>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
TODO("Not yet implemented")
}
}
Note: I have read multiple questions and answers related to this area on Stack overflow but could not find any valid answer.
The thing to consider is that definition of constructors in kotlin is different than java. In your provided snippet, the class has a primary constructor. According to the kotlin docs:
The primary constructor is part of the class header: it goes after the class name (and optional type parameters).
For example:
class GithubPagingSource(
service: GithubService,
query: String
)
also:
Note that parameters of the primary constructor can be used in the initializer blocks. They can also be used in property initializers declared in the class body.
So, what should we do if we want to use them inside the body of a function?
In this case, we have to declare class properties by adding var or val to the parameters of the primary constructor:
class GithubPagingSource(
val service: GithubService,
val query: String
) : PagingSource<Int, Repo>() {
init {
println("$query") // query is accessible here
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
println("$query") // query also is accessible here
}
}
Now, service and query are playing two roles:
first as constructor parameters
second as class variables
On the other hand, the encapsulation principle tells us to keep class variables as much restricted as possible. It means that you should keep them private unless there is a need to be visible from outside.
class GithubPagingSource(
private val service: GithubService,
private val query: String
) : PagingSource<Int, Repo>() {
...
}
So, if we have a reference to an instance of this class:
val githubPagingSource = ...
val query = githubPagingSource.query // is not accessible here
I have a small query, I have been confused on how to insert an object into a different entity.
I tried a work around doing:
#Query("INSERT INTO card_table VALUES(:id, :pid, :question, :answer, :type, :completed)")
suspend fun insertCard(id: UUID, pid: UUID, question: String, answer: String, type: CardType, completed: Boolean)
However, this has not worked.
My question is, how do I insert an object into different entity tables with a single database.
Note -> The insert() query works for inserting decks into the deck_table. The issue correlates to the card_table as I am unsure on how to reference it.
Thanks
database
#Insert(onConflict = OnConflictStrategy.ABORT)
suspend fun insert(deck: Deck)
#Insert
suspend fun insertCard(card: FlashCard)
#Database(entities = [Deck::class, FlashCard::class], version = 8, exportSchema = false)
#TypeConverters(DeckTypeConverters::class)
abstract class FlashCardDB : RoomDatabase() {
abstract fun DeckDAO(): DeckDAO
companion object {
private var INSTANCE: FlashCardDB? = null
fun getDatabase(context: Context, scope: CoroutineScope): FlashCardDB {
val tmpInstance = INSTANCE
if (tmpInstance != null) return tmpInstance
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
FlashCardDB::class.java,
"flash_cards_database"
).build() //.fallbackToDestructiveMigration() will causes users to loose data during migrations (use only for testing purposes)
INSTANCE = instance
return instance
}
tables
#Entity(tableName = "deck_table")
#Parcelize
data class Deck(
#PrimaryKey #ColumnInfo(name = "id") val id: UUID = UUID.randomUUID(),
#ColumnInfo(name = "title") var title: String,
#ColumnInfo(name = "date") var date: Calendar,
#ColumnInfo(name = "completed") var completed: Boolean
) : Parcelable {
#Entity(tableName = "card_table")
#Parcelize
data class FlashCard(
#PrimaryKey #ColumnInfo(name = "id") val id: UUID = UUID.randomUUID(),
#ColumnInfo(name = "parent_id") var pid: UUID,
#ColumnInfo(name = "question") var question: String,
#ColumnInfo(name = "answer") var answer: String,
#ColumnInfo(name = "type") var type: CardType,
#ColumnInfo(name = "completed") var completed: Boolean)
: Parcelable {
}
My question is, how do I insert an object into different entity tables with a single database
To insert object you need to put method with #Insert in interface or abstract class with #Dao
When you build your project - Room will generate implementation of your #Dao-interface. It would be plain class, but written in Java (for DeckDAO interface generated class would be DeckDAO_Impl.java). Also Room will implement #Insert-method (since you tell in this method what object you want to insert and Room knows its corresponding table's name). Let's say (to be simple) that implementation of this method would be executing query insert into _tableName_ values (...).
#Insert
suspend fun insert(deck: Deck) <-- Room will generate implementation "insert into deck_table values (..."
#Insert
suspend fun insert(card: FlashCard) <-- Room will generate implementation "insert into card_table values (..."
To get access to this implemented class' methods you should put your dao-interface in abstract class that extends Database. After build Room will make for you implementation of this abstract class and you can invoke method insert in your example with just:
FlashCardDB.getDatabase().DeckDAO().insert(deck)
or
FlashCardDB.getDatabase().DeckDAO().insert(card)
It's not necessary to create separate dao-interface for each entity. In theory you could use single dao-interface with all insert/delete/update/query-methods. Then all you need is to get access to all these methods through single FlashCardDB.getDatabase().DeckDAO() endpoint. You can put there 10 insert-methods with the same insert name but they should differ with parameter's type (it's just a method overloading) or you could use different method's names for clarity.
Though in practice (and in most tutorials) insert-delete-update-query methods for different entities for convenience are placed in different dao-interfaces. To achieve that you should put all these dao-interfaces to Database class. Let's say:
abstract class FlashCardDB : RoomDatabase() {
abstract fun deckDAO(): DeckDAO
abstract fun cardDAO(): CardDAO
So decision is up to you.
If i'm getting your question correctly you want to insert the card data into the database right? What you need to do is have a DAO for each database model. So your Card database model will have its own DAO:
#Dao
public abstract class CardDao {
#Insert
fun insert(card: Card)
}
#Database(entities = [Deck::class, FlashCard::class], version = 8, exportSchema = false)
#TypeConverters(DeckTypeConverters::class)
abstract class FlashCardDB : RoomDatabase() {
abstract fun deckDao(): DeckDao
abstract fun cardDao(): CardDao
...
Kindly let me know if this answers your question.
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