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.

