1 00:00:04,260 --> 00:00:06,580 G'day everyone, welcome back. 2 00:00:06,580 --> 00:00:11,500 Back in Android Studio and we're going to create an AppDialog class that we can use, 3 00:00:11,500 --> 00:00:14,700 whenever we need to show a confirmation dialog. 4 00:00:14,700 --> 00:00:20,920 Right click the package, select New, Kotlin File/Class. 5 00:00:20,920 --> 00:00:29,540 Let's call it AppDialog, and to save ourselves some typing, select Class. 6 00:00:29,540 --> 00:00:38,980 We're going to extend AppCompatDialogFragment, so we'll start by adding our usual tag, 7 00:00:38,980 --> 00:00:45,860 and then adding the extension. 8 00:00:45,860 --> 00:00:52,400 We know we're going to have to use callbacks, so let's have a think about the interface that we're going to define. 9 00:00:52,400 --> 00:00:56,280 We're going to use a dialog in a number of different places. 10 00:00:56,280 --> 00:01:00,220 We've already decided to show it when our user deletes a task 11 00:01:00,220 --> 00:01:07,620 but it could also be useful to prompt the user, if they close an AddEditFragment without saving any changes. 12 00:01:07,620 --> 00:01:11,740 A bit later, we'll be storing the timings for the tasks, 13 00:01:11,740 --> 00:01:15,300 and we'll give the user a chance to delete old records. 14 00:01:15,300 --> 00:01:18,240 That's a third place we'll use our dialog. 15 00:01:18,240 --> 00:01:21,420 Let's think about what we can do with our dialog. 16 00:01:21,420 --> 00:01:26,400 There are really only two things that can happen, once we've shown our dialog. 17 00:01:26,400 --> 00:01:31,940 The user can accept whichever action we're proposing, or they can refuse it. 18 00:01:31,940 --> 00:01:33,820 There's a third option as well. 19 00:01:33,820 --> 00:01:39,940 The user could close a dialog without responding to it at all, by using the back button, for example. 20 00:01:39,940 --> 00:01:47,380 To fully implement everything that we can do in the dialog, our interface needs to define three functions. 21 00:01:47,380 --> 00:01:57,660 We'll start with a comment to remind us what we're going to be doing, 22 00:01:57,660 --> 00:02:13,280 and then we'll define our functions. 23 00:02:13,280 --> 00:02:18,280 That would let the activity respond to anything that the user could do with our dialog. 24 00:02:18,280 --> 00:02:23,920 In this app, we're only interested in responding when a user confirms an action. 25 00:02:23,920 --> 00:02:32,230 I'll leave the other two functions, but I'll comment them out. 26 00:02:32,230 --> 00:02:35,760 If you decide you want to implement them, then you can do so, 27 00:02:35,760 --> 00:02:42,980 but remember that everything using this AppDialog class, will need to implement all the functions in the interface, 28 00:02:42,980 --> 00:02:47,700 even if it doesn't want to respond to the dialog being cancelled. 29 00:02:47,700 --> 00:02:51,000 So what are those two parameters in the function? 30 00:02:51,000 --> 00:02:56,260 Well, we're going to use this dialog class in a number of different situations, 31 00:02:56,260 --> 00:03:01,840 and it's quite possible that a single activity might want to confirm deletion of a record, 32 00:03:01,840 --> 00:03:05,420 and confirm exiting from the Edit operation. 33 00:03:05,420 --> 00:03:10,620 The activity's callback function will be called by both instances of the dialog, 34 00:03:10,620 --> 00:03:14,540 and it needs to be able to tell which dialog is responding. 35 00:03:14,540 --> 00:03:20,320 When an Activity creates its dialogs, it'll give each one a unique ID. 36 00:03:20,320 --> 00:03:27,800 It can then test this ID in the callback function, so that it knows which dialog the code is responding to. 37 00:03:27,800 --> 00:03:34,800 The second parameter in our interface functions, is the bundle that was passed when the dialog fragment was created. 38 00:03:34,800 --> 00:03:41,380 I won't explain about that just yet, because it's far easier to understand when you see it being used. 39 00:03:41,380 --> 00:03:46,080 Then you'll understand the problem that passing it back is designed to solve. 40 00:03:46,080 --> 00:03:48,340 So we'll come back to that shortly. 41 00:03:48,340 --> 00:03:55,580 Our DialogFragment is a Fragment, and we've seen that a fragment's constructor can't have any parameters. 42 00:03:55,580 --> 00:04:00,860 If we want to pass any values into DialogFragment, we use a bundle, 43 00:04:00,860 --> 00:04:04,300 just like we did with the AddEditFragment class. 44 00:04:04,300 --> 00:04:08,040 Values are stored in a bundle as a key / value pair, 45 00:04:08,040 --> 00:04:13,160 and in AddEditActivity, we use the class name as the key. 46 00:04:13,160 --> 00:04:18,100 In this DialogFragment, we're going to provide more than one value. 47 00:04:18,100 --> 00:04:20,920 I'll define a few constants for the keys. 48 00:04:20,920 --> 00:04:39,880 That way, the classes using our dialog don't have to specify the exact strings, and the keys are used consistently. 49 00:04:39,880 --> 00:04:44,680 The next bit of code's very similar to what we did in AddEditFragment. 50 00:04:44,680 --> 00:04:48,280 We need a field to store the object that we'll be calling back, 51 00:04:48,280 --> 00:04:53,120 and we set it to the calling Activity in the onAttach functions. 52 00:04:53,120 --> 00:05:01,320 I'll add the field, 53 00:05:01,320 --> 00:05:31,580 then use Android Studio to generate the onAttach and onDetach functions, using Ctrl O. 54 00:05:31,580 --> 00:05:42,220 And of course, we'll add the logging, 55 00:05:42,220 --> 00:05:46,040 Be careful when choosing the onAttach function from the dialog. 56 00:05:46,040 --> 00:05:49,860 There's a deprecated onAttach that takes an Activity argument. 57 00:05:49,860 --> 00:05:52,540 We want the one that takes a Context. 58 00:05:52,540 --> 00:05:57,100 We should check that the calling activity does implement the required interface, 59 00:05:57,100 --> 00:05:59,660 and we'll raise an exception if it doesn't. 60 00:05:59,660 --> 00:06:04,460 We did the same thing in AddEditFragment, and this is pretty standard code. 61 00:06:04,460 --> 00:06:07,860 If you're going to cast an object to an interface type, 62 00:06:07,860 --> 00:06:11,920 then it's a good idea to check that it can be of that type first. 63 00:06:11,920 --> 00:06:17,520 If it isn't, the error will be spotted by the programmer, you, pretty quickly. 64 00:06:17,520 --> 00:06:21,260 without having to wait until the callback functions are called. 65 00:06:21,260 --> 00:06:26,120 I'm going to use a slightly more complicated test in onAttach. 66 00:06:26,120 --> 00:06:30,800 We could use our dialog from an Activity, or we could use it from a Fragment. 67 00:06:30,800 --> 00:06:36,280 If we call it from an Activity, the context will be fine, and we've seen that before. 68 00:06:36,280 --> 00:06:42,920 However, if we use the dialog from a Fragment, the context will be the Fragment's Activity, 69 00:06:42,920 --> 00:06:45,880 and in that case, we don't want to call back the Activity. 70 00:06:45,880 --> 00:06:50,400 Fortunately, the Android framework supports what we're trying to do. 71 00:06:50,400 --> 00:06:55,860 Fragments have a parent fragment, that refers to the fragment that created them. 72 00:06:55,860 --> 00:07:01,360 That'll make sense when you see the code. 73 00:07:01,360 --> 00:07:17,980 We'll first try and cast the parent fragment to be a DialogEvents. 74 00:07:17,980 --> 00:07:24,940 If there is no parent fragment, then it'll be null, and we'll get a typecast exception. 75 00:07:24,940 --> 00:07:40,820 If that happens, we should have a context, the Activity, and we'll try to cast that to be a DialogEvents. 76 00:07:40,820 --> 00:07:52,280 If that fails, then the Activity doesn't implement the interface. 77 00:07:52,280 --> 00:07:58,420 And if our attempt to cast parent fragment fails, that means the fragment doesn't implement the interface. 78 00:07:58,420 --> 00:08:01,340 In both cases, we'll throw an exception. 79 00:08:01,340 --> 00:08:05,120 The onDetach function is the same as in AddEditFragment. 80 00:08:05,120 --> 00:08:10,580 We make sure that we won't attempt to call back functions, if the Activity's been destroyed. 81 00:08:10,580 --> 00:08:22,960 We'll be testing for null when we come to call the interface functions. 82 00:08:22,960 --> 00:08:29,120 For a dialog fragment, we normally set everything up in the onCreateDialog function. 83 00:08:29,120 --> 00:08:48,120 I'll get Android Studio to generate the function for us, after the onAttach function. 84 00:08:48,120 --> 00:08:52,320 The code's pretty much copied from the Android doc we were looking at earlier. 85 00:08:52,320 --> 00:08:55,480 I've used variables for the ID's and messages, 86 00:08:55,480 --> 00:08:59,920 and we've got errors because a few variables haven't been initialled yet, 87 00:08:59,920 --> 00:09:02,540 but we'll do that in a moment 88 00:09:02,540 --> 00:09:06,680 We're using an AlertDialog, rather than a Date or Time Picker, 89 00:09:06,680 --> 00:09:13,420 so we create a new AlertDialogBuilder that will build the dialog for us. 90 00:09:13,420 --> 00:09:17,160 We need to provide some basic information for the dialog. 91 00:09:17,160 --> 00:09:20,500 The message to be displayed, for example, 92 00:09:20,500 --> 00:09:23,360 and the text to appear on the two buttons. 93 00:09:23,360 --> 00:09:26,000 But this is pretty much boilerplate code, 94 00:09:26,000 --> 00:09:32,180 and you'll generally start all your dialog fragments with something like this to begin with. 95 00:09:32,180 --> 00:09:38,000 Each of the AlertDialogBuilder functions returns the instance that it was called on, 96 00:09:38,000 --> 00:09:40,720 so we can chain the function calls together, 97 00:09:40,720 --> 00:09:43,300 which we've seen done a few times now. 98 00:09:43,300 --> 00:09:49,140 Our code uses the setMessage function to tell the Builder what text to display, 99 00:09:49,140 --> 00:09:55,940 and the setPositiveButton and setNegativeButton functions to set the text on the buttons, 100 00:09:55,940 --> 00:09:58,620 and add the onClick listener to them. 101 00:09:58,620 --> 00:10:03,560 These listeners are very similar to the button onClick listeners that we've used before. 102 00:10:03,560 --> 00:10:09,020 Instead of a reference to the button, we get a reference to the dialog in the first parameter. 103 00:10:09,020 --> 00:10:13,780 We're not interested in anything about the actual dialog object that the builder creates for us, 104 00:10:13,780 --> 00:10:15,980 so we'll ignore this first parameter. 105 00:10:15,980 --> 00:10:22,340 It can be useful in a custom dialog, though, because it allows us to call the dialog's cancel function. 106 00:10:22,340 --> 00:10:24,120 We'll discuss that later. 107 00:10:24,120 --> 00:10:28,120 The second parameter, which, is the button that was clicked, 108 00:10:28,120 --> 00:10:34,420 or the position of an item, if we were allowing our dialog to allow one of a list of options to be chosen. 109 00:10:34,420 --> 00:10:38,760 We're not doing that, so we'll ignore that parameter as well. 110 00:10:38,760 --> 00:10:44,880 In fact, we'll get some suggestions for improving those calls, once we've corrected the errors. 111 00:10:44,880 --> 00:10:51,740 I've included the code that you'd use, if you want to respond to the negative button, as comments in the code. 112 00:10:51,740 --> 00:10:55,720 You'll have to include an onClick listener when setting the buttons, 113 00:10:55,720 --> 00:10:59,940 but it can be empty, as our negative button listener is here. 114 00:10:59,940 --> 00:11:05,660 Okay, our dialog would be a bit limited if it always displayed the same message. 115 00:11:05,660 --> 00:11:10,380 We'll be passing in the message that we want to display, in the arguments bundle, 116 00:11:10,380 --> 00:11:15,220 in the same way as we passed task information to the AddEditFragment. 117 00:11:15,220 --> 00:11:19,160 We'll also want the buttons to reflect what the dialog's all about, 118 00:11:19,160 --> 00:11:21,940 so we pass in text for the two buttons. 119 00:11:21,940 --> 00:11:26,440 I've already mentioned the dialogue ID, and that will appear in our bundle. 120 00:11:26,440 --> 00:11:43,300 We'll start by retrieving the bundle that was provided, when this dialog fragment was created, and extract the values from it. 121 00:11:43,300 --> 00:11:53,480 I've already created the constants that we'll use as keys, for retrieving the values of the bundle. 122 00:11:53,480 --> 00:11:56,080 As long as there is a bundle, 123 00:11:56,080 --> 00:12:01,660 we can use those keys to retrieve the values. 124 00:12:01,660 --> 00:12:06,940 Our DialogFragment relies on being provided with at least a message to display, 125 00:12:06,940 --> 00:12:10,840 and our callback interface requires a dialogue ID. 126 00:12:10,840 --> 00:12:22,800 So if arguments is null, we'll raise an exception 127 00:12:22,800 --> 00:12:29,860 If our dialog's used without these being provided, that exception will indicate what the programmer is doing wrong, 128 00:12:29,860 --> 00:12:33,500 which is helpful, as we're not writing any documentation. 129 00:12:33,500 --> 00:12:37,740 That exception will be thrown as soon as the dialog is displayed. 130 00:12:37,740 --> 00:12:41,940 It'll be picked up even before the app enters formal testing. 131 00:12:41,940 --> 00:12:48,160 It's intended to help programmers, and users of the app will never see it. 132 00:12:48,160 --> 00:12:52,640 Okay, I've made the text for the buttons optional. 133 00:12:52,640 --> 00:13:05,160 If the text isn't provided for the two buttons, they'll default to showing OK, and Cancel. 134 00:13:05,160 --> 00:13:07,240 One thing you may have noticed, 135 00:13:07,240 --> 00:13:16,180 is that I'm talking about text, but I'm retrieving int values, resource IDs, for the positive and negative button captions. 136 00:13:16,180 --> 00:13:21,520 It's a good idea to store all strings that users will see in string resources. 137 00:13:21,520 --> 00:13:32,300 So we're passing the string resource IDs in our bundle, instead of the strings themselves. 138 00:13:32,300 --> 00:13:41,600 The setMessage, setPositiveButton and setNegativeButton functions can accept either a string or an int argument. 139 00:13:41,600 --> 00:13:45,440 The functions have been overloaded in the DialogBuilder class. 140 00:13:45,440 --> 00:13:49,160 If you want to work with strings instead, then you can. 141 00:13:49,160 --> 00:13:53,660 Control click on the setMessage function 142 00:13:53,660 --> 00:13:55,180 to view the source, 143 00:13:55,180 --> 00:14:02,860 and you'll see the two overloaded functions; one taking an int ID, and the other a CharSequence. 144 00:14:02,860 --> 00:14:07,080 The Java String class implements this CharSequence interface, 145 00:14:07,080 --> 00:14:09,860 so a String is a CharSequence. 146 00:14:09,860 --> 00:14:13,260 If you're going to use string resources, and you should, 147 00:14:13,260 --> 00:14:17,360 then it makes sense to extract the resources as late as possible. 148 00:14:17,360 --> 00:14:23,040 There's not much point extracting the strings in MainActivity, if this dialog is never shown. 149 00:14:23,040 --> 00:14:28,160 That's why I'm passing the resource IDs, rather than the strings. 150 00:14:28,160 --> 00:14:30,540 The reason we don't do that for the message, 151 00:14:30,540 --> 00:14:36,120 is because that may have to be built up by combining a resource string with some other values, 152 00:14:36,120 --> 00:14:39,180 such as a task name or task ID. 153 00:14:39,180 --> 00:14:41,960 Our dialog doesn't know anything about tasks, 154 00:14:41,960 --> 00:14:49,040 so we have to let the calling program extract the string resource, and add any extra values that it needs. 155 00:14:49,040 --> 00:15:00,060 It is likely that the message can contain variable text, but it's quite unlikely that the button's caption would. 156 00:15:00,060 --> 00:15:03,580 Now that we've finished the code, all the errors have gone, 157 00:15:03,580 --> 00:15:08,840 except for our oc and cancel resource IDs that we're using as the defaults. 158 00:15:08,840 --> 00:15:19,180 Rather than editing the res/value/strings.xml file, click on ok, 159 00:15:19,180 --> 00:15:22,480 then drop down from the light bulb that appears. 160 00:15:22,480 --> 00:15:27,920 We want the first option, Create string value resource 'ok'. 161 00:15:27,920 --> 00:15:30,180 The resource name's filled in for us. 162 00:15:30,180 --> 00:15:35,000 All we need to do is to provide the resource value, OK. 163 00:15:35,000 --> 00:15:41,920 We can do the same thing for the cancel. 164 00:15:41,920 --> 00:15:47,420 This time, the resource value is Cancel. 165 00:15:47,420 --> 00:15:49,220 Right, let's end this video here. And I'll see you in the next one.