Kotlin observeForever every time is null - android

i'm trying realise some global object for my app and observe inside to some LiveData from my DAO. Here is how it looks:
object appCommon {
#Volatile
lateinit var config: Config
suspend fun initAppCommon() {
GlobalScope.launch(Dispatchers.Main) {
Database_mW.getDBInstance(AppController.appInstance).ConfigDAO().getLive()
.observeForever(Observer {
config = it
})
}
initConfig()
}
private suspend fun initConfig() {
withContext(Dispatchers.IO) {
if (Database_mW.getDBInstance(AppController.appInstance).ConfigDAO().get() == null) {
Database_mW.getDBInstance(AppController.appInstance).ConfigDAO().insert(Config(getDeviceSerialNumber(), "", "", ""))
}
}
}
}
Here is DAO:
#Dao
interface Config_dao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(config: Config)
#Update
fun update(config: Config)
#Query("DELETE FROM Config")
fun clear()
#Query("SELECT * from Config LIMIT 1")
fun get(): Config
#Query("SELECT * from Config LIMIT 1")
fun getLive(): LiveData<Config>
#Query("update Config set device_secret = :secret")
fun updateSecret(secret: String)
}
So when i'm calling ConfigDAO().insert, observer is invoking but it = null.
Can somebody explain how to get latest inserted object inside observer for to update my global var?

Related

execute room db queries async using coroutine

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.

Why did livedata builder not invoked when data inserted to room

im really new on AAC and repository.
I have made an app with MVVM and repository.
Activity
class UserTestActivity : AppCompatActivity() {
private val userViewModel : UserViewModel by viewModel<UserViewModel>()
private lateinit var button : AppCompatButton
private var count : Int =0
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_test
button = findViewById(R.id.testButton)
val userObserver = Observer<MutableList<UserModel>> { newUserList ->
Log.d("room-db-status", "size: "+newUserList.size)
}
userViewModel._user.observe(this, userObserver)
button.setOnClickListener(View.OnClickListener {
count++
Toast.makeText(this, "updated: "+count, Toast.LENGTH_SHORT).show()
userViewModel.insertUser(UserModel(
uid = count.toString(),
nickName = "Alexar",
gender ="female",
age = 22,
birth ="19990901",
mainResidence= "Seoul",
subResidence = "???",
tripWish = mutableListOf("!!!","!!?"),
tripStyle = mutableListOf("!!!","!!?"),
selfIntroduction = "hi -_-",
uriList = mutableListOf("!!!","!!?"),
geohash = "none",
latitude = 37.455,
longitude = 124.890,
mannerScore = 4.5,
premiumOrNot = false,
knock = 0
))
})
}
}
this is ViewModel
class UserViewModel (
private val userRepository : UserRepository): ViewModel() {
val _user : LiveData<MutableList<UserModel>> = liveData(Dispatchers.IO) {
val data = userRepository.getAllUser()
emit(data)
}
fun insertUser (userModel: UserModel) {
viewModelScope.launch(Dispatchers.IO) {
userRepository.insertUser(userModel)
}
}
}
Repositoty
interface UserRepository {
suspend fun getAllUser () : MutableList<UserModel>
suspend fun insertUser (userModel: UserModel)
}
RepositoryImpl
class UserRepositoryImpl (
private val localDataSource : UserLocalDataSource,
private val userMapper: UserMapper) :UserRepository{
override suspend fun getAllUser() : MutableList<UserModel> {
val userList : List<UserEntity> = localDataSource.getAllUser()
var temp = mutableListOf<UserModel>()
for (user in userList)
temp.add(userMapper.entityToModel(user))
return temp
}
override suspend fun insertUser(userModel: UserModel) {
return localDataSource.insertUser(userMapper.modelToEntity(userModel))
}
}
UserLocalDataSource
interface UserLocalDataSource {
suspend fun getAllUser () : MutableList<UserEntity>
suspend fun insertUser (userEntity: UserEntity)
}
UserLocalDataSourceImpl
class UserLocalDataSourceImpl(
private val appDatabase: AppDatabase) : UserLocalDataSource {
override suspend fun getAllUser() : MutableList<UserEntity> {
return appDatabase.userEntityDao().getAllUser()
}
override suspend fun insertUser(userEntity: UserEntity) {
appDatabase.userEntityDao().insertUser(userEntity)
}
}
UserEntityDao
interface UserEntityDAO {
#Query ("SELECT * FROM user " )
suspend fun getAllUser() : MutableList<UserEntity>
#Query ("SELECT * FROM user WHERE uid = (:uid) ")
suspend fun getUser(uid: String) :UserEntity
#Insert (onConflict = REPLACE)
suspend fun insertUser (user : UserEntity)
#Query("DELETE FROM user WHERE uid = (:uid)")
suspend fun delete(uid : String)
}
there are also Mapper and Koin injection.
when trying to insert user data to room, it was successful. but
after that, liveData Builder
val _user : LiveData<MutableList<UserModel>> = liveData(Dispatchers.IO) {
val data = userRepository.getAllUser()
emit(data)
}
not invoked...
Of course, that builder is invoked only once when app started haha
who knows why??
I do not know.
You shouldn't rely on the liveData builder to be invoked when data is inserted/updated in DB. Please refer to this doc on how to work with liveData builder.
To achieve the case when an observer of LiveData is invoked when data is inserted/updated in DB, methods must return LiveData object in UserEntityDao:
interface UserEntityDAO {
#Query ("SELECT * FROM user " )
suspend fun getAllUser() : LiveData<List<UserEntity>>
#Query ("SELECT * FROM user WHERE uid = (:uid) ")
suspend fun getUser(uid: String) : LiveData<UserEntity>
// ...
}

