You know, when I was doing my planning, I expected Part 3 of this tutorial to get us all the way to rendering a window containing the buddy list. So it’s somewhat amusing that this time, I don’t expect to be at that state at the end of this part. Instead, I want to focus on a few problems and annoyances with the existing code. Never fear, though, we’ll definitely be back to coding Kivy interfaces in Part 5!
In this part, we’ll focus on fixing a problem with the interface, handling errors on login, and taking care of that need to call “disconnect” on the xmpp object after displaying the buddy list. Be forewarned however! You may also encounter digressions on version control, debugging, and testing.
If you’ve gotten lost or want to jump in without doing the previous tutorials, you can start from the example repository as of the end of part 3:
git clone https://github.com/buchuki/orkiv.git git checkout end_part_three
Table Of Contents
Here are links to other parts of this tutorial.
- Part 1: Introduction to Kivy
- Part 2: A basic KV Language interface
- Part 3: Handling Events
- Part 4: Code and Interface Improvements
- Part 5: Rendering a Buddy List
- Part 6: ListView Interaction
- Part 7: Receiving messages and interface fixes
- Part 8: Width based layout
- Part 9: Deploying your Kivy application
Handling tab and enter events on text input
When I was testing the interface in part 2, I found that I naturally wanted to tab from one textfield to another, a common paradigm for desktop interfaces. Most desktop oriented toolkits have built-in support for what they call “tab order”; the order in which focus switches from one field to another when the tab key is pressed. Kivy doesn’t supply any tools for this. I think this highlights both Kivy’s emphasis on keeping a simple API and the fact that a lot of it was designed to work with mobile devices, which typically interact by touch and have neither physical keyboards nor a tab key. However, that doesn’t mean we can’t tab between our fields, it just means we have to do a bit more legwork.
Legwork generally starts with a web search. Lately, I wonder if I shouldn’t exercise my brain to come up with a solution on my own before turning to a search engine. However, deadlines are always tight, and Stack Overflow so often has an answer. Now, the code provided there doesn’t work quite the way I want it to, but it does tell us how to move forward in solving this task. Let’s get our hands dirty. We can start by adding a new class to our
from kivy.uix.textinput import TextInput # At the top class AccountDetailsTextInput(TextInput): next = ObjectProperty() def _keyboard_on_key_down(self, window, keycode, text, modifiers): if keycode == 9: # 9 is the keycode for self.next.focus = True elif keycode == 13: # 13 is the keycode for self.parent.parent.parent.login() # this is not future friendly else: super(AccountDetailsTextInput, self)._keyboard_on_key_down( window, keycode, text, modifiers)
Yikes! That’s a little bit more complex piece of code than we’ve seen so far. It’s a good thing it’s so short! Let’s go through each staement. We start by creating a new widget called
AccountDetailsTextInput that inherits from
TextInput. This means that it looks and behaves exactly like a
TextInput object… because it is one! However, we modify it a bit. First, we give it a
next ObjectProperty just like we did to register
TextInputs as properties on the
AccountDetailsForm class in Part 3. We’ll be assigning a value to this
next property inside the KV Language file later.
Then we override the
_keyboard_on_key_down method on
TextInput. You’re probably wondering how I knew that was the method I needed to override. Admittedly, the Stack Overflow answer provided a good hint, but I also read through the Kivy source code to make sure I knew what I was overriding and how it worked. Reading through other people’s source code can be intimidating, but is is the absolute best way to understand how something works. It’s also extremely educational, especially if you come across constructs that you don’t understand and have to look them up.
If you’re not familiar with Object Oriented Programming, you might want an explanation of what “overriding” is. Basically, whenever this method is called, we want it to do something different from what would happen if the same method were called on the parent class. Specifically, we want to handle the and keypresses in a different way.
So that’s what the conditional inside the method does. It checks the keycode value to see if it is 9 or 13. I didn’t know that the keycode was a tuple, but the source code and stack overflow question both indicated that the first value stores the keycode. I do happen to know that 9 and 13 are the keycodes for tab and enter because I’ve written similar handlers before. Can you think of a way (other than the aforementioned search engine) to determine what the keycode is for a given keypress?
Instead of the conditional in the code above, you could simply do a
print(keycode) inside this method and hook up the appropriate KV language syntax as we’ll discuss below. When you run the program, you’d be able to see the output on the console when the tab or enter key was pressed. There’s the information you need. Run with it!
The line where we focus the next object property is pretty straightforward; we just set a boolean value on it and let Kivy take care of the details. However, the line for the enter button is a bit tricky. To be honest, I hate this line of code. It’s very fragile. It calls
self.parent to get the parent of the text input, which is a
GridLayout, then calls
.parent on that which yelds a
BoxLayout (we’re navigating up through the
orkiv.kv hierarchy here), and calls parent one more time to get the
AccountDetailsForm. Then we can finally call the
login method on that, to do the same thing as if we had clicked the login button. Thus users can enter their details, tab to the next box, and login without lifting their hands from the keyboard!
The reason I hate this line of code is that future changes in
orkiv.kv could easily break it. If we decide to insert another
Layout into the mix, this line will need to have another
.parent added. If we decide to remove the surrounding
BoxLayout and put the
GridLayout as a direct child of
AnchorLayout, it will again break. Worse, if we made such a change and ran the program, we might see that the new layout looked exactly as we wanted it to, but totally forget that we needed to test the tab-order widget.
There are other ways to get access to the
AccountDetailsForm and its
login method, but I’m not too keen on them either. We could call
Orkiv.get_running_app().root. Or we could add an
ObjectProperty to the
AccountDetailsTextInput and set that property to point at the magic root variable. This latter is probably the most extensible, but it means that the Kivy Language file needs to be a bit uglier. I chose to stick with the
.parent notation, but to include a comment to remind me that if I look at this section of code in the future, it would be ideal if I had an insight for how to implement it more elegantly.
Finally, we have the weird
else clause which covers any case where the user has presed a key other than tab or enter. In that event, we just want the parent
TextInput class to do what it would normally do; display the character in the text field on the screen. So we use the
super builtin and pass it first the class we want to get the parent of, and the instance of that class, namely
self. This returns basically the same object as
self but as if it was an instance of
TextInput instead of
AccountDetailsTextInput. Then when we call
_keyboard_on_key_down on that class, we get the default behavior for the class.
The rather arcane syntax of super is much simpler in Python 3, where we could just call
super._keyboard_on_key_down The two argument syntax is still supported, as it is necessary to support a feature called multiple inheritance. That’s something I recommend you don’t learn about too early, not because it is a particularly complicated construct, but because it is, in my opinion, much less useful than it appears and you are quite likely to misapply it!
Oy gevalt, but that was a lot of explanation for not very much code. Luckily, you can probably figure out the changes to the Kivy Language file for yourself:
Label: text: "Server" AccountDetailsTextInput: id: server_input next: username_input Label: text: "Username" AccountDetailsTextInput: id: username_input next: password_input Label: text: "Password" AccountDetailsTextInput: next: server_input password: True id: password_input
We did two things to each
TextInput here. First, we changed it to be an instance of the new
AccountDetailsTextInput and second, we added a
next attribute to each of them. This
next points at the id of whichever input box it makes sense to be the next in the tab order.
But what about invalid logins?
Have you tried intentionally or accidentally entering invalid account details into this form and submitting it? The application dies outright, which is neither optimal nor friendly. Users are not infallible. In fact, users can be incredibly dedicated to totally breaking your fancy interface! Let’s try to anticipate some of their mistakes and supply an error message that makes a little sense instead of just crashing.
Let me tell you up front, this turned out to be harder than I expected! I don’t want to do anything too complicated for the intended audience of this tutorial. I ended up poking around in the sleekxmpp source code quite a bit to come up with this solution. I don’t like the results, but I saved it for you on a separate git branch. Too many tutorials pretend that the solution they present to you is the obvious one; instead, I want you to see that programming is often all about exploration and backing up and trying again.
The problem with the code I linked is that it locks up the interface so it’s hard to interact with. Since nothing can happen until connection has occurred anyway, I think it will be better to pop up a window that tells the user to wait while we perform the connection.
Before I show you the code, I want to describe how I was testing it as I developing it. I performed several different tasks and changed the code until all of them were working. This was time consuming (and the reason this tutorial is a little late) and tricky. Each time I changed things, I had to go through all the tasks to make sure that fixing one problem hadn’t broken other things. Often it had, which meant I had something else to fix and test. However, at the end of this section, you’ll be able to perform all these tasks with expected results:
- Log into my personal jabber server and see the buddy list
- Log into a throw away google account and see the buddy list
- Click the login button without entering any details and get an error message to try again
- Enter credentials for a host that does not exist and get an error message to try again
- Enter invalid credentials for the google account and get an error message to try again
Let’s start by building a Kivy Modal View widget. A
ModalView essentially sits in front of the other widgets, by default taking up the whole window, though you can make it look like it’s floating on top of the interface by giving it a size. How did I know this was the widget I wanted? I didn’t. I had to search through the Kivy API library for what I wanted. I first stumbled across the Popup widget, but it has extra features that we don’t want. However, I saw that it inherits from
ModalView and that’s exactly what we need.
So we’re going to create a
ModalView object that notifies the user that a connection is being attempted. If the credentials are invalid, it will give them a short message and invite them to return to the login widget and try again. If they are valid, it will print the Buddy List on itself. Let’s start adding this new class to the
from kivy.uix.modalview import ModalView # At the top of the file from kivy.uix.label import Label class ConnectionModal(ModalView): def __init__(self, jabber_id, password): super(ConnectionModal, self).__init__(auto_dismiss=False, anchor_y="bottom") self.label = Label(text="Connecting to %s..." % jabber_id) self.add_widget(self.label) self.jabber_id = jabber_id self.password = password
This is similar to what we did for the
AccountDetailsForm above. We create a subclass of ModalView and initialize it with a jabber_id and password. We initialize the superclass, passing in a couple of arguments to tweak the behavior. We disable
auto_dismiss because we don’t want the popup to disappear until we tell it to. Otherwise, it would disappear if the user clicked on it anywhere, even though it’s still trying to connect. We also supply
ModalView inherits from
AnchorLayout. When we add a button later, we’ll want it to pop up in front of the
Label at the bottom of the screen. Notice how we are constructing a user interface in Python code here and that it’s rather bulky compared to the KV Language way of constructing an interface. However, since there’s only one widget for now and a second one created dynamically, it doesn’t really make sense to style this class in the KV Language.
We then construct a
Label and add it to the Layout using
add_widget. We store the reference to label on
self so that we can update the text later. We similarly store the
Obviously, we aren’t constructing this widget anywhere, so it’s not going to be displayed if we run the code. Let’s rewrite the
login() method to pop up this window instead of trying to connect. Note that if you were developing on your own, you might want to keep a copy of the current
login() code in a separate buffer because we’ll be needing to adapt it to live in this new class, soon. Or you can just check your git history to see how the login method used to look.
git show HEAD^:orkiv/__main__.py will show what it looked like in the previous revision.
HEAD^ says “previous revision” to git. Yes it’s arcane syntax. You can see dozens of other ways to specify which revision you want to show in the git rev-parse manual.
Anyway, let’s display that popup in the
def login(self): jabber_id = self.username_box.text + "@" + self.server_box.text modal = ConnectionModal(jabber_id, self.password_box.text) modal.open()
We kept the one line from the old
login() method which constructs the
jabber_id. But now instead of connecting, we construct a
ConnectionModal widget and instruct it to open itself. If you run this, it will no longer try to connect to jabber at all. Even though the connection code still exists on the
Orkiv, that method is not being called. Obviously, calling that is the next step, but lets make tweak that
connect_to_jabber method first to make it inform us when it cannot connect.
I didn’t notice the need for this change until later on when I was doing my own testing, but it’ll be easier to explain if we do it now. It’s common to do this when crafting commits in git (less so in Mercurial, which is an endless source of frustration for me). Instead of committing each piece of poorly thought out code as I go, I figure out what needs to be done and then design the commits so that they tell a coherent story to anyone that reads the history. If you read through the Examples committed on the github repo you can see each scene in this story as it is played out. It is much easier for you to understand than if I had committed each of my mistaken attempts in whatever crazy order that they occurred. When crafting git history, remember that there will always be a reader of your code and try to make the most elegant story possible.
Specifically, replace the single
self.xmpp.connect() line with the following four lines:
from sleekxmpp.exceptions import XMPPError # At the top of the file self.xmpp.reconnect_max_attempts = 1 connected = self.xmpp.connect() if not connected: raise XMPPError("unable to connect")
There are actually two fixes here. I had to scour the sleekxmpp documentation to find the
reconnect_max_attempts property. Without it, the client keeps trying to connect over and over and never fails. However, if we tell it not to bother trying to reconnect at all, for some reason, it fails to connect to gmail. So we allow it to reconnect once to keep gmail happy, but not indefinitely to keep our users happy.
connect() function returns a boolean value indicating whether the connection was successful. If it’s not successful, we want to communicate to whatever method called
connect_to_jabber that it was unsuccessful. So we raise an exception that, handily enough, sleekxmpp has defined in their code. When we raise an exception, execution immediately stops in this method. However, we can catch the exception in the calling code. Let’s add that calling method to the
ConnectionModal class now:
from sleekxmpp.jid import InvalidJID # At the top of the file def connect_to_jabber(self): app = Orkiv.get_running_app() try: app.connect_to_jabber(self.jabber_id, self.password) self.label.text = "\n".join(app.xmpp.client_roster.keys()) except (XMPPError, InvalidJID): self.label.text = "Sorry, couldn't connect, check your credentials" finally: if hasattr(app, "xmpp") and app.xmpp: app.xmpp.disconnect()
Notice that this method is called
connect_to_jabber just like the one on the
Orkiv class, but this one is attached to
ConnectionManager. There is no law against reusing names if it makes sense to do so, as long as they are in a different namespace (the
Orkiv namespace is completely independent from the
ConnectionModal namespace). In fact, if you recall the consistency rant from Part 3, you will know that it is often desirable to do so.
We have a
connect_to_jabber method on the
ConnectionModal class that calls a totally different
connect_to_jabber method on the
Orkiv class. The latter method might raise either an
XMPPError or an
InvalidJID error. The former is raised explicitly if
connected is False; the latter is actually raised inside the sleekxmpp code. Either way, it gets propagated up to the the
ConnectionModal‘s method, where we catch it using a
try...except clause. We also add a
finally clause that checks if an
xmpp property has been added to the app and, if so, disconnects it to keep it from freezing up the app, as we discussed in Part 3.
If you would like more information on exceptions, see the Python tutorial. There’s also good coverage of Exception handling in my book, Python 3 Object Oriented Programming.
It only takes one line of code to have this
connect_to_jabber called as soon as the
ConnectionModal is opened.
ModalView has a
open event, so we just have to assign it a callable (any function or method, such as
connect_to_jabber is a callable) that is called whenever that event is invoked. So hook this up as the last line of
self.on_open = self.connect_to_jabber
There’s one more thing I want to do. When there is a failure, instead of just showing a message and forcing the user to close the app, it would be much better if they had a way to go back to the login form. Let’s add a button that allows them to try again. Clicking this button will hide the modal window and the original login form that was behind it will be visible so the user can edit the details. This can be done by replacing the contents of the
except clause with the following code:
from kivy.uix.button import Button # At top of file self.label.text = "Sorry, couldn't connect, check your credentials" button = Button(text="Try Again") button.size_hint = (1.0, None) button.height = "40dp" button.bind(on_press=self.dismiss) self.add_widget(button)
Here we’re adding more user interface in Python code and once again we see that it’s bulky compared to the KV Language. We’re still rendering the label, but we’re also adding a button to the bottom of the window. We’re giving it a 40 display pixel height, remembering to set
None in the y dimension so the layout knows to check the height. We then bind the
on_press event of the button to the
dismiss method (a callable, remember?) on
ModalView. When the button is clicked, the modal disappears. Now it’s much easier to test each of the failure conditions I listed earlier, one after the other.
Technically, this interface still freezes up when we connect, but it’s less invasive since we’re rendering a label instead of broken text areas. The correct fix would probably be to connect in another thread and then schedule interface updates using the Kivy clock, but that’s a bit advanced for this tutorial.
The last problem I want to address today is the fact that we disconnect as soon as we render the Buddy List. That’s not going to be very useful when we get around to actually sending and receiving messages. However, not disconnecting causes the program to hang when we close the window because the
xmpp object is sitting there doing it’s job: waiting for incoming jabber messages.
We need to disconnect that object when the window closes. Luckily, Kivy gives us an event on the
App class, named
stop, that we can hook into to do just that. But let’s arrange our code nicely.
I spend a lot of time rewriting code that I wrote before. Why? To make it more maintainable. I need the code to be readable in the future when I’ve forgotten what it does. Python is designed to be a very readable language, but that doesn’t mean that it’s self organizing. It’s one thing to write readable sentences in English, but quite another thing to have a coherent and cohesive paragraph.
Even though it’s temporary code, the
hasattr call in our exception handler (which checks if the
xmpp property has been added to the object or not) is a code smell. It would be better if the
xmpp property always exists on the object and to check whether or not it is a valid value. So let’s set that property to
None by adding an initializer method to the
def __init__(self): super(Orkiv, self).__init__() self.xmpp = None
Let’s also add a
disconnect_xmpp method to the
Orkiv class that does the necessary cleanup for us. This method will be called both when the app stops and when the login method fails (but not on successful login). It’s a bit more involved than the dirty call to
xmpp.disconnect() that we were making in our temporary code, because we want to be sure to get it right:
def disconnect_xmpp(self): if self.xmpp and self.xmpp.state.ensure("connected"): self.xmpp.abort() self.xmpp = None
The conditional checks too things. First, it checks that
self.xmpp is an actual object, and not
None. If this check is
False, python won’t even bother with the second check, which is to see if the
ClientXMPP object is connected. I had to actually look through the
sleekxmpp source code to figure out how to check if it’s connected using that weird
state.ensure syntax. If
xmpp IS connected, then we call
abort(), which calls the
disconnect() method we were calling earlier as well as some other cleanup tasks. Finally, regardless of whether it was connected, we set the
xmpp property to
None which both frees the memory used by sleekxmpp for garbage collection and puts the app in its known initial state in case we want to try to connect again.
While we’re add it, let’s hook up the
stop event handler on the
Orkiv. All it needs to do, for now, is call the method we just added:
def on_stop(self): self.disconnect_xmpp()
You might be wondering why we have two methods, instead of just putting the
disconnect_xmpp method body inside of
on_stop. There are a couple reasons. One is that we’re going to be calling
disconnect_xmpp from a the exception handler in the next example. Of course, we could just call
on_stop instead, but that wouldn’t be as readable; the name
disconnect_xmpp very clearly describes what the method does. Second, there is every chance that we’ll later have to add additional code to the
stop event handler to clean up other things when the app closes. By that point, we’ll likely have forgotten that
on_stop is being called from other places, and we may not want the new cleanup code to be called during the connection/login phase. Instant bug! It is often extremely worthwhile to use longer code if it is more readable than a shorter alternative.
Let’s do that last step now: call the
disconnect_xmpp method when login fails. We can remove the
finally clause, since we no longer want to disconnect if login is successful. And we can remove the
hasattr call, since that check is now being done more elegantly inside the
disconnect_xmpp method itself:
except (XMPPError, InvalidJID): self.label.text = "Sorry, couldn't connect, check your credentials" button = Button(text="Try Again") button.size_hint = (1.0, None) button.height = "40dp" button.bind(on_press=self.dismiss) self.add_widget(button) app.disconnect_xmpp()
More on maintaining history
That’s it for coding in this part of the tutorial. I’m a bit concerned that the changes introduced in this tutorial were hard to follow, since we were making sweeping changes all over the file, but I wanted to describe them one at a time. If you’re having trouble following it, you might have better luck looking at the individual git commits. If you select a commit, github will highlight the lines that have been added and removed in each change, making it easier to follow. It also allows you to see the entire file (there’s a “View File” button) if you’re feeling lost. I tried very hard to craft the changes in each commit as well as the commit message so that the history is very easy to follow.
You should try to do this yourself when you are developing. It is unlikely your history will be quite as readable as this repository (my real life repos certainly aren’t!), because it is generally considered unwise to edit your history after you have pushed it to a public repository that other people have seen (think of trying to edit a published book after somebody buys it.) However, before you have pushed your changes, it is perfectly ok to rewrite and reorganize your commits so that they make the maximum amount of sense.
Git provides several tools for such work. One of the easiest to understand is the
git commit --amend command. This command is like a regular commit, except that instead of creating a new commit with whatever changes you have prepared with
git add, it alters the last committed patch to include those changes. It even allows you to edit the commit message! This is very useful if you make a commit and then realize you forgot to change something in that commit. For example, it’s a terrific way to exclude useless, cluttering commits with messages like “Conform to pep8″, “I forgot to remove a debugging statement”, “Update documentation”, or the most heinous commit message of all: “Blah”.
Another really handy tool that I used extensively while developing this particular tutorial is
git add -p <filename>. Because I didn’t actually know exactly how my attempts with the
ConnectionModal code were going to work out, I wrote and tested everything before writing the narrative. But then I wanted to commit only specific parts of what I had written in each example, each commit containing a single, coherent collection of changes.
git add -p is an extended version of
git add (the
p is short for
--patch) that loops over the changes in the file and allows you to interactively select exactly which hunks you want to go into the next commit. There are quite a few options you can use during each step; press the
h to get an overview. Don’t forget to call git commit when you’re done!
If you liked the tutorial and would like to see similar work published in the future, please support me. I hope to one day reduce the working hours at my day job to have more time to devote to open source development and technical writing. If you think this article was valuable enough that you would have paid for it, please consider supporting me in one or more of the following ways:
- Purchase my books: Creating Apps In Kivy>, Python 3 Object Oriented Programming and Hacking Happy
- Write a positive review of these books
- Tip me on Gittip
Finally, if you aren’t in the mood or financial position to help fund this work, you can always do your part to share it on your favorite social platforms.