Welcome back, and a special thanks to one of our students, Mike Seibel,
for asking the question that prompted us to include this video.
I'm not going to go into great detail about how coroutines work.
We're looking at how to use them in our android apps
and some potential pitfalls.
If you want to understand how they work,
you'll need to learn about suspending functions amongst other things.
The blogs at medium dot com are a useful source of information
and so is the Kotlin language documentation.
JetBrains and Google have done a lot of the hard work for us
to spare developers from going into multi-threading in great detail.
Which is great
because multi-threaded programming is notoriously difficult to get right.
But even with all the work done by JetBrains and Google,
there's still a bit more that we need to know.
The first thing is launching a coroutine on the main thread
doesn't block the main thread.
Kotlin has a suspend keyword that can be applied to functions
to make them suspending functions.
How they work is beyond the scope of this course,
what's important is that the thread a suspending function runs on
can suspend the function.
It can go off and do other work and resume the function when it wants to.
That gets very technical, so let's focus on the practicalities.
I'll modify our SaveTask function
in the TaskTimerViewModel
to use a default dispatcher for one of the coroutines.
I'll use the one that saves an edited task.
Accept the import of Kotlinx.co-routines.delay
if prompted.
Don't ever write code like that.
What I'm trying to do is to slow our code down
so that we can see if it blocks the main thread.
We've got a loop that goes around 10,000 times,
performing a trivial calculation before pausing for a millisecond.
Our coroutine should take at least 10 seconds to execute.
That will make it easy to check if it blocks or not.
If it does, the main thread will be unresponsive
for about 10 seconds.
Before running the app, check the logcats filter.
We should still have ViewModelProvider
in the filter box and Regex ticked.
Okay. Run the app then edit a task.
The sort order is currently 4.
Make a note of the old value I'm going to change it to 99
and then tap save.
The UI is totally responsive.
I can tap the eye icon.
I can go into the reports.
I can even edit another task.
The UI is definitely not blocked.
After about 10 seconds,
we should expect to see AppProvidersUpdate function being called.
I'm not going to search through the logcat to see it though
because I know it's not there.
What we will find is that the TaskTimerViewModel's
OnCleared function was called.
When I switched activities to view the report,
the main activity was destroyed.
The ViewModel's lifecycle is tied to the activities lifecycle
and our ViewModel was also destroyed.
I've just introduced a serious bug into our app.
When you read warnings about not using GlobalScope
on the Internet,
be careful and make sure you understand why they suggest that.
ViewModelScope, as we discussed earlier,
will cause the coroutine to be cancelled when the ViewModel is destroyed.
That's fine if you're doing something like downloading data.
There's no point wasting battery and bandwidth to download something that will never be used
because the activity was destroyed.
Here, we're updating a local database.
The user would reasonably expect the change to the task to be saved,
but when I edit the same task again,
we can see that it hasn't been saved.
We've still got the original sort order.
That's two bits of information for the price of one.
I'll take a minute to review what we've just learned.
Before that I'll edit another task and change the sort order
to 300.
I'll clear the logcat
and tap save.
Okay. The first thing we've learned is that coroutines don't block.
We've launched our coroutine
on the default dispatcher, which runs on the main thread.
You can see that in the logcat.
The log entry from SaveTask is on the main thread,
and we saw that the app remained responsive.
The second thing we've learned
is that ViewModelScope will cause our coroutine
to be cancelled when the view model gets destroyed.
That's often the behavior you'll want.
If the user closes the app,
there would be no point in continuing a large download, for example.
But sometimes that's not what you want.
If the user saves the task, they expect it to be saved,
even if they close the app.
Be aware of that, and don't rely on a coroutine launched on
ViewModelScope for long-running tasks that must happen.
We've just seen that the coroutine can be cancelled,
resulting in the change not being saved.
If our SaveTask really was that long running,
then we should use a service or some other mechanism to save the data.
Before I remove that horrible loop from our code,
check the logcat because I've been talking
and didn't switch away from the activity our coroutine completed.
We can see the update being logged and the data has been saved.
I'll finish this video by undoing the changes to our code.
Use the IO dispatcher and remove the loop.
I'll leave you to try and break the app.
See if you can save a change to a task and switch to reports faster
than the data can be saved.
You shouldn't be able to do that even using both hands on a real device.
But if you can, then our coroutine isn't a suitable way to be saving the data.
We may want to try that after the next video
when we remove the menu while a task is being edited.
I'll see you there.