I am building an app where I need to use Room database library. I have a specific thing to do - if an user enters some data, I need to store it in database and also update the UI to show it. Something like this -
fun insertAndShowData() {
// get relevant data
// launch a coroutine to store this data in the database
// update UI
}
What I understand is I need coroutines to do operations to database, but I'm not sure how to do it since I'm concerned that the coroutine might not finish before I update the UI, so the UI may not show the correct data. The examples on internet use either runBlocking or GlobalScope.launch, but it is mentioned that these are not recommended to use in a real application. Can someone tell me in detail how to do it, preferably with some code? I apologize in advance if I am asking very basic stuff since I am new to coroutines in Android.
I suggest you to use the LiveData with Room, then you don't need to handle the "return" in order to update your view.
You still need to call your Room functions inside a coroutine (functions are suspend)
For good practice, you can follow the codelabs (especially chapter 16) : https://developer.android.com/codelabs/android-room-with-a-view-kotlin#0
If the "update ui" code should be done only after the coroutine work is done, you can do this:
fun insertAndShowData() {
// get relevant data
lifecycleScope.launch {
withContext(Dispatchers.IO) {
//database operations here.
}
withContext(Dispatchers.Main) {
//update UI here.
}
}
}
You need to use MutableLiveData and listen and bind that data on View. On the worker thread you can use postValue(T value) method. Therefore you listening LiveData on the main thread you are able to update the view.
https://developer.android.com/reference/android/arch/lifecycle/MutableLiveData
Related
So far I've been using this pattern whenever I want to access the database:
runBlocking {
launch {
// fetch something from the database and put it to some view
}
}
Now that I'm diving deeper into Kotlin coroutines, I'm increasingly convinced, that this is a bad pattern. Essentially, I might as well just allowMainThreadQueries, as my pattern blocks the main thread anyway.
Unfortunately, I haven't found a proper pattern yet. How to effectively use Kotlins coroutines to access the database?
Is runBlocking the only entry point into coroutines?
Consider this scenario:
override fun onCreate() {
setContentView(someLayout)
// concurrently fetch something from the database and put it in some view
// onCreate may return before this has finished
someButton.setOnClickListener {
// concurrently insert or update something in the database
}
}
You should never use runBlocking in an Android project, unless you are mixing Kotlin coroutines code with some Java code that can't use coroutines and it needs a way to call some coroutine in a blocking manner on one of its own background threads. In this case, you might use runBlocking to create a bridge function for the Java code to call, but you would never call this function from Kotlin and certainly never call it from the main thread. Calling blocking code on the main thread freezes the UI, which makes your app feel janky and risks triggering an ANR (application not responding) error.
The correct way to begin a coroutine is to use a CoroutineScope to launch your coroutine. These are already provided for you by the Android Jetpack framework for Activities, Fragments, and ViewModels.
In an Activity, use lifecycleScope.launch. In a Fragment, you should usually use viewLifecycleOwner.lifecycleScope.launch. In a ViewModel, use viewModelScope.launch.
What does using a CoroutineScope instead of runBlocking do? It prevents the long-running suspending actions (like reading the database from the disk) from blocking the main thread and freezing your UI. And it automatically cancels the long-running work when the Activity/Fragment/ViewModel is torn down, so it prevents memory leaks and wasted resources.
Assume that you are using Room,
runBlocking and allowMainThreadQueries are usually used for Test purpose and you should never use them in release product.
what allowMainThreadQueries do is give you permission to access database from Main Thread which is you should Never do, because it may freeze the UI.
use lifecycleScope.launch to launch coroutine from Fragment/Activity or viewModelScope.launch from ViewModel, you might need to explicitly add the dependencies
def lifecycleVersion = '2.4.0'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
// Lifecycles only (without ViewModel or LiveData)
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
Lifecycle release note https://developer.android.com/jetpack/androidx/releases/lifecycle
you should call the database operation from the ViewModel to prevent cancelation from configuration change, If the user rotate the screen while the operation is in progress, it'll be canceled and the result won't be cached.
In Activity/Fragment
// read data from db
lifecycleScope.launch {
viewModel.someData.collect {
//do some stuff
}
}
// insert
someButton.setOnClickListener {
viewModel.insertToDatabase(someData)
}
In ViewModel
class MainViewModel (val dao: Dao) : ViewModel() {
// you should expose your data as Observable
val someData : Flow<List<SomeData>> = dao.getAllSomeData()
fun insertToDatabase(data:SomeData) = viewModelScope.launch {
dao.insert(data)
}
}
// query
#Query("SELECT * FROM some_data_table")
abstract fun getAllSomeData(): Flow<List<SomeData>>
I have a flow that does CPU intensive work as shown below:
fun doWork():Flow<MyResult> =
flow{
for(i in 1..100){
//calculate()
}
emit(MyResult())
}
I collect from it inside a Fragment as shown below:
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
launch {
viewModel.doWork().collect {
val result = it ?: return#collect
// Preview result
}
}
}
But, since I am collecting using the main thread, the flow body runs on the main thread which is not the best thing to do.
How can I make the flow execute on a different thread?
In case you intend to do heavy calculations inside a flow it is recommended to move it to the default dispatcher like so,
flow{
//CPU intensive work
}.flowOn(your chosen dispatcher)
Libraries like the Room database and retrofit for networking handle the correct threading under the hood for you, so you do not have to worry about using .flowOn
Using withContext() inside a flow will not work for you.
This link is also very useful if you want to know more about asynchronous Flow.
Because database fetches usually happen asynchronously by default, a variable that holds the data from the firebase database fetch will be null when used right after the fetch. To solve this I have seen people use the ".await()" feature in Kotlin coroutines but this goes against the purpose of asynchronous database queries. People also call the succeeding code from within 'addOnSuccessListener{}' but this seems to go against the purpose of MVVM, since 'addOnSuccessListener{}' will be called in the model part of MVVM, and the succeeding code that uses the fetched data will be in the ViewModel. The answer I'm looking for is maybe a listener or observer that is activated when the variable (whose value is filled from the fetched data) is given a value.
Edit:
by "succeeding code" I mean what happens after the database fetch using the fetched data.
As #FrankvanPuffelen already mentioned in his comment, that's what the listener does. When the operation for reading the data completes the listener fires. That means you know if you got the data or the operation was rejected by the Firebase servers due to improper security rules.
To solve this I have seen people use the ".await()" feature in Kotlin coroutines but this goes against the purpose of asynchronous database queries.
It doesn't. Using ".await()" is indeed an asynchronous programming technique that can help us prevent our applications from blocking. When it comes to the MVVM architecture pattern, the operation for reading the data should be done in the repository class. Since reading the data is an asynchronous operation, we need to create a suspend function. Assuming that we want to read documents that exist in a collection called "products", the following function is needed:
suspend fun getProductsFirestore(): List<Product> {
var products = listOf<Product>()
try {
products = productsRef.get().await().documents.mapNotNull { snapShot ->
snapShot.toObject(Product::class.java)
}
} catch (e: Exception) {
Log.d("TAG", e.message!!)
}
return products
}
This method can be called from within the ViewModel class:
val productsLiveData = liveData(Dispatchers.IO) {
emit(repository.getProductsFromFirestore())
}
So it can be observed in activity/fragment class:
private fun getProducts() {
viewModel.producsLiveData.observe(this, {
print(it)
//Do what you need to do with the product list
})
}
I have even written an article in which I have explained four ways in which you can read the data from Cloud Firestore:
How to read data from Cloud Firestore using get()?
In the last Google I/O, Jose Alcerreca and Yigit Boyar told us that we should no longer use LiveData to fetch data. Now we should use suspend functions for one-shot fetches and use Kotlin's Flow to create a data stream. I agree that coroutines are great for one-shot fetching or other CRUD operations, such as inserting, etc. But in cases where I need a data stream, I don’t understand what advantages Flow gives me. It seems to me that LiveData is doing the same.
Example with Flow:
ViewModel
val items = repository.fetchItems().asLiveData()
Repository
fun fetchItems() = itemDao.getItems()
Dao
#Query("SELECT * FROM item")
fun getItems(): Flow<List<Item>>
Example with LiveData:
ViewModel
val items = repository.fetchItems()
Repository
fun fetchItems() = itemDao.getItems()
Dao
#Query("SELECT * FROM item")
fun getItems(): LiveData<List<Item>>
I would also like to see some examples of projects using coroutines and Flow to work with the Room or Retrofit. I found only a Google's ToDo sample where coroutines are used for one-shot fetching and then manually refetch data on changing.
Flow is sort of a reactive stream ( like rxjava ). There are a bunch of different operators like .map, buffer() ( anyway less no. Of operator compared to rxJava ). So, one of the main difference between LiveData and Flow is that u can subscribe the map computation / transformation in some other thread using
flowOn(Dispatcher....).
So, for eg :-
flowOf("A","B","C").map { compute(it) }.flowOn(Dispatchers.IO).collect {...} // U can change the execution thread of the computation ( by default its in the same dispatcher as collect )
With LiveData and map , the above can't be achieved directly !
So its recommended to keep flow in the repository level , and make the livedata a bridge between the UI and the repository !
The main difference is that
Generally a regular flow is not lifecycle aware but liveData is lifecyle aware. ( we can use stateFlow in conjunction with repeatOnLifecycle to make it lifecycle aware )
flow has got a bunch of different operators which livedata doesn't have !
But again , Its up to u how do u wanna construct your project !
As the name suggests, you can think of Flow like a continuous flow of multiple asynchronously computed values. The main difference between LiveData and Flow, from my point of view, is that a Flow continuously emits results while LiveData will update when all the data is fetched and return all the values at once. In your example you are fetching single values, which is not exactly what Flow was dsigned for [update: use StateFlow for that].
I don't have a Room example but let's say you are rendering something that takes time, but you wanna display results while rendering and buffering the next results.
private fun render(stuffToPlay: List<Any>): Flow<Sample> = flow {
val sample = Sample()
// computationally intensive operation on stuffToPlay
Thread.sleep(2000)
emit(sample)
}
Then in your 'Playback' function you can for example display the results where stuffToPlay is a List of objects to render, like:
playbackJob = GlobalScope.launch(Dispatchers.Default) {
render(stuffToPlay)
.buffer(1000) // tells the Flow how many values should be calculated in advance
.onCompletion {
// gets called when all stuff got played
}
.collect{sample ->
// collect the next value in the buffered queue
// e.g. display sample
}
}
An important characteristic of Flow is that it's builder code (here render function) only gets executed, when it gets collected, hence its a cold stream.
You can also refer to the docs at Asynchronous Flow
Considering that Flow is part of Kotlin and LiveData is part of the androidx.lifecycle library, I think that Flow is used as part of the uses cases in clean architecture (without dependencies to the framework).
LiveData, on the other hand, is lifecycle aware, so is a match with ViewModel
I have all my architecture using livedata at this moment, but Flow looks like an interesting topic to study and adopt.
I'm trying to implement a simple chat application on web sockets in Clean Architecture. I had to choose a db for caching all information, so I decided to use Realm, because I heard it was pretty good database for any kind of mobile applications. But when I actually faced the Realm, it turned out to be really painful experience for me to implement caching logic with it.
All problems come from applying transaction to database which then must be synced on all threads working with Realm. There seems to some kind of synchronization problem with my code. For example, I want to save my object to Realm and then query it out of.
Here I have two simple functions to save and to get chat:
fun getBackgroundLooper(): Looper {
val handlerThread = HandlerThread("backgroundThread")
if (!handlerThread.isAlive)
handlerThread.start()
return handlerThread.looper
}
fun saveChat(chat: Chat): Completable {
val realmChat = ChatMapper.domainToCache(chat)
return Completable.create { e ->
val realm = Realm.getDefaultInstance()
realm.executeTransactionAsync({
it.insertOrUpdate(realmChat)
}, {
realm.close()
e.onComplete()
}, {
realm.close()
e.onError(it)
})
// Subscribe on background looper thread
// to be able to execute async transaction
}.subscribeOn(AndroidSchedulers.from(getBackgroundLooper()))
}
fun getSingleChat(chatId: String): Single<Chat> {
return Single.defer {
val realm = Realm.getDefaultInstance()
realm.isAutoRefresh = true
val realmChat = realm.where(RealmChat::class.java)
.equalTo("id", chatId).findFirstAsync()
if (realmChat.isValid) {
realmChat.load()
val chat = ChatMapper.cacheToDomain(realmChat)
realm.close()
Single.just(chat)
}
realm.close()
Single.error<Chat>(ChatNotExistException())
// Subscribe on background looper thread
// to be able to execute auto refreshing
}.subscribeOn(AndroidSchedulers.from(getBackgroundLooper()))
}
So, when I try to run simple code like this
remote.getChat().flatMap {
cache.saveChat(it) //save chat to realm
.andThen(cache.getSingleChat(it.id)) //then query it by id
}
I always get no matter of what ChatNotExistException, but if I try to run query again in another attempt or after restarting the application, then the chat object gets found
I also tried many different approaches to execute this code:
I tried to use realm.refresh() in getSingleChat or not use it at all.
I tried to query chat synchronously with findFirst() and findAll() instead of findFirstAsync().
I tried querying chat on current thread without .subscribeOn().
I tried to use realm.executeTransaction() instead of async transactions.
I tried to add thread sleep between saving and querying, so that transaction may take some time to get applied and I need to wait before attempting to query the chat
I'm begging anybody to explain me what am I doing wrong and how to make this code working. I can't change the architecture of my application and use Realm objects as my view models, I need to find solution in these conditions.
But when I actually faced the Realm, it turned out to be really painful experience for me to implement caching logic with it.
Reading the docs regarding best practices help. For example, the default idea is that you define a RealmResults using an async query on the UI thread, add a change listener to it, and observe the latest emission of the database.
There is no "caching" involved in that beyond saving to the database and observing the database. Any additional complexity is added by you and is completely optional.
All problems come from applying transaction to database which then must be synced on all threads working with Realm.
All looper threads automatically make the Realm auto-refresh, therefore if addChangeListener is used as intended in the docs, then there is no need for trickery, Realm will manage the synchronization between threads.
I want to save my object to Realm and then query it out of.
realm.executeTransactionAsync({
No reason to use executeTransactionAsync when you are already on a background thread.
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction((r) -> {
// do write here
});
}
realm.where(RealmChat::class.java)
If you do import io.realm.kotlin.where, then you can do realm.where<RealmChat>().
.findFirstAsync()
No reason to use findFirstAsync() instead of findFirst() when you are already on a background thread. Also no reason to use load() when you're on a background thread, because you should be using findFirst() in the first place anyway.
You are also most likely missing a return#defer Single.just(chat) to actually return the chat if it's found. That is most likely what your original problem is.
With the handler thread things you're doing though, you might want to consider taking a look at this project called "Monarchy", as it intends to set up the ability to run queries on a background looper thread while still observing the results. It is labelled stagnant but the ideas are sound.