Quiz app, categories have questions and questions have answers.
I have two queries in the DAO.
In the first one, I get all the categories from the data:
#Query("SELECT * from category_table ORDER BY category_id")
fun getAllCategories(): LiveData<List<Category>>
In the second one, I get a list of questions and answers by category id:
#Query("SELECT * FROM question_table WHERE parent_category_id = :categoryId ")
fun getQuestionsWithAnswersByCategoryId(categoryId: Long): LiveData<List<QuestionWithAnswers>>
Repository:
val getAllCategories: LiveData<List<Category>> = quizDao.getAllCategories()
fun getQuestionsWithAnswersByCategoryId(id: Long): LiveData<List<QuestionWithAnswers>> {
return quizDao.getQuestionWithAnswers(id)
}
This is my viewmodel:
val getAllCategories: LiveData<List<Category>>
var questionById: LiveData<List<QuestionWithAnswers>>
init {
getAllCategories = repository.getAllCategories
questionByCategoryId = repository.getQuestionWithAnswersByCategoryId(???????)
}
The problem is that I don't know the categories' id beforehand and need to obtain them from the database.
when I try to obtain a category id in the viewmodel like this:
var categoryId = getAllCategories.value[0].categoryId
it returns null.
Is there any way to obtain categories id which wrapped in livedata inside a viewmodel?
Related
I have an list of strings:
val mylist = listOf("cat","flower")
and a table that has a string typed column named question
I can write the query to find questions that are exactly matched with one of list items:
#Query("SELECT * FROM objects WHERE question IN (:mylist)")
List<Object> queryObjects(List<String> mylist);
But in fact the question column data is not of single word type, but string. I need to find results that every one of the list items are in that strings .for example the record : is this a cat
The use of IN is basically an = test of the expression on the the left of the IN clause against the list of values on the right. That is only exact matches are considered.
However, what you want is multiple LIKE's with wild characters, and an OR between each LIKE e.g question LIKE '%cat%' OR question LIKE '%flower%' or perhaps CASE WHEN THEN ELSE END or perhaps a recursive common table expression (CTE).
The former two (LIKEs or CASEs) would probably have to be done via an #RawQuery where the LIKE/CASE clauses are built at run time.
The Recursive CTE option would basically build a list of words (but could get further complicated if, anything other than spaces, such as punctuation marks were included.)
Another option could be to consider Full Text Search (FTS). You may wish to refer to https://www.raywenderlich.com/14292824-full-text-search-in-room-tutorial-getting-started
Working Example LIKE's
Here's an example of implementing the simplest, multiple LIKEs clauses separated with ORs:-
Objects (the Entity):-
#Entity
data class Objects(
#PrimaryKey
val id: Long? = null,
val question: String
)
AllDAO (the Daos):-
#Dao
interface AllDAO {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(objects: Objects)
#RawQuery
fun getObjectsRawQuery(query: SupportSQLiteQuery): List<Objects>
fun getObjects(values: List<String>): List<Objects> {
var i = 0
val sb = StringBuilder().append("SELECT * FROM objects WHERE ")
for(v in values) {
if (i++ > 0) {
sb.append(" OR ")
}
sb.append(" question LIKE '%${v}%'")
}
sb.append(";")
return getObjectsRawQuery(SimpleSQLiteQuery(sb.toString()))
}
}
TheDatabase (not uses .allowMainThreadQueries for convenience and brevity):-
#Database(entities = [Objects::class], version = 1, exportSchema = false)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDAO(): AllDAO
companion object {
var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
}
Putting it all together, loading some test data and running some extracts:-
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()
dao.insert(Objects(question = "This is a cat."))
dao.insert(Objects(question = "This is a flower."))
dao.insert(Objects(question = "this is nothing."))
dao.insert(Objects(question = "The quick brown fox jumped over the lazy dog"))
logObjects(dao.getObjects(listOf("cat","dog")),"Extract1\t")
logObjects(dao.getObjects(listOf("flower","cat")),"Extract2\t")
logObjects(dao.getObjects(listOf("brown","nothing")),"Extract3\t")
}
fun logObjects(objects: List<Objects>,prefix: String) {
for (o in objects) {
Log.d("OBJECTINFO","$prefix Question is ${o.question} ID is ${o.id}")
}
}
}
Result
2022-04-18 04:58:05.471 D/OBJECTINFO: Extract1 Question is This is a cat. ID is 1
2022-04-18 04:58:05.471 D/OBJECTINFO: Extract1 Question is The quick brown fox jumped over the lazy dog ID is 4
2022-04-18 04:58:05.473 D/OBJECTINFO: Extract2 Question is This is a cat. ID is 1
2022-04-18 04:58:05.473 D/OBJECTINFO: Extract2 Question is This is a flower. ID is 2
2022-04-18 04:58:05.474 D/OBJECTINFO: Extract3 Question is this is nothing. ID is 3
2022-04-18 04:58:05.474 D/OBJECTINFO: Extract3 Question is The quick brown fox jumped over the lazy dog ID is 4
Note in the above no consideration has been given to handling an empty list (a failure would occur due to the syntax error of SELECT * FROM objects WHERE ;). That is the example is just intended to demonstrate the basic principle.
Helloo,
----DAO---
#Transaction
#Query("select * from expenses where date= :date")
suspend fun getExpenseWithTagByDate(date: LocalDate): Flow<List<ExpenseWithTag>>
I have a dao code like below. BUT if I need query two days or more, How can I do it?
fun getEnpenseWithTagByMonth(dates: List<LocalDate>) =
flow {
val lists = arrayListOf<Flow<List<ExpenseWithTag>>>()
dates.forEach {
val expenses = expenseDao.getExpenseWithTagByDate(it)
lists.add(expenses)
}
emit(lists)
}.flowOn(Dispatchers.IO)
then
getEnpenseWithTagByMonth(dates).asLiveData()
finally i get a type, and it's very terrible:
ArrayList<Flow<List<ExpenseWithTag>>>>>
How I can write the code with Concise and efficient
To answer your question
if I need query two days or more, How can I do it?
You could use an in query, and then group them.
#Query("select * from expenses where date IN (:dates)")
suspend fun getExpenseWithTagByDate(dates: List<LocalDate>): Flow<List<ExpenseWithTag>>
fun getEnpenseWithTagByMonth(dates: List<LocalDate>): Flow<List<List<ExpenseWithTag>>> {
return expenseDao.getExpenseWithTagByDate(dates).map { expenses ->
expenses.groupBy{ expense -> expense.date }.values
}
}
I am trying to get the user ID from the newest user. How can I make the insert method spit the ID when the ID is autogenerated?
in Model
#PrimaryKey(autoGenerate = true)
val userId: Int
in Dao
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun addUserWithLong(user: User): LiveData<Long>
in Repository
fun addUserWitLong(user: User): LiveData<Long> {
return userDao.addUserWithLong(user)
}
in ViewModel
fun addUserWithLong(user: User): LiveData<Long> {
return repository.addUserWitLong(user)
}
in Fragment
val id: Long? = userViewModel.addUserWithLong(user).value
I have read in the docs that #Insert returns Long as the row ID but I do not know how to program it. Now the error is "Not sure how handle insert method return type." Is there some way to make with LiveData and not with Rxjava. That is without the need to download more dependecies.
As per the documentation here
If the #Insert method receives a single parameter, it can return a
long value, which is the new rowId for the inserted item. If the
parameter is an array or a collection, then the method should return
an array or a collection of long values instead, with each value as
the rowId for one of the inserted items. To learn more about returning
rowId values, see the reference documentation for the #Insert
annotation, as well as the SQLite documentation for rowid tables
So you can use it like
#Insert(onConflict = OnConflictStrategy.REPLACE)
long addUserWithLong(user: User)
or if you are inserting a list
#Insert(onConflict = OnConflictStrategy.REPLACE)
long[] addUserWithLong(user: List<User>)
Edit-1
After checking answers from this post.
No, you can't. I wrote an answer to the issue. The reason is, that
LiveData is used to notify for changes. Insert, Update, Delete won't
trigger a change.
I just created a test project and successfully received Id of last inserted item in activity. Here is my implementation.
Dao
#Insert
suspend fun addUser(user: Users): Long
Repo
suspend fun insertUser(context: Context, users: Users): Long {
val db = AppDatabase.getInstance(context)
val dao = db.userDao()
return dao.addUser(users)
}
ViewModel
fun addUser(context: Context, users: Users) = liveData {
//you can also emit your customized object here.
emit("Inserting...")
try {
val userRepo = UsersRepo()
val response = userRepo.insertUser(context, users)
emit(response)
} catch (e: Exception) {
e.printStackTrace()
emit(e.message)
}
}
Activity
viewModel.addUser(applicationContext, user).observe(this, Observer { userId ->
Log.d("MainActivity", "Inserted User Id is $userId")
})
Check test application here.
I am practicing my android skills (beginner) by coding a grocery list app. I have two tables in my db, a shopping_item table (The items I want to buy) and a reference_item table (The items I know the category and the unit price). Each time I add a shopping item, there is an refId field referencing to the reference item id corresponding. It is a default value to a default reference item if the shopping item is not referenced yet.
I use a MVVM model. I then have a DAO, a repository, a viewModel and my fragments that display data.
When I add a new shopping item, I want to know if there is a corresponding reference item. I want to do the following Query:
#Query(value = "SELECT refId FROM reference_items WHERE reference_item_name = :refName")
suspend fun getRefItem(refName : String) : Int
It returns the id of the reference item corresponding as an Int or is null if it is not referenced yet. In my repository, I have a function like that:
suspend fun getRefItem(refName : String) = db.getShoppingDao().getRefItem(refName)
For now, I think I am doing alright. No mistake in sight I guess.
The problem begin when I try to implement my viewModel. What should I do? What about my fragment?
I have a addNewItem(name: String, amount: Int) function in my fragment to add the new item. I can find the reference item corresponding with the name provided.
I tried multiple things, using LiveData, suspend functions, mutableLiveData/LiveData, but I am getting lost right now. Every tutorials or examples use LiveData or Query all data from the db. I just want one Integer, one Time, no need of LiveData I think.
here is the complete solution. Hope this is useful for you.
DAO
#Query(value = "SELECT refId FROM reference_items WHERE reference_item_name = :refName")
suspend fun getRefItem(refName : String) : Int
Repository
// Specify return datatype as Int
suspend fun getRefItem(refName : String): Int = db.getShoppingDao().getRefItem(refName)
ViewModel
fun getRefItem(name: String): LiveData<Int> {
val result : MutableLiveData<Int>() <-- setup livedata to return as value
viewModelScope.lanuch {
result.postValue(repository.getRefItem(name))
}
return result <-- return livedata
}
Fragment
fun addNewItem(name: String, amount: Int) {
// setup viewModel observer
viewModel.getRefItem(name).observer { viewLifecycleOwner, { value ->
// GET YOUR INT VALUE HERE
Log.i("VALUE", value)
}
}
}
So, I'm using Room database to store courses and I'm stuck on the method that returns the course with the name(course) that I want because it's always returning null. I have diminished my database to have 2 courses with the course variable as:
As you can see in the picture above, when I try to get the CourseEnt in the Repository with course = fun, which I can see below that it exists, it returns a LiveData with a null value instead of the CourseEnt that I wanted.
Any idea on what I'm doing wrong or on what should I look into with debugger?
Here's the code:
Entity:
#Entity(tableName = "courses_table")
data class CoursesEnt (#PrimaryKey val course: String,
val location: String,
val description: String,
val difficulty: Double,
val distance: Double,
val photos: ListInt,
val category: String,
val activities: ListString)//ListString is a type converter that converts a String into a List<String> and vice-versa
DAO:
#Dao
interface CoursesDao {
#Query("SELECT * from courses_table ORDER BY course ASC")
fun getAllCourses(): LiveData<List<CoursesEnt>>
#Query("SELECT * FROM courses_table WHERE course LIKE :str")
fun getCourse(str: String):LiveData<CoursesEnt>
...
}
Repository:
class CoursesRepository(private val coursesDao: CoursesDao){
val allCourses: LiveData<List<CoursesEnt>> = coursesDao.getAllCourses()
var singleCourse: LiveData<CoursesEnt> = coursesDao.getCourse("")
#WorkerThread
fun getCourse(str: String) {
singleCourse = coursesDao.getCourse(str)
}
...
}
Read documentation, liveData will always return null for straight call, you have to observe LiveData, the result value from Room will be in block. This is some example of usage
mViewModel.getAllUsers().observe( this#YourActivity, Observer {
// it - is all users from DB
})
but if you will call
val users = mViewModel.getAllUsers()
the result will be null