Room database update creates unexpected UI side effects

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!

How to implement multiple queries with room database?

Im creating an android app using room database with MVVM pattern, the problem is that i cant use multiple queries when fetching data. I can fetch data once, but then i cant do it anymore.
DAO interface:
#Dao
interface StockDao {
#Insert
suspend fun insert(stock:Stock)
#Update
suspend fun update(stock:Stock)
#Delete
suspend fun delete(stock:Stock)
#Query("DELETE FROM stock_table")
suspend fun deleteAll()
#Query("SELECT * FROM stock_table")
fun selectAll():Flow<List<Stock>>
#Query("SELECT * FROM stock_table WHERE isFinished = 0")
fun selectAllUnfinished(): Flow<List<Stock>>
#Query("SELECT * FROM stock_table WHERE isFinished = 1")
fun selectAllFinished():Flow<List<Stock>>
#Query("SELECT * FROM stock_table ORDER BY totalSpent DESC")
fun selectAllOrderByDesc():Flow<List<Stock>>
#Query("SELECT * FROM stock_table ORDER BY totalSpent ASC")
fun selectAllOrderByAsc():Flow<List<Stock>>
}
Repository:
class StockRepository(private val stockDao: StockDao) {
private lateinit var allStock: Flow<List<Stock>>
suspend fun insert(stock: Stock) {
stockDao.insert(stock)
}
suspend fun update(stock: Stock) {
stockDao.update(stock)
}
suspend fun delete(stock: Stock) {
stockDao.delete(stock)
}
suspend fun deleteAll() {
stockDao.deleteAll()
}
fun selectAll(): Flow<List<Stock>> {
allStock = stockDao.selectAll()
return allStock
}
fun selectAllOrderByDesc(): Flow<List<Stock>> {
allStock = stockDao.selectAllOrderByAsc()
return allStock
}
fun selectAllOrderByAsc(): Flow<List<Stock>> {
allStock = stockDao.selectAllOrderByAsc()
return allStock
}
fun selectAllFinished(): Flow<List<Stock>> {
allStock = stockDao.selectAllFinished()
return allStock
}
fun selectAllUnfinished(): Flow<List<Stock>> {
allStock = stockDao.selectAllUnfinished()
return allStock
}
}
Viewmodel class:
class StockViewModel(private val repo: StockRepository) : ViewModel() {
companion object {
const val ALL = 0
const val ORDER_BY_DESC = 1
const val ORDER_BY_ASC = 2
const val FINISHED = 3
const val UNFINISHED = 4
}
var allStocks = repo.selectAll().asLiveData()
fun insert(stock: Stock) = viewModelScope.launch {
repo.insert(stock)
}
fun update(stock: Stock) = viewModelScope.launch {
repo.update(stock)
}
fun delete(stock: Stock) = viewModelScope.launch {
repo.delete(stock)
}
fun deleteAll() = viewModelScope.launch {
repo.deleteAll()
}
fun selectAllStockWithFilter(filter: Int): LiveData<List<Stock>> {
when (filter) {
ALL -> allStocks = repo.selectAll().asLiveData()
ORDER_BY_DESC -> allStocks = repo.selectAllOrderByDesc().asLiveData()
ORDER_BY_ASC -> allStocks = repo.selectAllOrderByAsc().asLiveData()
FINISHED -> allStocks = repo.selectAllFinished().asLiveData()
UNFINISHED -> allStocks = repo.selectAllUnfinished().asLiveData()
}
return allStocks
}
class StockViewModelFactory(private val repo: StockRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(StockViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return StockViewModel(repo) as T
}
throw IllegalArgumentException("Unknown viewModel class")
}
}
}
Application class:
class FinanceApplication :Application(){
private val database by lazy { FinanceDatabase.getInstance(this)}
val stockRepository by lazy { StockRepository(database.stockDao()) }
}
Activity using this viewmodel :
class StocksActivity : AppCompatActivity() {
//Layout components
private lateinit var binder: ActivityStocksBinding
private lateinit var recyclerView: RecyclerView
private lateinit var recyclerViewAdapter: StockAdapter
//ViewModel
private val viewModel: StockViewModel by viewModels {
StockViewModel.StockViewModelFactory((application as FinanceApplication).stockRepository)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binder = ActivityStocksBinding.inflate(layoutInflater)
setContentView(binder.root)
fetchStocks()
}
private fun fetchStocks() {
viewModel.allStocks.observe(this) {
recyclerViewAdapter.submitList(it)
}
}
private fun initRecyclerViewLayout() {
val recyclerViewLayoutBinder = binder.includedLayout
recyclerView = recyclerViewLayoutBinder.stocksRecyclerView
recyclerViewAdapter = StockAdapter(this)
recyclerView.adapter = recyclerViewAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_stock_toolbar, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_stock_toolbar_filter_all -> viewModel.selectAllStockWithFilter(StockViewModel.ALL)
R.id.menu_stock_toolbar_filter_maior_menor -> viewModel.selectAllStockWithFilter(StockViewModel.ORDER_BY_DESC)
R.id.menu_stock_toolbar_filter_menor_maior -> viewModel.selectAllStockWithFilter(StockViewModel.ORDER_BY_ASC)
R.id.menu_stock_toolbar_filter_finalized -> viewModel.selectAllStockWithFilter(StockViewModel.FINISHED)
R.id.menu_stock_toolbar_filter_opened -> viewModel.selectAllStockWithFilter(StockViewModel.UNFINISHED)
}
return true
//NOTHIN HAPPENS AFTER CHOOSING ONE
}
}
When i enter the activity, all the data is fetched normally, but when i click on a menu item to apply some filter on it, nothing happens, the data doesnt change. How can i fix this?
allStocks may seem dynamic because it's LiveData, but remember that it's still a reference to an object in memory. When StocksActivity is created, it observes allStocks in it's initial state. For the sake of simplicity, let's say allStocks is pointing to an object in memory with the address of "A". When selectAllStockWithFilter() is eventually invoked, the allStocks handle is updated to point to a new instance of LiveData living in memory at address "B". The problem you're facing is that StocksActivity is still observing "A". Nothing communicated that the allStocks handle itself has been changed.
One way to resolve this would be to change allStocks into an instance of MutableLiveData. Subsequently, whenever the contents of this allStocks should be updated, instead of reassigning allStocks, you would update it's internal "value". This allows the ViewModel to pump new/updated values through the same LiveData object instance that StocksActivity is observing.
Something like this:
class StockViewModel(private val repo: StockRepository) : ViewModel() {
...
val allStocks = MutableLiveData<List<Stock>>().apply { value = repo.selectAll() }
...
fun selectAllStockWithFilter(filter: Int) {
when (filter) {
ALL -> allStocks.postValue(repo.selectAll())
ORDER_BY_DESC -> allStocks.postValue(repo.selectAllOrderByDesc())
ORDER_BY_ASC -> allStocks.postValue(repo.selectAllOrderByAsc())
FINISHED -> allStocks.postValue(repo.selectAllFinished())
UNFINISHED -> allStocks.postValue(repo.selectAllUnfinished())
}
}
...
}

