Reputation: 86
I am currently trying to get data out of my Room Database without using a ViewModel. This is because I am working on a NotificationHandler which can be triggered at any point by an Alarm Manager.
Below is my code so far. This code below starts with a call to sortNotification
from another class. sortNotification
then calls launchAsyncTask
which inturn goes off to the database by calling getQuotesFromDatabase
. I then wait for the results (I believe), assign the data from the database to listOfQuotes
variable, then call displayNotification
to use it. My issue is, listOfQuotes
is always null when I am trying to use it displayNotification
.
Now I know the database has content as when I open my application and go to an Activity which has a ViewModel, the data is retrieved successfully. I think my issue is likely to be with the async task not completing properly or with my coroutineScope. I just need listOfQuotes
to have data when the code gets into displayNotification
. Any help would be greatly appreciated. Thanks in advance.
private var job = Job()
private val ioScope = CoroutineScope(Dispatchers.IO + job)
private lateinit var listOfQuotes: LiveData<List<DefaultQuote>>
fun sortNotification() {
launchAsyncTask()
}
private fun launchAsyncTask() = CoroutineScope(Dispatchers.IO).launch {
val asyncTask = ioScope.async {
getQuotesFromDatabase()
}
listOfQuotes = asyncTask.await()
displayNotification()
}
private suspend fun getQuotesFromDatabase(): LiveData<List<DefaultQuote>> {
return withContext(Dispatchers.IO) {
val defaultQuoteDao = QuoteDatabase.getDatabase(context, this).defaultQuoteDao()
val defaultQuoteRepository = DefaultQuoteRepository(defaultQuoteDao)
defaultQuoteRepository.allQuotes
}
}
private fun displayNotification() {
val quote = listOfQuotes.value?.let {
val size = it.size
val randomIndex = (0..size).random()
it[randomIndex]
} ?: throw NullPointerException("Quotes not found")
// ... then do notification stuff
I have also added in the code from my DAO:
@Dao
interface DefaultQuoteDao {
@Query("SELECT * FROM $DEFAULT_TABLE_NAME")
fun getAllQuotes(): LiveData<List<DefaultQuote>>
}
And the code from my repository:
class DefaultQuoteRepository(private val defaultQuoteDao: DefaultQuoteDao) {
val allQuotes: LiveData<List<DefaultQuote>> = defaultQuoteDao.getAllQuotes()
}
And the code for QuoteDatabase.getDatabase(Context, CoroutineScope)
:
fun getDatabase(context: Context, scope: CoroutineScope): QuoteDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
QuoteDatabase::class.java,
DATABASE_NAME
)
.fallbackToDestructiveMigration()
.addCallback(QuoteDatabaseCallback(scope))
.build()
INSTANCE = instance
return instance
}
}
Upvotes: 1
Views: 870
Reputation: 2963
The specific problem is you are never observing the value of the listOfQuotes
LiveData. This is required to initiate the fetch from the database.
Overall you're doing this in a strange way. You should use either coroutines or LiveData. Both of them allow you to observe data in the database, but you don't need both. That would be like wrapping an async call inside and async call then having to unwrap them both. You should either:
Flow<List<DefaultQuote>>
from your dao function getAllQuotes
I recommend 2. if you expect your application to become medium large or complex. Flow
allows you to map or combine data in a more succinct and flexible manner.
Then, your function sortNotification
would become:
// Ideally this should be injected into the class, but for a Service that's a little difficult.
// At a minimum you should initialize it once for the class
val defaultQuoteRepository: DefaultQuoteRepository by lazy {
DefaultQuoteRepository(QuoteDatabase.getDatabase(context, this).defaultQuoteDao())
}
fun sortNotification() {
defaultQuoteRepository.allQuotes
.map { listOfQuotes ->
listOfQuotes.random()
}
.flowOn(Dispatchers.IO)
.onEach { randomQuote ->
displayNotification(randomQuote)
}
// This makes the above onEach lambda run on the main thread so you're safe to show notifications.
// Ideally you should have a specific scope defined but tbh if you're not testing it's not that important
.launchIn(GlobalScope)
}
Upvotes: 2