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.
Related
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)
}
}
So, I have two ViewModels and two screens in my app. The first screen is for the presentation of a list of diary item elements, second is for detailed information on diary items. In a second ViewModel, I have an id to get a record from DB but can find it. What can I do to get it?
DAO:
interface DiaryDao {
#Query("SELECT * FROM diaryItems")
fun getAllDiaryPosts(): LiveData<List<DiaryItem>>
#Query("Select * from diaryItems where id = :id")
fun getDiaryPostById(id: Int) : DiaryItem
#Query("Delete from diaryItems where id = :index")
fun deleteDiaryPostByIndex(index : Int)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertDiaryPost(diaryItem: DiaryItem)
#Update
suspend fun updateDiaryPost(diaryItem: DiaryItem)
#Delete
suspend fun deleteDiaryPost(diaryItem: DiaryItem)
#Query("Delete from diaryItems")
suspend fun deleteAllDiaryItems()
}
Repository
class DiaryRepository #Inject constructor(private val diaryDao: DiaryDao) {
val readAllData: LiveData<List<DiaryItem>> = diaryDao.getAllDiaryPosts()
suspend fun getDiaryPostDyIndex(index: Int): DiaryItem {
return diaryDao.getDiaryPostById(index)
}
}
First viewmodel
#HiltViewModel
class PostListViewModel
#Inject
constructor(
private val diaryRepository: DiaryRepository,
) : ViewModel() {
private var allDiaryItems: LiveData<List<DiaryItem>> = diaryRepository.readAllData
}
Second viewmodel
#HiltViewModel
class PostDetailViewModel
#Inject
constructor(
private val savedStateHandle: SavedStateHandle,
private val diaryRepository: DiaryRepository
) : ViewModel() {
sealed class UIState {
object Loading: UIState()
data class Success(val currentPosts: DiaryItem) : UIState()
object Error : UIState()
}
val postDetailState: State<UIState>
get() = _postDetailState
private val _postDetailState = mutableStateOf<UIState>(UIState.Loading)
init {
viewModelScope.launch (Dispatchers.IO) {
try {
val diaryList: DiaryItem = diaryRepository.getDiaryPostDyIndex(2) //it is for test
_postDetailState.value = UIState.Success(diaryList)
} catch (e: Exception) {
withContext(Dispatchers.Main) {
_postDetailState.value = UIState.Error
}
}
}
}
}
I am sure you are getting errors. because you updating UI State in IO Thread
fun getDairyItem(itemId: Int){
viewModelScope.launch (Dispatchers.IO) {
try {
val diaryList: DiaryItem = diaryRepository.getDiaryPostDyIndex(itemId)
withContext(Dispatchers.Main) {
_postDetailState.value = UIState.Success(diaryList)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
_postDetailState.value = UIState.Error
}
}
}
}
I am developing news app I want to save data to database after fetching data from server using room but I am just confused I have followed tutorial but their logics is different how can I save data properly so that after getting data from server and I can save data to database?
below my SportNewsDatabase.kt
#Database(entities = [Article::class], version = 1, exportSchema = false)
abstract class SportNewsDatabase : RoomDatabase() {
abstract fun sportNewsDao(): SportNewsDao
companion object {
private var instance: SportNewsDatabase? = null
fun getInstance( context: Context): SportNewsDatabase? {
if (instance == null) {
synchronized(SportNewsDatabase::class.java) {
instance = Room.databaseBuilder(context.applicationContext, SportNewsDatabase::class.java, "article_database")
.fallbackToDestructiveMigration()
.build()
}
}
return instance
}
}
}
below SportNewsDao.kt
#Dao
interface SportNewsDao {
#Query("SELECT * FROM article")
fun getAllData(): LiveData<List<Article>>
#Insert
suspend fun addAll(article: List<Article>)
#Update
suspend fun updateArticle(article: Article)
#Delete
suspend fun deleteArticle(article: Article)
}
below my repository class
interface NewsRepository {
// Suspend is used to await the result from Deferred
suspend fun getNewsList(): UseCaseResult<List<Article>>
var sportNewsDao: SportNewsDao
}
fun insertArticle(article: List<Article>){
val insertArticles
}
#Suppress("UNCHECKED_CAST")
class NewsRepositoryImpl(private val sportsNewsApi: SportNewsInterface) : NewsRepository {
override suspend fun getNewsList(): UseCaseResult<List<Article>> {
return try {
val result = sportsNewsApi.getNewsAsync().body()!!.articles
UseCaseResult.Success(result)
} catch (ex: Exception) {
UseCaseResult.Error(ex)
}
}
}
below MainViewModel.kt where I am using Kotlin coroutines
#Suppress("UNCHECKED_CAST")
class MainViewModel(val newsRepository: NewsRepository) : ViewModel(), CoroutineScope {
// Coroutine's background job
val job = Job()
// Define default thread for Coroutine as Main and add job
override val coroutineContext: CoroutineContext = Dispatchers.Main + job
val showLoading = MutableLiveData<Boolean>()
val sportList = MutableLiveData<List<Article>>()
val showError = SingleLiveEvent<String>()
fun loadNews() {
// Show progressBar during the operation on the MAIN (default) thread
showLoading.value = true
// launch the Coroutine
launch {
// Switching from MAIN to IO thread for API operation
// Update our data list with the new one from API
val result = withContext(Dispatchers.IO) {
newsRepository?.getNewsList()
}
// Hide progressBar once the operation is done on the MAIN (default) thread
showLoading.value = false
when (result) {
is UseCaseResult.Success<*> -> {
sportList.value = result.data as List<Article>
}
is Error -> showError.value = result.message
}
}
}
override fun onCleared() {
super.onCleared()
// Clear our job when the linked activity is destroyed to avoid memory leaks
job.cancel()
}
}
#sashabeliy
Hi, i think i have the solution of your problem but i am not a expert in mvvm and coroutine so if someone have a better solution i take it :)
For my app i launch the the download in the main thread and only when i get the response i use Dispatchers.IO for insert in Room the data received.
In ViewModel
fun downLoadPDV(last_update : String) : LiveData<List<PDV>> {
return repositoryPDV.downloadPDV(last_update)
}
In Repository
fun downloadPDV(last_update : String ): LiveData<List<PDV>> {
val mutableLiveData: MutableLiveData<List<PDV>> = MutableLiveData()
val apiService: ApiInterface = RetrofitClient.getRetrofitClient().create(ApiInterface::class.java)
apiService.CallListPDV(last_update)?.enqueue(object :
Callback<List<PDV>> {
override fun onResponse(
call: Call<List<PDV>>,
response: Response<List<PDV>>
) {
Log.e(ContentValues.TAG, "Download PDV Success => $response")
if (response.isSuccessful && response.body() != null) {
mutableLiveData.setValue(response.body());
GlobalScope.launch(Dispatchers.IO) {
for (pdv in response.body()!!) {
insertPDV(pdv)
}
}
}
}
override fun onFailure(call: Call<List<PDV>>, t: Throwable) {
Log.e(ContentValues.TAG, "[Error] Download PDV Fail --> $call")
}
})
return mutableLiveData
}
I return the Data download for update the UI and inform the user of how many items was downloaded
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,
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?