(Note to English-speaking readers: the links entitled Komentarze na tej stronie lead to comment pages.)
As you might have guessed, I pay siginificant attention to UI/UX issues. Since I’ve been implementing undo functionality recently, I thought I’d share a few thoughts about it.
On the surface, undo seems easy: you record all changes you have made and revert the last one on demand. When you look at details, however, things can get messy.
First of all, how do you actually “record all changes”? You might record the state after every change (probably simple in many applications, but potentially a memory bottleneck). You might just record the deltas (better from the memory perspective, but a bit more involved). In the case of Logeox, however, I decided to do something even different. Instead of recording the state after each “change” (i.e., turtle command), I record the commands themselves. Since they are sufficient to completely recreate the state at any point of time, undoing the last command is easy: you just delete the last link in the chain and replay them again. (Theoretically, it means that undoing a change is a lot more expensive timewise than issuing commands, but I do not expect this to be a real problem. And if it ever becomes one, I can easily add some caching, like e.g. recording the full state every 40 commands or something like that.)
Then there comes another question: what exactly a change is? In my case, I have a few commands: go forward, turn left/right, pen up/down. Should all of them be undoable? This would be the simplest thing to do (and the first one I did), but probably not the best. After consulting half of my users (i.e., my dear wife), I decided that only actual turtle movements should be undoable. (By movement, I mean a command changing the turtle’s position, so turns are not included.)
This raises another question. Assume the following sequence of user actions: go forward, turn right, undo. Should the undo restore the direction from before the “go forward” command? I think the answer is positive (and fairly obvious). Consider now this: pen down, go forward, pen up. Should the undo change the state to pen down again? This time the answer seems less obvious, but still affirmative. And so I did it this way.
And how did I actually implement all these? It turned out to be pretty simple. I added another method to the
isUndoable, which returns false for each command except
GoForward. (I briefly considered making it return false by default and only overriding it in
GoForward. After a while, I decided that I like defining it explicitly in each command a bit better. Yes, it looks like code duplication, but (1) it makes me think before deciding – for each and every command – whether it should be undoable, and (2) it felt kind of wrong to me to hardcode the choice that happens to be more common in the parent class.) And
Undo, instead of just removing the last command from the list, keeps removing commands until the first one with
isUndoable returning true. (And while at that, I fixed a small but irritating bug with the app crashing when trying to undo when no commands were yet recorded.)
return false; and nothing else.
And now that I googled it a bit and gave it a minute of thinking, I guess there’s even a better way: to have the
isUndoable() method return a field, initialized to the proper value by the constructor. And BTW, googling for that revealed a whole mess of opinions and possibilities of having fields with various qualifiers –
Well, the title of this post does not mean (unfortunately) that I’ve overcome all my design/coding problems (not to mention my life problems…) – it’s just a lame pun relating to the “clear the screen” feature.
There are actually four “layers” of it. The first one is the implementation, which is (more or less) trivial. (Probably the only less trivial part was reusing the
ArrayList of Paths using the
.clear() method instead of recreating the whole object – not rocket science, but good to know that it’s there.)
The second one is the UI. Currently,
Clear is an option in the menu, which is far from satisfactory – it should really be a button in the action bar. For now, however, it is in the menu, and it turned out that there is yet another Android quirk here: the menu has a transparent background by default, but if
AppCompatActivity instead of
Activity, the menu looks good again. Wtf, Google? I have to admit that I had to revise my former opinion on well-designed-ness of Android – not that it’s bad, but there seems to be a lot of irritating details like that. Sure, I can live with that, but why do I have to?
Never mind that anyway – there are more important things than this. One of them is the exact behavior of the “clear-screen” menu item (or button). It should clear the screen, sure, but should it bring back the turtle to the center? The more I think about it, the more I guess the answer is no; instead, I’m going to make another button, called “home”, which will bring the creature back in case it gets lost.
But here’s the third one, also UI-connected: what should the icons for these buttons look like? The “home” one is pretty obvious, but the “clear screen” less so. I’m going now with the traditional “dog-eared empty piece of paper” with a sparkle of newness, but I’m open to better ideas.
Two posts ago I wrote about my struggle with the
Well, it seems the struggle is over. I’m now in the process of implementing commands, and so far it seems to work fine.
BTW, can you spot a pattern here? Day n, I hit the wall, and have no idea how todo something or why something does not work. Day n+k (for k a small natural number, often 1), everything is up and running.
A reminder to myself: whenever I seem to be unable to solve a problem, perform these simple steps:
Coming back to programming: I decided to go with an inner (i.e., not static) class. The reason was simple: I needed both instances of commands (in retrospect, this is kind of obvious…) and access to fields of the
Turtle class. Of course, this means that the
Turtle.java file started to grow, but I never subscribed to the “one-file-per-class-since-my-editor-cannot-deal-with-large-files-or-what” camp anyway.
Incidentally, it seems that I’ll need no changes outside the
Turtle class! (At least not until I’m going to implement actual new functionality.) Point for you, encapsulation.
And tomorrow, I’m going to publish an overdue post about the clear-screen feature (the post is ready, but I decided not to publish more than one per day). It seems that the app is slowly maturing – though I have quite a few ideas left to explore!
(Więcej means More in Polish; click it to see older entries.)