Dusty Phillips: Creating an Application in Kivy: Part 8

17 Aug

Sorry for the delay in posting this, folks. I’ve been away on vacation and then at Pycon Canada, which was amazing. And another apology, in advance, that this article is going to be bloody short compared to parts 1 through 7. That’s not only because I’m short on time; it also turned out that what I wanted to do was easier than I expected!

Anyway, time for content! Now that we have a working jabber client, is our job done? The client is certainly functional, but I’d still like to experiment with displaying a different representation depending on the size of the window. Specifically, if it is wider than a certain number of pixels, let’s show the buddy list beside the chat window; otherwise, let’s stick with the switching back and forth that we currently have.

If you haven’t been following along or want to start this example at the same point at which I’m starting, you can clone the github repository with my examples:


git clone https://github.com/buchuki/orkiv.git
git checkout end_part_six

Remember to create and activate a virtualenv populated with the appropriate dependencies, as discussed in part 1.

Table Of Contents

Here are links to all the articles in this tutorial series:

  1. Part 1: Introduction to Kivy
  2. Part 2: A basic KV Language interface
  3. Part 3: Handling events
  4. Part 4: Code and interface improvements
  5. Part 5: Rendering a buddy list
  6. Part 6: ListView Interaction
  7. Part 7: Receiving messages and interface fixes
  8. Part 8: Width based layout

Thinking through design

Since I’m not entirely certain how I want this code (or interface) to look, let’s spend a few paragraphs describing it so we all know we’re developing the same thing. It’s simple to say “we want chat windows to display beside the buddy list when the screen is big enough”, but what does that mean exactly? At what points in the code do we have to handle it? I’m actually “thinking out loud” here so you can see the various paths a brain can follow when programming.

Let’s follow the process from program startup. When the account credentials are displayed, we may as well just show the form no matter how big the screen is. When the Buddy List is first displayed after login, it can take up the whole screen as well. It’s not until we open our first chat that we have to decide if we are going to add the new chat beside the buddy list or to replace it. Of course, it’s not just a matter of adding the chat window, we also have to remove any existing chat windows, since we don’t just want to keep adding windows every time they click a new buddy. One way we could do this is add a ChatWindowContainer and ensure that widget only contains one chat window. Another is to keep track of which widows are loaded and unload them as needed. A third option is to clear all widgets and add both the Buddy List and Chat Window to a clean slate.

Regardless of the width of the screen, when the user clicks the “Buddy List” button in the chat window, it should do the same thing: close all chat windows and show only the buddy list, full width. One option worth exploring is to hide that button when we are in wide screen view. Or perhaps the button should be removed altogether; a gesture might be a better interface. While gestures are all the rage in mobile devices right now, I’m not a fan of them from an affordance perspective. It’s never clear to the end user when or where a gesture is appropriate. While I use them extensively in my highly customized AOKP installation, I wouldn’t want to explain a gesture based interface to my parents.

In addition to deciding what to render when chat windows are added or removed, we need to do something when the window changes size. If it is in wide screen mode while a chat window is displayed, and then gets smaller, we have to remove the buddy list to switch to the original behavior. If it is in narrow mode while a chat window is displayed and it gets wider, we have to add the buddy list back.

I feel like it would be wise to have a “mode” property bound to the window size, so that we always know whether we are currently supposed to be in wide or narrow mode. However binding that property to size means that the mode will change without telling us what the “old” mode was. Thus, we have no way to know whether the mode we are supposed to be in is the one we are currently in. On the other hand, such information can be inferred from the size or contents of the child list on the OrkivRoot object.

Finally, it seems most of our calculations can be simplified by noticing that we only have to do things different if a chat window is currently displayed or about to be displayed.

Now I think I have a plan in my head. You may or may not have come to the same conclusions while reading the above, and it’s possible you’ve had some better ideas. That said, this is how I intend to implement this. It looks like it’s going to be simpler than I originally expected. Hooray for thinking things through in advance!

1. Add a mode property to OrkivRoot that is automatically set from the size of the window

