1 00:00:05,680 --> 00:00:11,160 Alright, so as I mentioned at the end of the last video, we found that many students can struggle with this concept 2 00:00:11,160 --> 00:00:17,400 of callback functions. So what we're going to do is start off by doing things in the same way as a button. 3 00:00:17,400 --> 00:00:22,760 So we're gonna set up or add a setDownloadCompleteListener function and get raw data 4 00:00:22,760 --> 00:00:26,590 first. Let's go back to that class, 5 00:00:26,590 --> 00:00:33,440 and at the top, just above the override fun onPostExecute, in other words just after the download status 6 00:00:33,440 --> 00:00:43,240 definition on line 19, we're going to add fun setDownloadCompleteListener, 7 00:00:43,240 --> 00:00:53,940 and the parentheses, then it's going to be callback object, colon MainActivity, and add our left and right curly braces as 8 00:00:53,940 --> 00:01:00,550 normal. Then we're going to type listener equals callback object. 9 00:01:00,550 --> 00:01:03,700 Now we're getting an error at the moment because we haven't declared listener. 10 00:01:03,700 --> 00:01:05,080 So let's go ahead and do that. 11 00:01:05,080 --> 00:01:15,820 We'll do that above that line, so private var listener colon MainActivity, question mark, 12 00:01:15,820 --> 00:01:24,340 equals null. And you can see that we've got a warning there about this field leaking a context object. 13 00:01:24,340 --> 00:01:29,320 Ignore that for now because it's only temporary, to show how the callback we're going to create is just the 14 00:01:29,320 --> 00:01:32,290 same as a button click listener. 15 00:01:32,290 --> 00:01:37,990 OK so we know that our onDownloadComplete function's in MainActivity, which is why our setDownload 16 00:01:37,990 --> 00:01:42,880 CompleteListener function defines our main activity type as its parameter. 17 00:01:42,880 --> 00:01:44,560 And you can see that on line 23. 18 00:01:44,560 --> 00:01:50,160 Now listener's going to be null until we call setDownloadCompleteListener to initialize it, 19 00:01:50,160 --> 00:01:54,400 and that's why we've declared it to be a nullable type on line 21. 20 00:01:54,400 --> 00:02:00,880 Now we can call the listeners onDownloadComplete function in the onPostExecute function. 21 00:02:00,880 --> 00:02:09,389 So let's go and add the code for that, after the log, so listener question mark, dot onDownload 22 00:02:09,389 --> 00:02:17,670 Complete, then parentheses, and it's going to be result comma and downloadStatus, the two arguments we're passing to it. 23 00:02:17,670 --> 00:02:23,310 Now we've got an error at the moment because result is a nullable string, and our onDownloadComplete 24 00:02:23,310 --> 00:02:26,760 function needs a non-null string type. 25 00:02:26,760 --> 00:02:32,100 Now onPostExecute's going to be passed the return value from doInBackground, 26 00:02:32,100 --> 00:02:35,460 and we've made sure that doInBackground doesn't return null. 27 00:02:35,460 --> 00:02:39,800 In fact it's return type is the non-nullable string type. 28 00:02:39,800 --> 00:02:46,290 And you can see that again here on line 26. Now changing the function signature to accept a string, 29 00:02:46,290 --> 00:02:48,170 isn't going to cause any problems here. 30 00:02:48,170 --> 00:02:53,760 So we'll do that to fix the error. I'll change that, and you can see the error then disappears. 31 00:02:53,760 --> 00:02:59,010 Now we do have to use a safe call when calling the onDownloadComplete method, 32 00:02:59,010 --> 00:03:01,200 and that's because listener might be null. 33 00:03:01,200 --> 00:03:06,900 In other words it's no guarantee that the caller will call the setDownloadCompleteListener function 34 00:03:06,900 --> 00:03:13,230 before starting the async task. Now that's definitely true, because we're not calling it yet. 35 00:03:13,230 --> 00:03:16,500 Now we need to do that before starting the async task, 36 00:03:16,500 --> 00:03:20,370 in other words, before we call the execute function in main activity. 37 00:03:20,370 --> 00:03:30,810 So let's go back to main activity, and go up to this code, and actually before the call to dot execute, in 38 00:03:30,810 --> 00:03:35,870 between the definition of our get raw data variable, we want to put it in there. So it's 39 00:03:35,870 --> 00:03:44,670 getRawData.setDownloadCompleteListener and in parentheses, this, is the argument. 40 00:03:44,670 --> 00:03:51,930 Now I'll explain what 'this' is in a moment. So let's actually try running it though to make sure it works, 41 00:03:51,930 --> 00:04:01,970 and we'll also open log cat. 42 00:04:01,970 --> 00:04:06,020 Now we can see here if we go back and have a look there's a lot of data there. 43 00:04:06,020 --> 00:04:10,990 And in fact the json data has been logged twice in the log cat, 44 00:04:10,990 --> 00:04:13,820 and you can see there's two versions of that, 45 00:04:13,820 --> 00:04:22,240 but the one we're looking at here is the logged output from onPostExecute. But a bit further down, and you can see 46 00:04:22,240 --> 00:04:27,050 onDownloadComplete is also printing out the json data aswell. 47 00:04:27,050 --> 00:04:28,460 So that's working fine overall. 48 00:04:28,460 --> 00:04:34,430 When the async task finishes executing, it calls its onPostExecute function. 49 00:04:34,430 --> 00:04:40,520 Now the onPostExecute function calls the onDownloadComplete function in the listener, which is a current 50 00:04:40,520 --> 00:04:42,620 instance of our main activity. 51 00:04:42,620 --> 00:04:49,190 And again we can see that here, onDownloadCompleteownload called, and there's our function onDownloadComplete, so 52 00:04:49,190 --> 00:04:51,880 it's being called in there. 53 00:04:51,880 --> 00:04:58,070 And this is exactly the same as what happens when you tap a button. You register your activity, or an anonymous 54 00:04:58,070 --> 00:05:00,570 inner class that it contains as a listener, 55 00:05:00,570 --> 00:05:04,370 by calling the setOnClickListener function of the button. 56 00:05:04,370 --> 00:05:10,130 Then when the button's tapped, the button calls your activities onClick function. 57 00:05:10,130 --> 00:05:18,470 So here what we're doing, we're registering our activity as a listener, according to set onDownloadComplete function. 58 00:05:18,470 --> 00:05:24,600 You can see that code being executed on line 20, and when getRawData finishes downloading, it'll call our 59 00:05:24,600 --> 00:05:27,090 onDownloadComplete function. 60 00:05:27,090 --> 00:05:31,890 That's why we're passing this to the call, to setDownloadCompleteListener in MainActivity. 61 00:05:31,890 --> 00:05:35,550 This refers to the current instance of a class. 62 00:05:35,550 --> 00:05:39,770 Now you can think of Kotlin automatically doing something like, 63 00:05:39,770 --> 00:05:48,170 if I just type it up here, private val this equals MainActivity. So 64 00:05:48,170 --> 00:05:51,370 basically think of Kotlin automatically doing something like that. 65 00:05:51,370 --> 00:05:53,150 Now it doesn't really do that, 66 00:05:53,150 --> 00:05:57,410 and I've got an error when I tried, but that's effectively what this is. 67 00:05:57,410 --> 00:06:03,500 It's an instance of a class, MainActivity in this case, that's running the code in the class. 68 00:06:03,500 --> 00:06:11,480 So by passing this, and I'll just delete this now, so by passing this down here to setDownloadcomplete 69 00:06:11,480 --> 00:06:17,120 Listener, we're telling getRawData that it should call the onDownloadComplete function 70 00:06:17,120 --> 00:06:24,240 in this instance of Mainactivity. Alright, now I'm in danger of labouring this point a bit much, so 71 00:06:24,240 --> 00:06:25,560 I'm going to move on. 72 00:06:25,560 --> 00:06:30,060 We use listeners and callbacks a lot in this course, and I will be explaining it all again in a different 73 00:06:30,060 --> 00:06:36,930 way, in a later section. But it is something that students have struggled with in the past, but it's also 74 00:06:36,930 --> 00:06:39,760 an essential part of event driven programming. 75 00:06:39,760 --> 00:06:44,190 Windows and the various Linux GUI's also make extensive use of callbacks, 76 00:06:44,190 --> 00:06:49,320 so it's not just an Android thing. It's a really good thing to learn and really fundamentally understand 77 00:06:49,320 --> 00:06:51,360 as a developer. 78 00:06:51,360 --> 00:06:57,540 OK so moving on, we're going to change that code. So the base functionality remains the same, 79 00:06:57,540 --> 00:07:00,870 but now what we're going to do is implement it differently. 80 00:07:00,870 --> 00:07:06,630 Now we might want to call our getRawData from different classes, not just from MainActivity classes, 81 00:07:06,630 --> 00:07:12,660 so that means we need to ensure there's some way that the class does have an onDownloadComplete function. 82 00:07:12,660 --> 00:07:18,900 So what we're going to do is change the way we create the getRawData object, by adding this as an argument 83 00:07:18,900 --> 00:07:24,900 when we create it. So at the moment we've got val getRawData equals getRawData parentheses. 84 00:07:24,900 --> 00:07:29,290 We're going to add this as an argument. 85 00:07:29,290 --> 00:07:35,040 We've got an error there because getRawData class is expecting that argument. 86 00:07:35,040 --> 00:07:41,460 But also what we're going to do is now delete this next line, well I'll comment it out at least, as we won't need that 87 00:07:41,460 --> 00:07:50,640 after we make this change. So going back to getRawData, 88 00:07:50,640 --> 00:07:57,960 we need a field to store the object whose method we need to call, and then we'll set that field in the constructor. 89 00:07:57,960 --> 00:08:01,770 So looking at our class definition line on line 17, 90 00:08:01,770 --> 00:08:07,780 we're going to change that, we're going to add parentheses after getRawData, and we're going to type private val 91 00:08:07,780 --> 00:08:19,200 listener colon MainActivity. I'll leave everything else as it is. Now I've declared the callback object to be of 92 00:08:19,200 --> 00:08:24,930 the type MainActivity, so that we can pass any main activity class object to it. 93 00:08:24,930 --> 00:08:27,900 There is a problem with that, and we'll look at that in a minute. 94 00:08:27,900 --> 00:08:31,800 For now we're just getting things working and understanding how it works. 95 00:08:31,800 --> 00:08:38,429 So now that we've done that, we don't need our private listener object here on line 21, and 96 00:08:38,429 --> 00:08:42,690 we also don't need this setDownloadCompleteListener function, 97 00:08:42,690 --> 00:08:49,530 so I'm going to comment that out as well. Alright so our getRawData class is definitely going to have a non- 98 00:08:49,530 --> 00:08:50,940 null listener now. 99 00:08:50,940 --> 00:08:57,150 So therefore we can remove the safe call operator from the onPostExecute method. So over here, 100 00:08:57,150 --> 00:09:00,120 we can actually get rid of that now. 101 00:09:00,120 --> 00:09:06,660 So when the download finishes and the onPostExecute function's called, it'll call our main activities 102 00:09:06,660 --> 00:09:12,140 onDownloadComplete function, and provide it with the data and the status result. 103 00:09:12,140 --> 00:09:18,310 And this is exactly the same as before, but we're just providing the listener object differently. So we're 104 00:09:18,310 --> 00:09:26,920 going to clear the log cat, got it open, clear that and we'll run it again, 105 00:09:26,920 --> 00:09:31,230 probably don't need to see the interface. So nothing's really changed 106 00:09:31,230 --> 00:09:37,310 there, you can see that we've still got the data returned. We can see that onDownloadComplete is being called, and 107 00:09:37,310 --> 00:09:42,740 we've got two copies of the adjacent data showing. So basically things have worked just like last time. 108 00:09:42,740 --> 00:09:49,220 So you can see onCreate being called there, an onCreate ending, and the data being logged in onPostExecute, and after 109 00:09:49,220 --> 00:09:56,150 logging the data onPostExecute, causing onDownloadComplete function of main activity, and a bit lower 110 00:09:56,150 --> 00:10:00,290 down in the log cat, was the data that I'm showing you on the screen now. 111 00:10:00,290 --> 00:10:03,650 That's again the data being logged again by onDownloadComplete. 112 00:10:03,650 --> 00:10:05,390 So that's how callbacks work. 113 00:10:05,390 --> 00:10:10,790 You create a function that the called object will call when something interesting happens. 114 00:10:10,790 --> 00:10:16,700 In this case we gave the getRawData instance a reference to the main activity object, by using this from 115 00:10:16,700 --> 00:10:23,130 inside the main activity class. Now the problem I've just mentioned is that there's nothing to guarantee 116 00:10:23,130 --> 00:10:29,630 that main activity does have an onDownloadComplete method. Now ours does, but none of the other main activity classes 117 00:10:29,630 --> 00:10:32,090 that we've created did. 118 00:10:32,090 --> 00:10:37,790 So the way to deal with that is by defining an interface that the calling object must implement, 119 00:10:37,790 --> 00:10:40,010 so an interface is a binding contract. 120 00:10:40,010 --> 00:10:46,840 So anything that implements our interface guarantees that it'll implement the functions we specify. 121 00:10:46,840 --> 00:10:49,470 Now this might be easier to see rather than talk about, so 122 00:10:49,470 --> 00:10:53,850 I'm going to start by defining the interface in a getRawData class. 123 00:10:53,850 --> 00:10:55,310 We're going to go back to the top here, 124 00:10:55,310 --> 00:10:58,520 close down our log cat window, and 125 00:10:58,520 --> 00:11:07,850 below our download status definition I'm going to type interface, onDownloadComplete, and open up a code 126 00:11:07,850 --> 00:11:20,360 block, and within there we're going to put fun onDownloadComplete parentheses, data colon, String comma status colon 127 00:11:20,360 --> 00:11:29,880 DownloadStatus. So to define an interface we just declare it in a very similar way to declaring a class, 128 00:11:29,880 --> 00:11:35,410 and specify the functions that must be implemented by anything that implements the interface. 129 00:11:35,410 --> 00:11:39,710 Now you don't actually write any code for the functions. That's up to the implementer. 130 00:11:39,710 --> 00:11:44,330 All we're doing is providing the name, parameters and return type if any. 131 00:11:44,330 --> 00:11:50,120 So now that we've done that we can change the type of our listener object to be onDownloadComplete. Now just like with 132 00:11:50,120 --> 00:11:55,040 classes, the convention is that interface names should start with a capital letter, 133 00:11:55,040 --> 00:11:59,540 so the interface that we're going to use is on download complete with a capital O. 134 00:11:59,540 --> 00:12:04,360 It specifies a single method which I've called onDownloadCcomplete with a lowercase o, and you can see that 135 00:12:04,360 --> 00:12:06,590 on lines 21 through 23. 136 00:12:06,590 --> 00:12:13,790 So now that we've done that, we can change our listener type. So at the moment time we've got var listener main activity. 137 00:12:13,790 --> 00:12:18,010 We can change that to OnDownloadComplete. 138 00:12:18,010 --> 00:12:21,720 So now our listener property's of type OnDownloadComplete. 139 00:12:21,720 --> 00:12:28,040 Now if I tried to run the app now we'd get an error, because main activity isn't an on download complete object. 140 00:12:28,040 --> 00:12:30,100 So we have to fix that first. 141 00:12:30,100 --> 00:12:36,320 So when I switch over to main activity, there's an error that has come up now when we create the getRawData 142 00:12:36,320 --> 00:12:43,280 object, because we can't pass an object of type main activity to a constructor that expects on download complete. 143 00:12:43,280 --> 00:12:49,850 So what we need to do to fix that, is to make main activity implement the on download complete interface. 144 00:12:49,850 --> 00:12:56,820 Now in Kotlin, we specify that by adding the interface name after the superclass in the declaration. 145 00:12:56,820 --> 00:13:04,280 So I'll come up here and start doing that. At the moment we've got class mainActivity colon appCompatActivity. I'm going to type comma, and 146 00:13:04,280 --> 00:13:12,670 if I start typing OnDownload, you can see that Android Studio offers the get raw data dot OnDownloadComplete 147 00:13:12,670 --> 00:13:14,580 interface as a suggestion. And you can't really see all of that, 148 00:13:14,580 --> 00:13:20,860 but we can see that the academy dot learn programming package there and I can press enter there, and you can see that's filled 149 00:13:20,860 --> 00:13:22,760 in for us automatically. 150 00:13:22,760 --> 00:13:28,040 So now main activity's declared as implementing the interface and can be used whenever an object of type 151 00:13:28,040 --> 00:13:30,530 on download complete is needed. 152 00:13:30,530 --> 00:13:35,630 So we can't just pass just any old main activity to the constructor of get raw data. 153 00:13:35,630 --> 00:13:38,630 We have to use one that implements the interface. 154 00:13:38,630 --> 00:13:44,030 And that means that our get raw data class can be sure that the object that it's been given does have 155 00:13:44,030 --> 00:13:49,470 the correct function for it to callback. Now if we scroll down and have a bit of a look down here, 156 00:13:49,470 --> 00:13:51,600 we can see that we've got an error here, 157 00:13:51,600 --> 00:13:57,450 and that's because we haven't marked on download complete as overriding the interface function, and that's something 158 00:13:57,450 --> 00:14:00,630 that Kotlin insists on. I'm going to type in 159 00:14:00,630 --> 00:14:07,660 override there, hope that keeps it happy, and I'm going to say a bit more about that in a minute, but 160 00:14:07,660 --> 00:14:18,130 I'm going to run the app again first just to make sure it's still working. 161 00:14:18,130 --> 00:14:23,320 Now it looks to me that everything's working fine. We've still got our two versions of the json data being 162 00:14:23,320 --> 00:14:27,310 outputted, and it seems that all the other functions are being called back correctly. 163 00:14:27,310 --> 00:14:29,170 So that looks to be working fine. 164 00:14:29,170 --> 00:14:35,770 So let's have another look at this on download complete function in main activity. 165 00:14:35,770 --> 00:14:40,770 Now we've already typed that function in rather than using the code generator, and it didn't have override 166 00:14:40,770 --> 00:14:44,880 specified. Now Kotlin insists that you mark overridden functions with 167 00:14:44,880 --> 00:14:49,210 override, and you saw that when I typed override there, the error disappeared. 168 00:14:49,210 --> 00:14:55,270 So override tells the compiler that the function that follows it is overriding an existing function 169 00:14:55,270 --> 00:15:01,810 in its superclass or an interface. Using override allows the compiler to check that the function has the 170 00:15:01,810 --> 00:15:07,480 correct name and the correct number and type of parameters. Android Studio also uses it to show these 171 00:15:07,480 --> 00:15:12,400 errors and warnings, over here on the right hand side, the right hand margin. 172 00:15:12,400 --> 00:15:20,790 Let's say for example that I typed the function name wrong. If I change it to have a capital L instead for download, 173 00:15:20,790 --> 00:15:23,450 and that by the way, is quite an easy mistake to make, 174 00:15:23,450 --> 00:15:32,780 but you can see when I do that we're suddenly getting errors. We've got an error here, and also an error up here as well. 175 00:15:32,780 --> 00:15:37,520 So the first one is this error here, 'onDownLoadComplete overrides nothing'. 176 00:15:37,520 --> 00:15:43,550 That means that no function with that name in the appcompat activity base class, or the on download complete 177 00:15:43,550 --> 00:15:47,150 interface, exists. There's no function of that name. 178 00:15:47,150 --> 00:15:49,730 And there's also an error in our base declaration 179 00:15:49,730 --> 00:15:54,920 up here. We're getting that error because the main activity no longer implements the correct function for 180 00:15:54,920 --> 00:15:56,390 the interface. 181 00:15:56,390 --> 00:16:01,400 So the override keyword allows Kotlin to check so that we don't make mistakes like this. 182 00:16:01,400 --> 00:16:06,980 So I'm going to go back and correct the spelling to onDownloadComplete to fix that error, and we'll finish the 183 00:16:06,980 --> 00:16:13,220 video here. In the next video we'll add the code for parsing the json data, so I'll see you in the next video.