G'day everyone, welcome back. Okay we've got the layout for our custom dialog.
In this video, we'll see how to display it when the user taps the about TaskTimer item,
that's in the menu. MainActivity is responsible for responding to menu items
and we'll be adding the code in there.
We're going to create a showAboutDialog function,
and call that from our onOptionsItemSelected function.
We'll start with a field to store a reference to our dialog, which I'll add after the
mTwoPane variable.
If we're going to display the dialogue in a showAboutDialog function, you
may be wondering why we need to store a reference to it in a field, instead of
using a local variable. I've sort of given that away with the comment. Okay,
we'll call the function in onOptionsItemSelected,
and we'll add the function immediately below.
This is pretty straightforward, after what we've done so far with the dialogues.
The difference when using a custom dialogue is that we have to
inflate the XML layout. That creates the view that the dialogue will be displaying.
Next we create an alert dialog.builder object to do all the work
of building our dialog for us. As I said, I'll come back to why we're using a
field aboutDialog, instead of just a local variable.
The setView function
uses our inflated view as the dialogue's contents, but you probably worked that one out.
Then we call the builder's create function to create the dialogue, and
store a reference to it in our aboutDialog field. Dialogues normally cancel
when you tap away from them on the screen. That's the default behavior.
It's possible to change that by passing false to the dialogue's setCanceledOnTouchOutside function.
If you want to prevent your dialogue from being cancelled when
the user taps away from it, that's how you do it. Just pass false to this function.
Remember they can still use the back button, though. Our dialogue won't have
any buttons, which is why I've explicitly passed true to that function.
Without buttons, the user won't have any obvious way to dismiss the dialogue, if the
default behavior changes. They'll try tapping away from it and won't know that
they have to use the back button. I prefer not to rely on default behavior,
if it's crucial to the correct functioning of the app. Okay, next we set
the title and the icon for the dialogue. Calling the builder's setTitle and setIcon
function is a correct way to do this, but that comment sort of gives the
game away. What we're doing here won't work. I'll explain why and put it right
once we've seen that it's not working. The last bit of code displays the
version information. We'll see where the version number comes from after seeing
the dialogue working. Notice that we have to use findViewById
here. we can't use a synthetic import because we have to look for the IDs in
message view. Watch out for that, because your code will compile quite happily if
you do use a synthetic import. It'll compile but it won't run, and you'll get
an error because the TextView will be null. I'll say that again because
it can be a tricky bug to find. If you try to use synthetic imports to refer to
views in a dialog, your code will compile but it will crash when you run it.
If you want to see that happening, change line 124.
Copy and paste, comment out the first line
and change aboutVersion to about_version. Android Studio adds a
synthetic import of kotlinx.android.synthetic.main.about.*
to the imports, and the code looks fine. I'll swap the comments around
and leave the incorrect line in the code.
The function finishes by showing the dialogue. We're getting a warning from
Android Studio about passing null as the route view. We've discussed that before.
It's not normally a good idea, because Android won't be able to apply styles
and so forth to the layout. There are two exceptions, and we saw the first one when
the layout we were creating was the root view, so there was no root to pass
into the inflate function. The other exception to the rule is here, when
creating a dialogue. Dialogues aren't part of the underlying activity. They
display on top of it and are styled separately. Once again, there is no root
view that we could sensibly use.
Those are the only times when you should pass
null as the root view, when inflating a layout. In all other cases, you'll have a
view that you can use as the root and you shouldn't pass null. I want to show
you how to disable these lint warnings when you're
absolutely sure that you know what you're doing, and want to ignore them.
I want to be completely clear here. This isn't a way to deal with these warnings.
Most of the time, you should fix the problem that's causing them. It's very rarely
that you should do what I'm about to show you. The warnings are there for a
reason, and just disabling them for a quiet life is not an approach you should
consider. Very occasionally, such as our call to the inflate function, the
warnings aren't relevant. It's okay to disable the warning in cases like this.
Click on the null argument in the call to inflate, and then use the light bulb
to get suggestions for how to respond to the warning. The option I want to use
here is the suppress option. That adds an annotation to this bit of code, to
suppress the warning in this place only. Anywhere else that you try to pass null
as the root view when inflating a layout, you'll still get the warning.
Choose the suppress option, and the annotation's added to the code. That removes the
warning from the function without disabling it anywhere else.
It's a useful way to prevent the warnings from appearing, when you've made a deliberate
decision to ignore them. That decision should be an informed one, though.
Don't just go disabling warnings when you really should fix them. If you're
wondering why you can't just ignore them, you can get Android Studio to stop on
each warning, when you check your code into a version control system such as
git, for example. That's a neat feature, as it helps you avoid checking in poor
quality code for your colleagues to sneer at. But if it stops on warnings
that you know you want to ignore, that'd get annoying. Suppressing the
warnings gives you a way to cope with that situation. Our dialogue code's now complete,
so run the app
and use the About TaskTimer option in the menu to see it in action.
In landscape mode, we use the information icon on the toolbar to display the About dialog.
Hopefully, you're not feeling too underwhelmed right now,
because I did warn you that the title and icon wouldn't appear. Everything else
looks fine, though, so we've just got to fix those two items. Before going back to
our code, tap away from the dialog and it should disappear. The reason we haven't
got the title and icon, is to do with where in the code we're calling the
setTitle and setIcon functions. If you call them after creating the dialogue, they
don't work. You don't get an error, they just don't do anything. We need to call
those two functions, before calling the builder.create function. I'll copy those
two lines to the clipboard, and paste them in before the call to setView and create.
I'll comment out the original calls. That way you'll still have them if you
download this source code. I'll remove this comment and we'll run the app again.
If we now check, we should get the complete dialog appearing.
I'll dismiss this dialog, rotate to landscape, and check again. We're going to rotate
back into portrait, but first, make sure the log cat's visible.
In fact, delete the contents so we can see anything that's new. When I rotate the emulator with the
dialog showing, we get an error.
You'll probably have to scroll up to see it
because it doesn't crash the app. Our app doesn't crash, but obviously, something's
not quite right and Android's logging an error.
The error is:
activity learnprogramming.academy.tasktimer MainActivity has leaked a window,
that was originally added here. So, we've got a memory leak because we're not
dismissing our dialogue. A memory leak happens when the Java garbage collection
mechanism can't reclaim the memory and other resources, that an object uses.
A dialog that isn't dismissed is a good example of how to leak memory.
We should dismiss the dialog when our activity is destroyed by Android. A good place to do
that is in the onStop function. If you're not sure why that's a suitable
place, refer back to the activity lifecycle video in section 4.
We've already got onStop at the end of the class. We added it just to see it being
called in the logcat, but now we've got a use for it.
We check that the dialog's showing, using a safe call in case it's null,
and then dismiss it if it is showing. That's why aboutDialog had to be a
field rather than a local variable. We need a reference to the dialogue so that
we can dismiss it here, in onStop.
Okay, now we shouldn't see that error in the logcat
when the dialogue's showing and we rotate the device.
There's another couple of things I want to talk about before we leave dialogues
and move on. In the next video, we'll have a look at where the version number came
from in our dialogue. After that, we'll test those links we added in the dialog,
and see how to make them work on older Android versions as well.
See you in the next one.