2. Add chat_visible and budddy_list_visible python properties (not kivy properties) that tells us if there are buddy list or chat windows visible.

3. Check the mode when adding a chat window and decide whether to replace the buddy list or not

4. On a size event, if a chat window is currently visible, determine whether the buddy list needs to be added or removed.

Properties

We’ve looked at Kivy properties in past parts of this tutorial, and you may already be familiar with Python properties. It’s somewhat unfortunate that the same word is being used to explain two different programmatic concepts. However, there are only so many words to describe the idea of a “characteristic”, and this idea is extremely common in many different programming libraries. So some overloading of terminology is inevitable. I tend to use the phrase “Kivy property” to refer to the kinds of properties that Kivy supplies. Kivy properties are pretty cool because they can automatically update themselves when other properties change and events can be bound to them so that those other properties also update themselves. In other words, Kivy properties are smart.

Python properties are not stupid, mind you. People sometimes have trouble wrapping their mind around Python properties, but they’re actually pretty simple. It might be convenient to think of python properties as just a way to make a method look like an attribute. Thus, you can access something like node.color and underneath the hood, it’s actually behaving as though node.color() was called. However, they’re even more interesting than that, because you can actually call a completely different method when you set the value of the property. Thus node.color = "red" can do about the same thing as node.color("red").

The confusing thing about python properties is not so much “how do I do this?” as “why would I do this”? There are many problem-specific answers, but two general ones come to mind. The first is the Python idiom that readability counts. In general, when you access a property on an object, you are doing something with an adjective on a noun. In contrast, when you call a method on an object, you are doing something (a verb) to a noun. However, sometimes when you are manipulating an adjective, you need to do a little extra calculation or manipulation when the value is set or accessed. Using properties, it’s possible for the code accessing the property to read “naturally”, as though you were doing something with an adjective on a noun, even though there is actually a little “doing to” going on as well.

The second reason is actually an evolution of the first. Often, in the first version of an API, we just add an attribute to an object and allow client code to manipulate it. Then, in a later iteration of the code, we want to do some kind of calculation, caching, or validation on these attributes. However, we can’t suddenly change the attribute into a method or the old client code will break. However, we can use properties to add these new manipulations and keep the original API, which is probably more readable from the calling code’s perspective, anyway.

At any rate, our OrkivRoot class needs to have one Kivy property and two Python properties in order to perform the calculations for treating the screen differently depending on its size:

(commit)


class OrkivRoot(BoxLayout):
mode = StringProperty("narrow")

@property
def chat_visible(self):
return ChatWindow in {c.__class__ for c in self.children}

@property
def buddy_list_visible(self):
return self.buddy_list in self.children

So we have a simple mode property that is exactly the same as any other Kivy property. We also have two read only python properties named buddy_list_visible and chat_visible. The former just checks if the buddy_list object is one of the children. The latter loops through the children of OrkivRoot and checks if any of them are instances of the ChatWindow class. Any time you access orkivroot.chat_visible, this method is executed.

The odd notation in the chat_visible property may need some explanation. (Note: If you’re running python 2.6 or older, it won’t even run) Python has a variety of comprehension syntaxes that allow us to create new iterator objects such as lists, generators, dictionaries, and sets based on existing iterators. This syntax is optimized for efficiency as compared to looping over one iterator and manually adding it to another. It’s also more readable if you know the syntax.

The braces, which you are probably used to denoting a dictionary, in this case actually generate a set, based on the contents of another iterator: self.children. For each element in self.children it adds the class of that element to the new set. Then the in operator is used to determine if a ChatWindow class is in that set.

In theory, we use a set instead of a list for efficiency. However, in this case, I’m actually only using it for pedagogy. The in keyword supports much faster lookups in a large set than it does in a large list. However, in this case, the number of children of a OrkivRoot will never be large. I’m just trying to drive home the point that different data structures have different strengths and weaknesses, and you need to choose them wisely. For example, a few months ago, one of my colleagues shaved nearly an hour off our loading process by converting a list to a set in this very fashion.

