Introduction
- This series is not going on the Android Room database. Anything I find cool about the Room database I will put here.
Youtube version
GitHub code
Before you get started
- In order for this tutorial to be useful to you please make sure that you followed the tutorial, HERE, or at least have a database up and running on your android app.
Getting started
- So this tutorial is going to focus on how you can retrieve a single value from Room database and display it to the UI. I want to stress this again, If you do not have a database up and running please follow the official Room code lab, HERE.
The DAO
- Starting in the data access object(DAO), we all know this is the area where we specify the SQL queries and associate them with method calls. Since we want to retrieve one full row of the table, we make this request:
@Query("SELECT * FROM calf WHERE calf.id==:calfId")
suspend fun findCalf(calfId:Long):Calf
- The code is pretty straight forward, the only thing that is worth pointing out is the
suspend
keyword. But before we can talk about this keyword we need to talk aboutCoroutines
Coroutines
- As the documentation states:
coroutine is an instance of suspendable computation
, which means that code inside of a coroutines runs asynchronously. So are coroutines Kotlin's version of Threads? No they are not. Conceptually coroutines are similar to threads in that they take a block of code and run it asynchronously. But coroutines actually run on top of a threads and are actually not even bound to a specific thread. They can start on one thread and finish on another. A good visual representation of a coroutine can be found, HERE. - I also want to point out that code inside of a coroutine runs sequentially(This will be important later).
Suspend functions
- Now that we have a basic understanding that a coroutine is a piece of asynchronous code, we can talk about the
suspend
keyword. The first thing to point out about a suspend function is that it can only be called from another suspend function or inside a coroutine. The main idea behind this function modifier is to be able to allow us to program more declaratively and to get away from the typicalcallback hell
. Although, technically speaking under the hood, the Kotlin compiler will turn all the suspend functions into optimized callbacks for us. - When we see a
suspend
function, that function must be called from inside another suspend function or a coroutine.
Repository
- The next layer inside this architecture is the repository layer. While not necessary it is considered a best practice to a repository layer. It's main job is to provide a clean API for data access to the rest of the application. The code in the repository layer will look like this:
suspend fun findCalf(calfId:Long):Calf{
return calfDao.findCalf(calfId)
}
- The code is nothing we have not seen before. The only thing to note is that we are calling the
calfDao.findCalf(calfId)
method which as we know is a suspend function, which means thatfindCalf()
must also be a suspend function
ViewModel
- This is where things start to get a little bit more complicated. For example, our method looks like this:
suspend fun findCalf(calfId:Long):Calf{
val deferred:Deferred<Calf> = viewModelScope.async {
repository.findCalf(calfId)
}
return deferred.await()
}
There are a lot of weird and fancy words, but lets start with
async{...}
. async is called a coroutine builder, remember that I said suspend functions need to be called inside of a coroutine or a suspend function wellasync{}
creates a coroutine for us. Now each coroutine must have acoroutine scope
, which is essentially the life time of the coroutine. For our code the coroutine scope is theviewModelScope
, which is a specific scope provided to us by Android.viewModelScope.async{...}
means that the code inside of this coroutine is scoped to this ViewModel and if ViewModel gets shut down then so does the coroutine.I am sure you are probably use to
launch{}
instead of theasync{}
. The main difference between the two is that async returns what is called aDeferred
object. This type of object is similar to aPromise
ofFuture
. Basically its an object that will return at some point in the future. With our code, it returns aDeferred<Calf>
which is a deferred object that contains a Calf object. Ok but our function returns a calf, so how do we get our Calf outside of the deferred object? We do that by calling theawait()
method on the deferred object, which will make the function suspend while it waits for the method to return. Which means that this function must be asuspend
function which means that this function must be called from a coroutine or another suspend function.
Connection to the UI
- So this is another part of the blog post can get a little weird so listen up!!
- We start inside of our Fragment class's
onViewCreated()
method and we know that we need to create a coroutine to call ourfindCalf()
method on the ViewHolder, we do so like this:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
CoroutineScope(Main).launch {
foundCalf = calfViewModel.findCalf(calfId)
}
- The most important thing to point out is
CoroutineScope(Main)
. This is how we can interact with the UI, Android does not allow us to interact with the UI if we are on a different thread besides the Main thread. SoCoroutineScope(Main)
switches our coroutine scope over to the Main thread. We can now use the Calf object stored inside offoundCalf
to update our UI.
Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.
Top comments (1)
Hello Tristan, I love the sample you have here; so much so I'm trying to use it for a project I have. However too late I realized you are using XML ui's and my project is with jetpack compose.
I was wondering if I could pick your brain with some stumbling blocks I have.
Thanks and congrats on a beatiful code.
Ray.