1
00:00:05,200 --> 00:00:09,600
Welcome back, and a special thanks to one of our students, Mike Seibel,

2
00:00:09,850 --> 00:00:13,210
for asking the question that prompted us to include this video.

3
00:00:13,760 --> 00:00:17,760
I'm not going to go into great detail about how coroutines work.

4
00:00:18,420 --> 00:00:21,020
We're looking at how to use them in our android apps

5
00:00:21,020 --> 00:00:22,620
and some potential pitfalls.

6
00:00:23,420 --> 00:00:25,420
If you want to understand how they work,

7
00:00:25,920 --> 00:00:29,580
you'll need to learn about suspending functions amongst other things.

8
00:00:30,680 --> 00:00:34,280
The blogs at medium dot com are a useful source of information

9
00:00:34,680 --> 00:00:37,280
and so is the Kotlin language documentation.

10
00:00:37,830 --> 00:00:41,190
JetBrains and Google have done a lot of the hard work for us

11
00:00:41,590 --> 00:00:44,950
to spare developers from going into multi-threading in great detail.

12
00:00:45,450 --> 00:00:46,450
Which is great

13
00:00:47,000 --> 00:00:51,000
because multi-threaded programming is notoriously difficult to get right.

14
00:00:52,800 --> 00:00:56,100
But even with all the work done by JetBrains and Google,

15
00:00:56,100 --> 00:00:58,600
there's still a bit more that we need to know.

16
00:00:58,900 --> 00:01:03,000
The first thing is launching a coroutine on the main thread

17
00:01:03,300 --> 00:01:05,000
doesn't block the main thread.

18
00:01:05,500 --> 00:01:09,600
Kotlin has a suspend keyword that can be applied to functions

19
00:01:09,600 --> 00:01:11,600
to make them suspending functions.

20
00:01:12,500 --> 00:01:14,800
How they work is beyond the scope of this course,

21
00:01:15,460 --> 00:01:19,860
what's important is that the thread a suspending function runs on

22
00:01:20,110 --> 00:01:21,710
can suspend the function.

23
00:01:22,510 --> 00:01:26,940
It can go off and do other work and resume the function when it wants to.

24
00:01:27,540 --> 00:01:31,200
That gets very technical, so let's focus on the practicalities.

25
00:01:42,080 --> 00:01:45,080
I'll modify our SaveTask function

26
00:01:45,080 --> 00:01:47,080
in the TaskTimerViewModel

27
00:01:47,380 --> 00:01:50,160
to use a default dispatcher for one of the coroutines.

28
00:01:50,820 --> 00:01:53,820
I'll use the one that saves an edited task.

29
00:02:30,320 --> 00:02:34,320
Accept the import of Kotlinx.co-routines.delay

30
00:02:34,320 --> 00:02:35,320
if prompted.

31
00:02:36,220 --> 00:02:37,820
Don't ever write code like that.

32
00:02:38,320 --> 00:02:41,120
What I'm trying to do is to slow our code down

33
00:02:41,120 --> 00:02:43,620
so that we can see if it blocks the main thread.

34
00:02:44,220 --> 00:02:47,520
We've got a loop that goes around 10,000 times,

35
00:02:47,520 --> 00:02:51,510
performing a trivial calculation before pausing for a millisecond.

36
00:02:52,710 --> 00:02:56,210
Our coroutine should take at least 10 seconds to execute.

37
00:02:56,710 --> 00:02:59,210
That will make it easy to check if it blocks or not.

38
00:02:59,760 --> 00:03:03,060
If it does, the main thread will be unresponsive

39
00:03:03,060 --> 00:03:04,560
for about 10 seconds.

40
00:03:04,560 --> 00:03:07,560
Before running the app, check the logcats filter.

41
00:03:10,360 --> 00:03:13,360
We should still have ViewModelProvider

42
00:03:13,360 --> 00:03:16,360
in the filter box and Regex ticked.

43
00:03:17,460 --> 00:03:20,060
Okay. Run the app then edit a task.

44
00:03:23,560 --> 00:03:25,860
The sort order is currently 4.

45
00:03:26,360 --> 00:03:29,660
Make a note of the old value I'm going to change it to 99

46
00:03:30,460 --> 00:03:31,960
and then tap save.

47
00:03:34,160 --> 00:03:36,360
The UI is totally responsive.

48
00:03:37,260 --> 00:03:39,260
I can tap the eye icon.

49
00:03:39,810 --> 00:03:41,310
I can go into the reports.

50
00:03:42,110 --> 00:03:44,110
I can even edit another task.

51
00:03:49,610 --> 00:03:52,610
The UI is definitely not blocked.

52
00:03:53,210 --> 00:03:54,810
After about 10 seconds,

53
00:03:55,170 --> 00:03:59,470
we should expect to see AppProvidersUpdate function being called.

54
00:04:00,020 --> 00:04:02,620
I'm not going to search through the logcat to see it though

55
00:04:02,620 --> 00:04:04,220
because I know it's not there.

56
00:04:05,220 --> 00:04:08,820
What we will find is that the TaskTimerViewModel's

57
00:04:08,820 --> 00:04:11,220
OnCleared function was called.

58
00:04:16,720 --> 00:04:19,220
When I switched activities to view the report,

