Code Small FTW: Tuesday

Yesterday’s installment including the TL;DR, Background, The Project, Ground Rules and The Work sections, can be found here.

The Design

One thing you should know about me: I am a horrible UI designer. I wouldn’t even begin to talk about UX. My goal here was to make something that my wife would find easy to use. I knew that if it was easy for her to use, she would use it. And I knew I would too. It’s also the reason I decided to focus on the App side first. I knew it would take me longer than the server side. I can write server side APIs in my sleep!

My first pomodoro was spent just writing notes about the design. My initial design goals were this:

  • To have the least amount of UI widgets that would get the job done
  • To have the most familiar interface and widgets for the iPhone possible
  • To make one hand use easy

I knew that I needed a screen to enter data and a screen to see historical data.

I also knew I needed some type of settings screen, although I would keep this as light as possible.

A tab interface seemed most natural for the iPhone.

The data entry tab (which I called the transact tab) needed two pickers (date and category), two text fields (amount and description), and two buttons (credit or debit).

For the category picker, I thought it would be useful to create some sort of interface to add and remove categories dynamically. This way, if our categories changed, we could easily update them in the app. But, since a primary goal of the app was to have the financial data stored centrally, we would need to sync the categories as well. Whoa – red flag time! Mid-pomodoro, it struck me that I was going too far. This App was just for my wife and I, after all.

On thinking about it, our important categories hadn’t changed in quite a while. Any variation we needed could be captured in the description. I decided to hard code the categories in the category picker. Again, Sacrilege. But, it got me out of the rabbit hole I was going down very quickly. No new networking sync process, no new screens or tabs. Just the picker I needed.

Just Finished another pomodoro. Writing at 30,000 feet on my way to West Palm. Gonna hit the Settlers of Catan app on the iPhone during the break.

The History tab shows all unprocessed transactions. As we enter data into clearcheckbook.com, we touch a row of the history table. When touched, the row on the history table changes color. Once we are finished entering data, clicking the process button on the history tab removes those rows from the history data.

The Settings tab has an input field for a token. For the purposes of this App, there is no registration or confirmation. The database will be pre-loaded with tokens for my wife and I. This decision eliminated a whole bunch of UI and API programming upfront.

Here are some screenshots from the initial UI:

Pretty minimal, right? Hardcoded categories and all. But, it’s just what we need.

Testing the App

I didn’t want to invest a lot of time in testing frameworks for the Titanium environment, but I did want to have some sort of tests before I started working on the backend or hooking up the App to the backend.

As a first pass at testing UI components and reactions to interactions, I wrote a small amount of test code mixed in with the real code. Sacrilege! I did, however, follow the tried and true conventions of code separation and an MVC architecture, in general. The hardcoded test code was embedded in the logic of the model. This made it so that when it was time to hook the App up to the back end, just the model code needed to be changed. No changes in any of the view code was necessary.

Here’s a snippet from the Transact tab view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
minusButton.addEventListener('click', function(e) {
  var data = {
    date: dateLabel.text,
    amount: amountTF.getValue(),
    description: descTF.getValue(),
    category: categoryLabel.text
  };
  TransactModel.debit(data, function(result) {
    if (result.status === "SUCCESS") {
      amountTF.setValue("");
      descTF.setValue("");
    }
    Ti.App.fireEvent('message', {
      message: result.message
    });
  });
});
...

Lines 3-8 prepare the data from the view to pass on to the model. Line 9 passes the data into the debit function of the TransactModel. The debit function calls back with a result. If the transaction was processed successfully, the amount and description fields are cleared. Lines 14-16 show a popup message on the screen with the result.

Now, let’s take a look at the initial model code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var credCounter = 0;
 
var TransactModel = {
  transact: function(data, callback) {
    if (credCounter%2 === 0) {
      var ret = { status: "SUCCESS", message: "submitted " + data.amount +
        " to " + data.category + "." };
    } else {
      var ret = { status: "FAILURE", message: "Unable to connect!"}
    };
    credCounter++;
    callback(ret);
  },
  credit: function(data, callback) {
    TransactModel.transact(data, callback);
  },
  debit: function(data, callback) {
    data.amount = (isNaN(parseInt(data.amount))) ? "" + 0 : "" + (0-parseInt(data.amount));
    TransactModel.transact(data, callback);
  }
};

The impact of lines 1, 5, and 11 is that every other time the transact function is called (such as from line 19 in the debit function), the transaction will succeed and every other time the transaction will fail. This allowed me to easily test the various result cases without having to connect to a backend just yet. Later on, when the backend was integrated, lines 1 and 5-11 were deleted and replaced with the real code. The final code is actually more succinct:

1
2
3
4
5
6
7
8
9
10
11
12
var TransactModel = {
  transact: function(data) {
    Ti.App.fireEvent('do_transaction', { data: data });
  },
  credit: function(data, callback) {
    TransactModel.transact(data);
  },
  debit: function(data, callback) {
    data.amount = (isNaN(parseFloat(data.amount))) ? "" + 0 : "" + (0-parseFloat(data.amount));
    TransactModel.transact(data);
  }
};

Shedding the prohibition against embedding test code in real code allowed me to rapidly test the view before any backend code was written at all. For this project, I was not slowed down and the quality of the code did not suffer. A more complex project would certainly benefit from a more formal testing harness. For future projects, I will look into what’s available for Titanium Appcelerator in the way of testing frameworks.

Tomorrow’s Installment: Pomodoro Break and The Server Side



Leave a Reply