Observe Data from Room using View model in Kotlin

I am creating a social media app and I am following MVVM Pattern
I am stuck at observing data from DB . As far I know is the Dao methods have to be executed on the background thread. But I am not able to implement viewmodel from Repository Class.I will paste the code ,Can anyone give me some help?
Dao
#Dao
interface FeedDao{
//Post feed item
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(feed:Feed):Long
//Update feed item
#Update(onConflict = OnConflictStrategy.IGNORE)
fun update(feed: Feed):Int
//Get feed from user friend's
#Query("SELECT * from feed")
fun getAllFeed():LiveData<List<Feed>>
}
Repository
class FeedRepository private constructor(private val feedDao: FeedDao) {
companion object {
#Volatile private var instance:FeedRepository? = null
private val apiClient = ApiClient.getApiClient().create(UserClient::class.java)
fun getInstance(feedDao: FeedDao)= instance?: synchronized(this){
instance ?: FeedRepository(feedDao).also { instance=it }
}
}
private fun getFeedResponse() {
//TODO:Check to change parameter values after adding Paging Functionality
val call: Call<List<Feed>> = apiClient.getFeed(20, 0)
call.enqueue(object :Callback<List<Feed>>{
override fun onFailure(call: Call<List<Feed>>?, t: Throwable?) {
Log.e("FeedRepository","Feed Callback Failure")
Timber.d(t?.message)
}
override fun onResponse(call: Call<List<Feed>>?, response: Response<List<Feed>>?) {
if(response!!.isSuccessful){
Timber.i("Successful Response -> Adding to DB")
addResponseTODB(response.body()!!)
}else{
when(response.code()){
400 -> Timber.d("Not Found 400")
500 -> Timber.d("Not logged in or Server broken")
}
}
}
})
}
private fun addResponseTODB(items:List<Feed>){
Timber.d("Response --> DB Started")
object : AsyncTask<List<Feed>,Void,Boolean>(){
override fun doInBackground(vararg params: List<Feed>?): Boolean {
var needsUpdate:Boolean = false
for(item in params[0]!!.iterator()){
var inserted = feedDao.insert(item)
if(inserted.equals(-1)){
var updated = feedDao.update(item)
if(updated > 0){
needsUpdate = true
}
}else{
needsUpdate = true
}
}
return needsUpdate
}
override fun onPostExecute(result: Boolean?) {
if (result!!){
loadFromDB()
}
}
}.execute(items)
}
private fun loadFromDB(){
Timber.d("loadFromDB")
object :AsyncTask<Void,Void,LiveData<List<Feed>>>(){
override fun doInBackground(vararg params: Void?): LiveData<List<Feed>>? {
return feedDao.getAllFeed()
}
override fun onPostExecute(result: LiveData<List<Feed>>?) {
}
}.execute()
}
public fun fetchFeed(){
Timber.d("fetchFeed")
loadFromDB()
getFeedResponse()
}
}
ViewModel
class FeedViewModel private constructor(private val
feedDao:FeedDao):ViewModel(){
// TODO: Implement the ViewModel
private val feedRepository= FeedRepository.getInstance(feedDao)
public val feed = MediatorLiveData<List<Feed>>()
val value = MutableLiveData<Int>()
init {
feed.addSource(feed,feed::setValue)
}
companion object {
private const val NO_FEED = -1
}
fun getFeedLD() = feed
}
First, you don't need to insert all Feed objects one by one in the Database. You can create a method inside FeedDao which to insert List<Feed>
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(feed: List<Feed>):Long
Than, it is not necessary to call loadFromDB() inside the onPostExecute() method, because your fun getAllFeed(): LiveData<List<Feed>>() method from #FeedDao returns LiveData and you can add Observer to it and every time when the data inside the Database is changes the Observer will be triggered.
And for your Network/Database requests you always can use NetworkBoundResource https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/repository/NetworkBoundResource.kt
Hope I helped somehow :)

Categories

Resources