59
00:04:19,220 --> 00:04:20,880
the main activity was destroyed.

60
00:04:21,430 --> 00:04:24,990
The ViewModel's lifecycle is tied to the activities lifecycle

61
00:04:25,390 --> 00:04:27,490
and our ViewModel was also destroyed.

62
00:04:27,890 --> 00:04:31,090
I've just introduced a serious bug into our app.

63
00:04:31,590 --> 00:04:34,890
When you read warnings about not using GlobalScope

64
00:04:34,890 --> 00:04:35,990
on the Internet,

65
00:04:35,990 --> 00:04:39,350
be careful and make sure you understand why they suggest that.

66
00:04:39,850 --> 00:04:42,450
ViewModelScope, as we discussed earlier,

67
00:04:42,950 --> 00:04:46,820
will cause the coroutine to be cancelled when the ViewModel is destroyed.

68
00:04:47,070 --> 00:04:50,070
That's fine if you're doing something like downloading data.

69
00:04:50,430 --> 00:04:54,630
There's no point wasting battery and bandwidth to download something that will never be used

70
00:04:54,990 --> 00:04:56,990
because the activity was destroyed.

71
00:04:57,190 --> 00:05:00,190
Here, we're updating a local database.

72
00:05:00,590 --> 00:05:04,190
The user would reasonably expect the change to the task to be saved,

73
00:05:04,690 --> 00:05:06,690
but when I edit the same task again,

74
00:05:07,190 --> 00:05:09,090
we can see that it hasn't been saved.

75
00:05:09,690 --> 00:05:11,990
We've still got the original sort order.

76
00:05:12,980 --> 00:05:15,940
That's two bits of information for the price of one.

77
00:05:16,240 --> 00:05:18,240
I'll take a minute to review what we've just learned.

78
00:05:19,040 --> 00:05:23,140
Before that I'll edit another task and change the sort order

79
00:05:23,140 --> 00:05:24,130
to 300.

80
00:05:31,630 --> 00:05:33,130
I'll clear the logcat

81
00:05:35,030 --> 00:05:36,020
and tap save.

82
00:05:38,680 --> 00:05:42,930
Okay. The first thing we've learned is that coroutines don't block.

83
00:05:43,530 --> 00:05:45,130
We've launched our coroutine

84
00:05:45,130 --> 00:05:48,130
on the default dispatcher, which runs on the main thread.

85
00:05:53,120 --> 00:05:55,020
You can see that in the logcat.

86
00:05:55,620 --> 00:05:59,020
The log entry from SaveTask is on the main thread,

87
00:05:59,520 --> 00:06:01,820
and we saw that the app remained responsive.

88
00:06:02,370 --> 00:06:03,970
The second thing we've learned

89
00:06:03,970 --> 00:06:07,370
is that ViewModelScope will cause our coroutine

90
00:06:07,370 --> 00:06:10,170
to be cancelled when the view model gets destroyed.

91
00:06:10,830 --> 00:06:12,830
That's often the behavior you'll want.

92
00:06:13,230 --> 00:06:14,830
If the user closes the app,

93
00:06:14,830 --> 00:06:18,330
there would be no point in continuing a large download, for example.

94
00:06:19,210 --> 00:06:21,510
But sometimes that's not what you want.

95
00:06:22,170 --> 00:06:25,770
If the user saves the task, they expect it to be saved,

96
00:06:26,070 --> 00:06:27,570
even if they close the app.

97
00:06:28,270 --> 00:06:32,390
Be aware of that, and don't rely on a coroutine launched on

98
00:06:32,790 --> 00:06:37,190
ViewModelScope for long-running tasks that must happen.

99
00:06:38,090 --> 00:06:40,890
We've just seen that the coroutine can be cancelled,

100
00:06:40,890 --> 00:06:43,390
resulting in the change not being saved.

101
00:06:44,270 --> 00:06:47,170
If our SaveTask really was that long running,

102
00:06:47,170 --> 00:06:51,160
then we should use a service or some other mechanism to save the data.

103
00:06:51,820 --> 00:06:54,320
Before I remove that horrible loop from our code,

104
00:06:54,620 --> 00:06:57,520
check the logcat because I've been talking

105
00:06:57,520 --> 00:07:01,620
and didn't switch away from the activity our coroutine completed.

106
00:07:02,220 --> 00:07:06,520
We can see the update being logged and the data has been saved.

107
00:07:07,120 --> 00:07:10,620
I'll finish this video by undoing the changes to our code.

108
00:07:10,620 --> 00:07:13,220
Use the IO dispatcher and remove the loop.

109
00:07:27,020 --> 00:07:28,820
I'll leave you to try and break the app.

110
00:07:29,320 --> 00:07:33,920
See if you can save a change to a task and switch to reports faster

111
00:07:33,920 --> 00:07:35,520
than the data can be saved.

112
00:07:36,070 --> 00:07:40,070
You shouldn't be able to do that even using both hands on a real device.

113
00:07:40,620 --> 00:07:45,420
But if you can, then our coroutine isn't a suitable way to be saving the data.

114
00:07:46,020 --> 00:07:48,720
We may want to try that after the next video

115
00:07:49,080 --> 00:07:52,380
when we remove the menu while a task is being edited.

116
00:07:53,040 --> 00:07:54,040
I'll see you there.

