I wanna create a menu about sorting by Task's attributes but don't know how because I am using database combined with Live Data, View Model and coroutine which is very complicated. Please show me how to handle these logics.
Data file
#Entity(tableName = "task_table")
data class Task(
//id is the PrimaryKey which is auto generated by Android Room Database
#PrimaryKey(autoGenerate = true)
val id: Int,
val date: String,
val content: String,
var timeLeft: String,
var isDone: Boolean
):Parcelable
Dao file
#Dao
interface TaskDao {
// means it will be just ignore if there is a new exactly same task then we're gonna just ignore it
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addTask(task: Task)
#Delete
suspend fun deleteTask(task: Task)
#Update
suspend fun updateTask(task: Task)
#Query("SELECT * FROM task_table order by timeLeft ASC")
fun readAllData(): LiveData<MutableList<Task>>
#Query("SELECT COUNT(id) FROM task_table")
fun getCount(): Int
}
Database file
#Database(entities = [Task::class], version = 1, exportSchema = false)
abstract class TaskDatabase : RoomDatabase() {
abstract fun taskDao(): TaskDao
companion object {
#Volatile
private var INSTANCE: TaskDatabase? = null
fun getDatabase(context: Context): TaskDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this)
{
val instance = Room.databaseBuilder(
context.applicationContext,
TaskDatabase::class.java,
"task_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
Repository file
// a repository class abstracts access to multiple data sources
class TaskRepository(private val taskDao: TaskDao) {
val readAllData: LiveData<MutableList<Task>> = taskDao.readAllData()
suspend fun addTask(task: Task) {
taskDao.addTask(task)
}
suspend fun deleteTask(task: Task) {
taskDao.deleteTask(task)
}
suspend fun updateTask(task: Task) {
taskDao.updateTask(task)
}
}
ViewModel file
class TaskViewModel(application: Application) : AndroidViewModel(application) {
val readAllData: LiveData<MutableList<Task>>
private val repository: TaskRepository
init {
val taskDao = TaskDatabase.getDatabase(application).taskDao()
repository = TaskRepository(taskDao)
readAllData = repository.readAllData
}
fun addTask(task: Task) {
viewModelScope.launch(Dispatchers.IO) {
repository.addTask(task)
}
}
fun updateTask(task: Task) {
viewModelScope.launch(Dispatchers.IO) {
repository.updateTask(task)
}
}
fun deleteTask(task: Task) {
viewModelScope.launch(Dispatchers.IO) {
repository.deleteTask(task)
}
}
}
In my fragment these will be a menu to choose how recyclerview items should be sorted. Don't know how to handle these logic. Please help. I appreciate!
Related
I have a recycler view implemented in MVVM architecture with room database. Created this query to sort recycler view in DAO file.
#Query("SELECT * FROM task_table ORDER BY " +
"CASE WHEN:choice =1 THEN date END ASC," +
"CASE WHEN:choice=2 THEN title END ASC")
fun readAllData(choice : Int): LiveData<MutableList<Task>>
Repository file
class TaskRepository(private val taskDao: TaskDao) {
val readAllData: LiveData<MutableList<Task>> = taskDao.readAllData(1)
suspend fun addTask(task: Task) {
taskDao.addTask(task)
}
suspend fun deleteTask(task: Task) {
taskDao.deleteTask(task)
}
suspend fun updateTask(task: Task) {
taskDao.updateTask(task)
}
fun deleteAllTasks() {
taskDao.deleteAllTasks()
}
}
ViewModel file
class TaskViewModel(
application: Application,
) : AndroidViewModel(application) {
val readAllData: LiveData<MutableList<Task>>
private val repository: TaskRepository
init {
val taskDao = TaskDatabase.getDatabase(application).taskDao()
repository = TaskRepository(taskDao)
readAllData = repository.readAllData
}
fun addTask(task: Task) {
viewModelScope.launch(Dispatchers.IO) {
repository.addTask(task)
}
}
fun updateTask(task: Task) {
viewModelScope.launch(Dispatchers.IO) {
repository.updateTask(task)
}
}
fun deleteTask(task: Task) {
viewModelScope.launch(Dispatchers.IO) {
repository.deleteTask(task)
}
}
fun deleteAllTask() {
viewModelScope.launch(Dispatchers.IO) {
repository.deleteAllTasks()
}
}
}
I don't know how to implement the sort menu in UI in order to make my room database sort in attributes. Which means I want to pass data from Fragment to Repository file so that DAO can take parameter.
First, you don't want to use MutableList with LiveData. If you would just add/remove from the value that is in the LiveData the underlying object is not changed and you won't get updates from it.
The most common approach(AFAIK) is to have LiveData of the thing that modifies your query. With combination of Transformations.switchMap you can use the output of one LiveData and translate it into another LiveData(your query).
class TaskViewModel(application: Application) : AndroidViewModel(application) {
private val taskDao: TaskDatabase.getDatabase(application).taskDao()
private val repository: TaskRepository = TaskRepository(taskDao)
private val _filter: MutableLiveData<Int> = MutableLiveData(1)
val filter: LiveData<Int>
get() = _filter
private val _data: MutableLiveData<List<Task>> = Transformations.switchMap(filter) { choice ->
repository.readAllData(choice)
}
val readAllData: LiveData<List<Task>>
get() = _data
fun updateFilter(filter: Int) {
_filter.postValue(filter)
}
}
Any change in _filter will trigger re-evaluaing via the switchMap which will get readAllData from your repository.
The following code is an MVVM architecture-based ROOM implementation in Kotlin for Android.
I have a problem with the pre-population of the database. My database doesn't have any data or I can't find the solution to get or insert data.
The IDE doesn't throw any compilation problems.
I think the problem is in the ViewModel but I'm at a dead end.
EDITED: I have seen the DB and it's populated. The insert is fine but not the data read. I will try to modify the ViewModel and its associated livedata and mutablelive data.
This is my data class:
#Entity(tableName = Constants.DATABASE_NAME)
data class Card(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = Constants.COLUMN_NAME_ID) val id: Int?,
#ColumnInfo(name = Constants.COLUMN_NAME_FAV) val fav: Boolean,
#ColumnInfo(name = Constants.COLUMN_NAME_DATE) val date: Date,
#ColumnInfo(name = Constants.COLUMN_NAME_RAW_VALUE) val rawValue: String,
#ColumnInfo(name = Constants.COLUMN_NAME_STORE_TYPE) val storeType: String,
#ColumnInfo(name = Constants.COLUMN_NAME_STORE_NAME) val storeName: String,
#ColumnInfo(name = Constants.COLUMN_NAME_STORE_NOTES) val storeNotes: String
)
This is my interface dao
#Insert
suspend fun insertNewCard(card: Card) {
}
#Delete
suspend fun deleteCard(card: Card)
#Query("SELECT * FROM ${Constants.DATABASE_NAME}")
fun getAllCards(): LiveData<List<Card>>
UPDATE:
This is my Database class
#Database(
entities = [Card::class],
version = 1,
exportSchema = true,
//autoMigrations = [AutoMigration (from = 1, to = 2)]
)
#TypeConverters(Converters::class)
abstract class CardsDatabase : RoomDatabase() {
abstract fun cardsDao(): CardsDao
private class CardsDatabaseCallback(private val scope: CoroutineScope)
: RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.cardsDao())
}
}
}
suspend fun populateDatabase(cardsDao: CardsDao) {
val testCard = Card(null,
false,
((Calendar.getInstance()).time),
"test",
"codebar test 1",
"Prueba tienda",
"Esta es una tienda de prueba"
)
val testCardTwo = Card(null,
false,
((Calendar.getInstance()).time),
"barcode test 2",
"codebar",
"Prueba tienda 2",
"Esta es una segunda prueba de tienda"
)
Log.d("ROOM", "$testCard")
cardsDao.insertNewCard(testCard)
}
}
companion object {
#Volatile
private var INSTANCE: CardsDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): CardsDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
CardsDatabase::class.java,
Constants.DATABASE_NAME)
.addCallback(CardsDatabaseCallback(scope))
.build()
INSTANCE = instance
instance
}
}
}
This is the repository class
val allCards: LiveData<List<Card>> = cardsDao.getAllCards()
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun insertCard(card: Card) {
cardsDao.insertNewCard(card)
}
suspend fun deleteCard(card: Card) {
cardsDao.deleteCard(card)
}
This is my viewModel:
The Logcat shows as null the LiveData at this point.
private val _allCards = MutableLiveData<List<Card>>()
val allCards : LiveData<List<Card>> = _allCards
init {
getAllCards()
Log.d("ROOM", "${allCards.value}")
}
private fun getAllCards() = viewModelScope.launch {
_allCards.value = cardRepository.allCards.value
Log.d("ROOM", "_ : ${_allCards.value}")
}
fun insertCard(card: Card) = viewModelScope.launch {
cardRepository.insertCard(card)
Log.d("ROOM", "inserted: $card")
}
}
class CardsViewModelFactory(private val repository: CardRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(CardsViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return CardsViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
This is the application class:
private val applicationScope = CoroutineScope(SupervisorJob())
private val database by lazy { CardsDatabase.getDatabase(this, applicationScope) }
val repository by lazy { CardRepository(database.cardsDao()) }
For last, the observe in MainActivity:
cardsViewModel.allCards.observe(this) { cards ->
cards?.let { adapter.setData(it) }
}
My version.
It works with classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21'
add to build.gradle:
plugins {
id 'kotlin-kapt'
}
dependencies {
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
}
create Entity.class:
#Entity(tableName = "cards")
data class CardEntity(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
val data: String
)
create Dao.class
#Dao
interface CardDAO {
#Insert
suspend fun insert(cardEntity: CardEntity): Long
#Delete
suspend fun delete(cardEntity: CardEntity): Int
#Update
suspend fun update(cardEntity: CardEntity): Int
#Query("SELECT * FROM cards WHERE id = :id")
fun getDataById(id:Int): Flow<CardEntity?>
#Query("SELECT * FROM cards")
fun getAll(): Flow<List<CardEntity>>
}
create Database.class:
for work you should to use singleton
#Database(
entities = [
CardEntity::class
],
version = 1
)
abstract class MyDatabase : RoomDatabase() {
companion object {
private var INSTANCE: MyDatabase? = null
fun get(context: Context): MyDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context, MyDatabase::class.java,
"testBase").build()
}
return INSTANCE as MyDatabase
}
}
abstract fun cardDAO(): CardDAO
}
test TestDatabase.class:
#RunWith(AndroidJUnit4::class)
class TestDatabase {
lateinit var db: MyDatabase
#Before
fun createDB() {
val appContext =
InstrumentationRegistry.getInstrumentation().targetContext
db = Room.inMemoryDatabaseBuilder(appContext,
MyDatabase::class.java).build()
}
#After
#Throws(IOException::class)
fun closeDB() {
db.close()
}
private fun setData() = runBlocking {
db.cardDAO().insert(CardEntity(data = "1"))
db.cardDAO().insert(CardEntity(data = "2"))
db.cardDAO().insert(CardEntity(data = "3"))
db.cardDAO().insert(CardEntity(data = "4"))
db.cardDAO().insert(CardEntity(data = "5"))
}
#Test
fun test() = runBlocking {
setData()
Assert.assertEquals(db.cardDAO().getAll().first().size, 5)
val flowIdFirst = db.cardDAO().getDataById(1)
Assert.assertEquals(flowIdFirst.first()?.data, "1")
Assert.assertEquals(db.cardDAO().getDataById(2).first()?.data, "2")
Assert.assertEquals(db.cardDAO().getDataById(3).first()?.data, "3")
Assert.assertEquals(db.cardDAO().getDataById(4).first()?.data, "4")
Assert.assertEquals(db.cardDAO().getDataById(5).first()?.data, "5")
Assert.assertTrue(db.cardDAO().update(CardEntity(1, "my new data")) > 0)
Assert.assertEquals(flowIdFirst.first()?.data, "my new data")
Assert.assertTrue(db.cardDAO().delete(CardEntity(1,"")) > 0)
Assert.assertEquals(flowIdFirst.first(), null)
}
}
The problem is the LiveData created in DAO. To fix it:
Remove LiveData from DAO to return a List:
#Query("SELECT * FROM ${Constants.DATABASE_NAME}")
suspend fun getAllCards(): List<Card>
From the repository, make a suspend fun that gets access to DAO and gets the List, and remove the LiveData type.
suspend fun getAllCards() : List<Card> = cardsDao.getAllCards()
Change the method to access the repository in ViewModel. It works fine.
private fun getAllCards() = viewModelScope.launch {
_allCards.value = cardRepository.getAllCards()
}
Solved.
I do not know how to implement a filter query properly inside the Repository and the ViewModel to use it to display the filtered string in a Textview or anything else really. My Entity, Dao, Repository, and ViewModel are as follows:
User.kt
#Entity(tableName = "user_data")
data class User (
#PrimaryKey(autoGenerate = true) val id: Int,
#ColumnInfo(name = "name") val name: String
)
UserDao.kt
#Dao
interface UserDao {
#Insert
fun addUser(user: User)
#Query("SELECT name FROM user_data WHERE name LIKE :filter LIMIT 1")
fun getFilteredUser(filter: String): LiveData<String>
}
UserRepository.kt
class UserRepository(private val userDao: UserDao) {
fun addUser(user: User) { userDao.addUser(User) }
fun getFilteredUser(filter: String) {userDao.getFilteredUser(filter)}
}
UserViewModel.kt
class UserViewModel(application: Application): AndroidViewModel(application) {
private val repository: UserRepository
init {
val userDao = UserDatabase.getDatabase(application).userDao()
repository = UserRepository(userDao )
}
fun addUser(user: User) {
viewModelScope.launch(Dispatchers.IO){
repository.addUser(user)
}
}
fun getFilteredUser(filter: String){
return repository.getFilteredUser(filter)
}
}
How would I proceed from here to make it possible to e.g. display the filtered User String in a textview or anything like that and how do I write the method correctly inside the repository and the viewmodel?
Thank you for your help!
Try the following
UserDao
change getFilteredUser as follows
#Query("SELECT name FROM user_data WHERE name LIKE '%' || :filter || '%' LIMIT 1")
fun getFilteredUser(filter: String): Stringl̥
UserRepo
use coroutines to perform the database I/O operations
suspend fun addUser(user: User) {
withContext(Dispatchers.IO) {
userDao.addUser(user)
}
}
suspend fun getFilteredUser(filter: String): String {
return withContext(Dispatchers.IO) {
userDao.getFilteredUser(filter)
}
}
ViewModel
fun addUser(user: User) {
viewModelScope.launch {
repository.addUser(user)
}
}
private val _dataToUi = MutableLiveData<String>()
val dataToUi: LiveData<String>
get() = _dataToUi
suspend fun getFilteredUser(filter: String): String? {
return withContext(Dispatchers.IO) {
repository.getFilteredUser(filter)
}
}
// to set the filterquery from the fragment/activity
fun setFliterQuery(query: String) {
viewModelScope.launch {
_dataToUi.value = getFilteredUser(query)
}
}
Activity
binding.button.setOnClickListener {
val queryKey = binding.queryKey.text.toString()
Log.i("activity", queryKey)
userViewModel.setFliterQuery(queryKey)
}
userViewModel.dataToUi.observe(this) { result ->
result?.apply {
Log.i("activity", result)
binding.resultText.text = result
}
}
I want to get callback when insert data successfully in Roomdb with coroutine
MyDao.kt
#Dao
interface MyDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(obj: Task): Long
}
TaskViewModel.kt
class TaskViewModel(var context:Context) : ViewModel{
private var appDao: AppDao
init {
val db = AppDatabase.getInstance(context)
appDao = db.appDao()
}
fun insertTask(tast: Task) {
GlobalScope.launch {
val mID = appDao.insert(task)
}
}
}
How to return mID from insertTask() method ?
Thanks you in advance
Wrap the call with the withContext and mark the function as suspend
suspend insertTask(task: Task) = withContext(Dispatchers.IO) { appDao.insertTask(task) }
And from your view:
fun saveTask(t: Task) = lifecycleScope.launch {
val id = viewModel.insertTask(t)
Toast.makeText(context, "Task $id has been inserted", Toast.LENGTH_SHORT).show()
}
You could also return a LiveData that triggers a callback when it's done:
fun insertTask(task: Task): LiveData<Long> {
val liveData = MutableLiveData<Long>()
viewModelScope.launch {
liveData.value = dao.insertTask(task)
}
return liveData
}
Further advice
Don't keep a reference to a context in your viewmodel. If you need the context use AndroidViewModel and AndroidViewModel#getApplication()
i am implementing Room with a vIewModel, my structure is the following
#DAO,#Entity,#Database,#Repository
#Entity(tableName="dx_table")
class dx_table(
#ColumnInfo(name = "name")
val naxme: String,
#PrimaryKey
#ColumnInfo(name = "phone")
val phone: String,
#ColumnInfo(name = "passx")
val passx: String,
#ColumnInfo(name = "login_fx")
val login_fx: String
)
#Dao
interface dx_dao{
#Query("SELECT * FROM dx_table")
fun get_all():LiveData<List<dx_table>>
#Insert
suspend fun insertTrx(dx_table:dx_table)
#Query("UPDATE dx_table SET login_fx =:login_fx where phone=:phonex")
suspend fun insertFx(login_fx: String,phonex: String)
#Query("SELECT * from dx_table where phone=:phonex")
suspend fun get_name_px(phonex: String):List<dx_table>
#Query("Delete from dx_table")
suspend fun deleteAll()
#Query("Select * from dx_table where login_fx=1")
suspend fun selectFx():List<dx_table>
}
#Database(entities = arrayOf(dx_table::class), version = 1, exportSchema = false)
public abstract class DxDatabase : RoomDatabase() {
abstract fun dxDao(): dx_dao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
private var INSTANCE: DxDatabase? = null
fun getDatabase(context: Context): DxDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
DxDatabase::class.java,
"dx_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
class dxRepository(private val dxDao: dx_dao ){
val k_d:LiveData<List<dx_table>> = dxDao.get_all()
suspend fun insert_trx(dx_table: dx_table){
dxDao.insertTrx(dx_table)
}
suspend fun insert_fx(login_fx: String,phonex: String) {
dxDao.insertFx(login_fx,phonex)
}
suspend fun select_fx() {
dxDao.selectFx()
}
suspend fun get_name_px(phonex: String) :List<dx_table> {
return dxDao.get_name_px(phonex) as List<dx_table>
}
}
The viewmodel is
class DxViewModel (application: Application) : AndroidViewModel(application) {
var repository: dxRepository
var k_d: LiveData<List<dx_table>>
init {
// Gets reference to WordDao from WordRoomDatabase to construct
// the correct WordRepository.
val dxDao = DxDatabase.getDatabase(application).dxDao()
repository = dxRepository(dxDao)
k_d = repository.k_d
}
fun insert_trx(dxTable: dx_table) = viewModelScope.launch {
repository.insert_trx(dxTable)
}
fun insert_fx(login_fx: String, phonex: String) = viewModelScope.launch {
repository.insert_fx(login_fx, phonex)
}
fun select_fx() = viewModelScope.launch {
repository.select_fx()
}
fun get_name_px(phonex: String) = viewModelScope.launch {
repository.get_name_px(phonex)
}
}
I can track the live data using observe,its not an issue, the problem i am facing is with the get_name_px(phone)
var mView = ViewModelProviders.of(this).get(DxViewModel::class.java)
var lm = mView.get_name_px(phone)
here lm seems to be job type , i need the return List , how do i get it .
In your viewModel function select_fx() return a job, because launch does not return result, so you have to either:
1) Use async and await
fun get_name_px(phonex: String) = viewModelScope.async {
repository.get_name_px(phonex)
}.await()
2) Not use launch viewModel, use it in Activity/Fragment
suspend fun get_name_px(phonex: String) = repository.get_name_px(phonex)
class CardFragment : Fragment() {
fun get() {
// launch in Dispatchers.Main
lifecycleScope.launch {
var lm = mView.get_name_px(phone)
}
// launch in background thread
lifecycleScope.launch(Dispatchers.Default) {
var lm = mView.get_name_px(phone)
}
}
}