learning Kotlin, proper app structure and stuff... Again... :)
So i have the following:
Dao
Repository
ViewModel
Dao Code
#Dao
interface ItemDao {
#Query("SELECT * from item_table ORDER BY Name ASC")
fun getSortedItems(): LiveData<List<Item>>
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
#Query("DELETE FROM item_table")
suspend fun deleteAll()
}
Repository
class ItemRepository (private val itemDao: ItemDao) {
val allItems: LiveData<List<Item>> = itemDao.getSortedItems()
suspend fun insert(item: Item) {
itemDao.insert(item)
}
}
ViewModel
class ItemViewModel(application: Application) : AndroidViewModel(application) {
private val repository: ItemRepository
val allItems: LiveData<List<Item>>
init {
val itemsDao = ItemDataBase.getDatabase(application, viewModelScope).itemDao()
repository = ItemRepository(itemsDao)
allItems = repository.allItems
}
fun insert(item: Item) = viewModelScope.launch {
repository.insert(item)
}
}
I need to get current amount of rows in a table and i was thinking of something like this:
add to Dao
#Query("SELECT COUNT(name) FROM item_table")
fun getAmount(): LiveData<Int>
and then in Repo
fun getAmount(): LiveData<Int>{
return itemDao.getAmount()
}
and the same in ViewModel
fun getAmount(): LiveData<Int> {
return repository.getAmount()
}
But again, should i also use an observer in Activity to get this value? Or, on the other hand, can i use just Int, while getting the table from DB as LiveData?
Related
what is the problem with the codes
db queries freeze ui.
every thing works , but freezing ui when running db query
what is the point that i dont know .
all queries make ui freeze,
when executing Delete query , recycleview animation freezing ,but after commenting the line that execute query , recycleview work smoothly
hilt Module:
#Module
#InstallIn(SingletonComponent::class)
object DatabaseModule {
#Provides
fun provideCardDao(passwordDatabase: PasswordDatabase): CardDao {
return passwordDatabase.cardDao()
}
#Singleton
#Provides
fun providesCoroutineScope(): CoroutineScope {
return CoroutineScope(SupervisorJob() + Dispatchers.Default)
}
#Provides
#Singleton
fun providePasswordDatabase(
#ApplicationContext appContext: Context,
coroutineScope: CoroutineScope
): PasswordDatabase {
return Room.databaseBuilder(
appContext,
PasswordDatabase::class.java,
"mydb.db"
)
.addMigrations()
.addCallback(MYDatabaseCallback(coroutineScope))
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
.fallbackToDestructiveMigration()
.build()
}
}
card Dao
#Dao
interface CardDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertCardDetails(cardDetails : BanksCardModel)
#Query("DELETE FROM CardDetailsTable WHERE id = :id")
suspend fun deleteCardDetails(id : Int)
#Query("SELECT * FROM CardDetailsTable WHERE id = :id")
fun getOneCardDetails(id : Int):LiveData<BanksCardModel>
#Query("SELECT * FROM CardDetailsTable")
fun getAllCardDetails() : LiveData<List<BanksCardModel>>
#Query("SELECT * FROM CardDetailsTable WHERE username LIKE '%' || :str || '%' OR bank_name LIKE '%' || :str || '%' "
)
fun queryOnCards(str:String): LiveData<List<BanksCardModel>>
}
password db
#Database(entities = [ BanksCardModel::class],version = 15,exportSchema = false)
abstract class PasswordDatabase : RoomDatabase() {
abstract fun cardDao() : CardDao
}
repository
#Singleton
class Repository #Inject constructor(private val cardDao: CardDao) {
suspend fun insertCardDetails(cardDetailsItem: BanksCardModel){
cardDao.insertCardDetails(cardDetailsItem)
}
suspend fun deleteCardDetails(id : Int){
cardDao.deleteCardDetails(id)
}
fun getOneCardDetails(id : Int):LiveData<BanksCardModel>{
return cardDao.getOneCardDetails(id)
}
fun getAllCardDetails() : LiveData<List<BanksCardModel>>{
return cardDao.getAllCardDetails()
}
fun queryOnCards(str : String) : LiveData<List<BanksCardModel>>{
return cardDao.queryOnCards(str)
}
}
viewModel
#HiltViewModel
class DetailsViewModel #Inject constructor(
private val repository: Repository
) : ViewModel() {
private var cardDetailsList : LiveData<List<BanksCardModel>>
init {
cardDetailsList = repository.getAllCardDetails()
}
fun insertCardDetails(cardDetailsItem: BanksCardModel)= viewModelScope.launch {
repository.insertCardDetails(cardDetailsItem)
}
fun deleteCardDetails(id : Int)= viewModelScope.launch {
repository.deleteCardDetails(id)
}
fun getOneCardDetails(id : Int):LiveData<BanksCardModel>{
return repository.getOneCardDetails(id)
}
fun getAllCardDetails() : LiveData<List<BanksCardModel>>{
return cardDetailsList
}
fun queryOnCards(str:String) : LiveData<List<BanksCardModel>>{
return repository.queryOnCards(str)
}
}
////edited
query in searchview
override fun onQueryTextChange(newText: String?): Boolean {
lifecycleScope.launch {
viewModel.queryOnCards(newText?:"").collect{
val searchedList = it.toMutableList()
showingEmptyListAnnounce(searchedList)
adapter.updateList(searchedList)
}
}
return true
}
the update list function in onQueryTextChange -- its using DiffUtils
fun updateList( updatedList : MutableList<BanksCardModel>){
val callback = CustomCallback(mList, updatedList)
val result = DiffUtil.calculateDiff(callback)
mList.clear()
mList.addAll(updatedList)
result.dispatchUpdatesTo(this)
}
video of screen
LiveData is outdated. Please consider to use Kotlin Flow.
Here is your use case:
lifecycleScope.launch {
dao.getData().collect { data ->
//handle data here
}
}
#Dao
interface MyDao {
#Query("SELECT * FROM somedata_table")
fun getData(): Flow<List<SomeData>>
}
Reference
There is a good Google codelab to practice Kotlin coroutine and Room queries
Also please consider to use StrictMode - it will show you this and similar issues in future.
What am I trying to achieve ?
Get a single row of data which has the id I need. The SQL equivalent of SELECT * FROM favs WHERE link='link'. I have written a fun named getOneFav() which for this. I am following the tutorial https://developer.android.com/codelabs/android-room-with-a-view-kotlin#0 and code from https://github.com/android/sunflower
What have I setup so far ?
Entity
#Entity(tableName = "favs")
data class Favorite(
#PrimaryKey #ColumnInfo(name = "link") val link : String,
#ColumnInfo(name = "keywords") val keywords : String
)
DAO
#Dao
interface FavDAO {
#Query("SELECT * FROM favs")
fun getAllFavsLive(): Flow<List<Favorite>>
#Query("SELECT * FROM favs WHERE link = :link")
fun getOneFav(link: String): Favorite
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(link: Favorite)
}
Repository
class FavRepo (private val favDao: FavDAO) {
val allFavs: Flow<List<Favorite>> = favDao.getAllFavsLive()
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun insert(link: Favorite) {
favDao.insert(link)
}
fun getOneFav(link: String) = favDao.getOneFav(link)
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun delete(link: String) {
favDao.delete(link)
}
}
ViewModel
class FavViewModel (private val repository: FavRepo) : ViewModel() {
val allFavs: LiveData<List<Favorite>> = repository.allFavs.asLiveData()
fun insert(link: Favorite) = viewModelScope.launch {
repository.insert(link)
}
fun getOneFav(link: String) = repository.getOneFav(link)
}
class FavViewModelFactory(private val repository: FavRepo) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(FavViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return FavViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
What problems am I facing ?
I am receiving an error saying
java.lang.RuntimeException: Unable to start activity ComponentInfo{[package name removed].MainActivity}: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
What have I tried so far ?
I have tried -
Adding suspend in front of the function getOneFav in DAO and Repository
Made the function run inside viewModelScope. It gave the same error as above. Also, this way the function returned a Job instead of the 'Favorite' data class object.
fun getOneFav(link: String) = viewModelScope.launch {
repository.getOneFav(link)
}
Followed this method here - How to implement a Room LiveData filter which even though worked, which seemed like an overkill for something so simple. Also despite the fact that the code is using MutableLiveData, I wasn't able to see any triggers when the insert happened.
You should run your queries in a different context:
class FavRepo (private val favDao: FavDAO) {
val allFavs: Flow<List<Favorite>> = withContext(Dispatchers.IO) {
favDao.getAllFavsLive()
}
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun insert(link: Favorite) = withContext(Dispatchers.IO) {
favDao.insert(link)
}
fun getOneFav(link: String) = withContext(Dispatchers.IO) {
favDao.getOneFav(link)
}
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun delete(link: String) = withContext(Dispatchers.IO) {
favDao.delete(link)
}
}
When I simply Insert my data for the first time, all is well and when I reload my tiles, they get saved in the database and the tiles reload normally - see Animation 1.
When I try to update my tiles, I have this weird behaviour, like a loop - see Animation 2.
I am wondering what could be causing this. Here is my code:
#Entity (tableName = "saved_values_table")
data class SavedValues (
#PrimaryKey()
var index: Int,
var visible: Boolean,
var enabled: Boolean,
var boardLetters: String,
var boardLettersValue: Int
)
Dao:
#Update
suspend fun updateValues(values: SavedValues)
Repo:
suspend fun updateValues(savedValues: SavedValues) {
savedValuesDao.updateValues(savedValues)
}
ViewModel:
fun saveValues(index: Int, boardLetters: Array<Letter>) {
val savedValues = readAllValues.value
if (savedValues != null) {
if (savedValues.isEmpty()) {
addValues(
SavedValues(
index,
visible = true,
enabled = true,
boardLetters = boardLetters[index].name,
boardLettersValue = boardLetters[index].value
)
)
}
else {
updateValues(
SavedValues(
index,
visible = true,
enabled = true,
boardLetters = boardLetters[index].name,
boardLettersValue = boardLetters[index].value
)
)
}
}
}
What am I doing wrong? Thanks for any help!
UPDATE
I am adding some more code here as the problem persists, contrary to what I said in my comment.
My whole Dao:
#Dao
interface SavedValuesDao {
#Insert
suspend fun addValues(values: SavedValues)
#Query("SELECT * FROM saved_values_table")
fun readAllValues(): LiveData<List<SavedValues>>
#Query("SELECT boardLetters FROM saved_values_table")
fun readBoardLetters(): LiveData<List<String>>
#Query("SELECT boardLettersValue FROM saved_values_table")
fun readBoardLettersValues (): LiveData<List<Int>>
#Update
suspend fun updateValues(values: SavedValues)
#Query("DELETE FROM saved_values_table")
suspend fun deleteAllValues()
}
My Database:
#Database(
entities = [Word::class, SavedValues::class, SavedWords::class],
version = 8,
exportSchema = false
)
abstract class WordDatabase : RoomDatabase() {
abstract fun wordDAO(): WordDao
abstract fun savedValuesDao(): SavedValuesDao
}
My Repository:
val readAllValues: LiveData<List<SavedValues>> = savedValuesDao.readAllValues()
val readBoardLetters: LiveData<List<String>> = savedValuesDao.readBoardLetters()
val readBoardLettersValues: LiveData<List<Int>> =
savedValuesDao.readBoardLettersValues()
suspend fun addValues(savedValues: SavedValues) {
savedValuesDao.addValues(savedValues)
}
suspend fun updateValues(savedValues: SavedValues) {
savedValuesDao.updateValues(savedValues)
}
suspend fun deleteAllValues() {
savedValuesDao.deleteAllValues()
}
Thanks for any help!
in my Android app I use MVVM model with repositories
most of my DAO and Repositories classes are almost identical, so I figured I could reuse some code by creating generic equivalents that are later extended
this worked fine for my DAO classes:
#Dao
interface BaseDao<T> {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg entity: T)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertList(entities: List<T>)
#Update
suspend fun update(entity: T)
#Delete
suspend fun delete(entity: T)
}
#Dao
interface SettingDao : BaseDao<Setting> {
#Query("SELECT value FROM settings WHERE id = :settingName")
fun get(settingName: String): LiveData<String>
#Query("SELECT * FROM settings ORDER BY id ASC")
fun getAll(): LiveData<List<Setting>>
#Query("DELETE FROM settings")
suspend fun deleteAll()
}
now I want to do something similar for repository, but I cannot figure out how
this is what I've tried (I get error java.lang.ClassCastException: java.lang.Object[] cannot be cast to: Setting[]) in BaseRepository$insert$2.invokeSuspend
abstract class BaseRepository<T>(private val baseDao: BaseDao<T>) {
suspend fun update(entity: T) {
withContext(Dispatchers.IO) { baseDao.update(entity) }
}
suspend fun insert(entity: T) {
withContext(Dispatchers.IO) { baseDao.insert(entity) }
}
suspend fun delete(entity: T) {
withContext(Dispatchers.IO) { baseDao.delete(entity) }
}
}
class SettingRepository(private val settingDao: SettingDao) : BaseRepository<Setting>(settingDao) {
val company = settingDao.get("company")
val ip = settingDao.get("ip")
suspend fun deleteAll() {
withContext(Dispatchers.IO) { settingDao.deleteAll() }
}
}
My intuition is that I have to adjust this bit private val baseDao: BaseDao<T> in BaseRepository, but I cannot figure out how
And 2nd question - is there a way to get a generic table name in DAO, so I could put
#Query("DELETE FROM settings")
suspend fun deleteAll()
in the generic interface as well (in this case replace settings with entity's tableName
i want to use SearchView for search some element in room db and i have a problem with this because i cant use getFilter in RecyclerViewAdapter because i have ViewModel maybe whoes know how to combine all of this element in one project.
I search one way use Transormations.switchMap. But I couldn’t connect them.
ProductViewModel
class ProductViewModel(application: Application) : AndroidViewModel(application) {
private val repository: ProductRepository
val allProducts: LiveData<List<ProductEntity>>
private val searchStringLiveData = MutableLiveData<String>()
init {
val productDao = ProductsDB.getDatabase(application, viewModelScope).productDao()
repository = ProductRepository(productDao)
allProducts = repository.allProducts
searchStringLiveData.value = ""
}
fun insert(productEntity: ProductEntity) = viewModelScope.launch {
repository.insert(productEntity)
}
val products = Transformations.switchMap(searchStringLiveData) { string ->
repository.getAllListByName(string)
}
fun searchNameChanged(name: String) {
searchStringLiveData.value = name
}
}
ProductDao
interface ProductDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertProduct(productEntity: ProductEntity)
#Query("SELECT * from products")
fun getListAllProducts(): LiveData<List<ProductEntity>>
#Query("DELETE FROM products")
suspend fun deleteAll()
#Query("SELECT * FROM products where product_name_ent LIKE :name or LOWER(product_name_ent) like LOWER(:name)")
fun getListAllByName(name: String):LiveData<List<String>>
}
ProductDao
#Query("SELECT * FROM products where product_name_ent LIKE :name or LOWER(product_name_ent) like LOWER(:name)")
fun getListAllByName(name: String):LiveData<List<ProductEntity>>
This Method in your dao should return LiveData<List<ProductEntity>> and not LiveData<List<String>>, because this query selects everything (*) from the entity and not a specific column.
it is similar to (#Query("SELECT * from products") fun getListAllProducts():LiveData<List<ProductEntity>>)
ProductViewModel
class ProductViewModel(application: Application) : AndroidViewModel(application) {
private val repository: ProductRepository
init {
val productDao = ProductsDB.getDatabase(
application,
viewModelScope
).productDao()
repository = ProductRepository(productDao)
}
private val searchStringLiveData = MutableLiveData<String>("") //we can add initial value directly in the constructor
val allProducts: LiveData<List<ProductEntity>>=Transformations.switchMap(searchStringLiveData)
{
string->
if (TextUtils.isEmpty(string)) {
repository.allProducts()
} else {
repository.allProductsByName(string)
}
}
fun insert(productEntity: ProductEntity) = viewModelScope.launch {
repository.insert(productEntity)
}
fun searchNameChanged(name: String) {
searchStringLiveData.value = name
}
}
Repository
...with the other method that you have, add the following:
fun allProducts():LiveData<List<ProductEntity>>=productDao.getListAllProducts()
fun allProductsByNames(name:String):LiveData<List<ProductEntity>>=productDao.getListAllByName(name)
In Your Activity Or Fragment Where you have the recyclerview adapter
inside onCreate() (if it is an activity)
viewModel.allProducts.observe(this,Observer{products->
//populate your recyclerview here
})
or
onActivityCreated(if it is a fragment)
viewModel.allProducts.observe(viewLifecycleOwner,Observer{products->
//populate your recyclerview here
})
Now set a listener to the searchView, everytime the user submit the query , call viewModel.searchNameChanged(// pass the new value)
Hope this helps
Regards,