I hope you find my digressions educational! As I’ve said before, my goal is to not just explain how to build a Jabber application in Kivy, but to demonstrate the various thought processes behind designing your own application in whatever programming language or library you might choose.

Let’s ignore those python properties for a bit and look back at that Kivy property. While the mode StringProperty has been defined, it’s not yet being set to anything. Let’s fix that in the Kivy language file:

(commit)


<OrkivRoot>
mode: "narrow" if self.width < 600 else "wide"
AccountDetailsForm:

That one new line of code is all that is needed to have a dynamically updating mode attribute. Any time the window size changes, the mode will automatically (it’s almost automagic, in fact) be set to either “narrow” or “wide” depending on what the width is. Kivy properties are very powerful. The width property of widget is automatically updated whenever the window gets resized. Then the Kivy language does a wonderful job of hooking up the event bindings such that any time that width changes, a callback that executes the line of code above is invoked.

For the record, I tested these properties by adding a couple print() statements when the show_buddy_chat method is invoked to render the current mode.

Make it work

Now we need to rework the show_buddy_chat method just a little:

(commit)


def show_buddy_chat(self, jabber_id):
self.clear_widgets()
if self.mode == "wide":
self.add_widget(self.buddy_list)
self.add_widget(self.get_chat_window(jabber_id))
self.buddy_list.new_messages.discard(jabber_id)
self.buddy_list.force_list_view_update()

Instead of explicitly removing the buddy_list, we now start with a clean slate by calling clear_widgets. Then we check if the mode is “wide” and add the buddy_list if it is. Then the chat window is added just like before, but now if the view is wide, it will peacefully slide in beside the buddy_list instead of replacing it.

The code for changing the window contents when the mode changes is a bit longer. The logic is simple, it’s just verbose. We need to add an on_mode method to the OrkivRoot. This method will only be fired when the mode changes thanks, once again, to the magic of Kivy properties.

(commit)


def on_mode(self, widget, mode):
if mode == "narrow":
if self.chat_visible and self.buddy_list_visible:
self.remove_widget(self.buddy_list)
else:
if self.chat_visible and not self.buddy_list_visible:
self.add_widget(self.buddy_list, index=1)

When I first tested this method, it failed because I didn’t accept any arguments on the event. So I had to figure out what the expected arguments were. I did this by making the method accept *args, which says “put all the non-keyword arguments in a list named args”. Then I printed that list to see what they were. So I knew that the arguments passed were a widget object and the current value of mode. Introspection in Python is so easy!

If the mode is narrow, it checks if both widgets are currently visible and removes the buddy_list if this is the case. The only way both widgets could be visible on an on_mode event is if the mode just switched from wide to narrow.

If the mode is wide, it checks if a chat window is visible and the buddy list is not. My first attempt only checked if the buddy_list was not visible and added it arbitrarily. This failed on startup, since OrkivRoot has a size, and therefore a mode, right from startup, even when the login form is being displayed. Thus, we only do the widget juggling if a chat_window is currently visible.

The index=1 argument to add_widget tells it to insert the buddy_list before the already visible chat_window rather than after.

And that’s it! Test that, try switching back and forth between the buddy list and the chat window, try resizing the window, try switching chats while the buddy list is visible. I’m a little dubious that it’s perfect, but I can’t find a situation where these few lines of code aren’t enough to do what I want. That’s rather unfortunate, because even with all my detours and excursions, this tutorial is incredibly short! However, I’m going to publish it as is, since you’ve all been waiting patiently while I was taking vacations and wandering around at Pycon Canada (where Kivy was mentioned both in the keynote speech and in the first talk of the day). Next week, let’s try to run this sucker on Android!

Financial Feedback

If you are enjoying this tutorial series and would like to see similar work published in the future, please support me. I dream of a future where open companies like gittip and open contributions help the world run in harmony instead of in competition. I believe that my work is high quality and worth backing. If you think this article held enough value that you would have paid for it, please consider backing me in one or more of the following ways:

via Planet Python http://archlinux.me/dusty/2013/08/17/creating-an-application-in-kivy-part-8/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 76 other followers

%d bloggers like this: