Writing Android Unit-Tests With Robolectric

Until recently, I’ve been unable to configure a Robolectric project that can easily be shared across a team and integrated with Jenkins CI without the use of Maven. I mentioned this in a previous post, and have asked for assistance on Pivotal’s Robolectric Google Group a handful of times. There are several jar files that need to be referenced, and dependencies between the test and main Android projects and (without the use of Maven) this is difficult to configure and move around between teammates.

Read on at Public Static Droid Main.

Working With Credit Cards In HotelTonight For iPad

If you haven’t heard, we released an iPad app. It’s pretty good too. We were confronted with quite a few interesting and unique challenges in bringing our iPhone app to the much larger screen of the iPad. Just blowing everything up so that the existing phone app used the entirety of the iPad’s screen wasn’t good enough. We needed to start with a blank slate and rebuild everything from scratch to make a true iPad experience that we could be proud of.

One area that received quite a bit of attention was the interface for entering and selecting a credit card to use during a booking. Since we sell a physical good (a hotel room reservation), we don’t use the in-app purchase frameworks that Apple provides for allowing upgrades and other purchases in the other apps you love.

The problem with credit card input is that no one enjoys doing it. It’s a utilitarian chore entering a string of numbers, an expiration date and a CVV whether you’re on the desktop or mobile. Our primary goal with creating this new card entry experience was to make it not only easy to use, but also fun.

For comparisons sake, this is what we started with on the iPhone:

iPhone Card Entry

Certainly a serviceable UI, but not something that we wanted to just import into the iPad UI. For one, having a full size table view in the middle of our booking workflow would look strangely out of place. Second, we tried really hard to minimize the number of popovers in the interface. Popovers are useful for supplemental information, but providing them as part of the main workflow in the app wouldn’t be a fit for the experience we were trying to provide our users.

Research/Inspiration

Before we went down the path of building our own card implementation, we spent some time downloading different mobile apps and seeing how they did credit card entry. The first place to look of course is at our friends at Square. A company built around taking mobile payments is an easy place to look. We also were incredibly impressed by our same day booking buddies at WillCall. Their entire app is impressive, but their card entry UI is what caught our eye the most.

What both Square and WillCall have in common is the single-line entry paradigm. Once you enter a credit card number, your cursor automatically shifts to the next field, followed by the next until you’re ready to submit. There is inline validation along the way to ensure the user isn’t hitting the authentication servers with obviously bad data such as an expired card or an invalid card number.

What We Ended Up With

For the first version of HotelTonight for iPad, this is what we shipped:

Card Entry State

There are three states to our card selection user interface:

  1. The card entry state (above)
  2. The card validation state (below)
  3. The card selection state (the screenshot below that).

The card entry state has two fields: the card number and card expiration. The card number field is just a vanilla UITextField, but we do a bit of massaging of the input as the user types. Most noteably, we have a UITextFieldTextDidChangeNotification handler attached that does three things:

  1. Ensure that we have a valid length card number
  2. Ensure that the card number matches the type of cards we support: Visa, MasterCard, American Express and Discover.
  3. Ensure the card passes a luhn validation

If the card number is validated, we automatically transition the user to the expiration date field so they can continue typing. No need to tap a next button or manually tap on the expiration field. Upon entering a successful expiration date, we transition to the validation state.

Card Validation State

The validation state transition is where we highlight what type of card the user has entered as well as providing the last four digits of their card number. Tapping on either of those transitions back to the card entry state. The user can also backspace from the CVV field to get back there. More on that later.

Once a card is entered, it shows in in our credit card tray.

Card Selection State

The currently selected card has a full-color treatment while the others are desaturated. If you have more than a few cards, the picker turns into a scrollable control that lets you choose between the different ones.

The Little Details

We obsess over the little details at HT. The first time we played with a functional implementation of the card picker control, we were annoyed that deleting the last character in a text field didn’t push back to the previous text field or picker state. It turns out this isn’t as easy as we had hoped.

Whenever you make a change to a UITextField, you have the opportunity to perform an action using the - textField:shouldChangeCharactersInRange:replacementString: delegate method. Unfortunately it is not called if there are no characters in a text field. To work around this, we decided to insert a zero width space (\u200B for the Unicode fans in the house) at the beginning of each text field. UITextField won’t render the space, but it is capable of being deleted, and subsequently firing our delegate method. We then check if the delete key is being pressed while the text field only contains that zero width space. If it is, we transition to the previous field.

Things We Waffled Over

In the run-up to shipping our shiny new iPad app, we gave everyone in the office an opportunity to test out the application. Tests like this are always eye opening because developer eyes tend to glaze over after staring at the same piece of functionality for days and weeks on end. We miss stuff, or assume other things are more obvious than they really are.

The first thing our user tests uncovered was that people didn’t know what to do with the card picker in its default selection state if they didn’t have any cards already associated with their account. To alleviate this, we default to showing the picker to the user in its card entry state if there are no credit cards associated with the currently logged in user.

We also decided to add a bit of help text underneath each the entry fields to further guide the user along. Each text field has placeholder text, but in the case of fields like CVV we were concerned some users might not know what a CVV was. The help text offers a bit of guidance.

Moving Forward

As with all things software, we have a few ideas of how we want to enhance the card picker going forward. For one, users can’t currently edit or delete cards. There are also some additional treatments and animations we’d like to add to the card tray as you transition from the selection state into the new card input.

You can check out the card picker in action by downloading our new iPad app on the App Store.

Checking If Rake Was Called With Verbose Or Trace

Recently we needed to check whether a rake task was called with the --verbose or --trace flags.

For verbose you can do the following inside your rake task:

Pretty simple, right? Be careful though, the default value for verbose is the symbol :default so using it in a predicate like `if verbose` will allow it to pass through. You will need to explicitly compare it to true.

Checking if your rake task was called with --trace is a bit more, well, verbose. Here is how you would do that:

Improving Accessibility Support In HotelTonight For iOS

I recently joined the HotelTonight development team to help improve and further our existing iOS app as well as building some even cooler stuff down the road. One of the first things I wanted to tackle was ensuring that our iOS app offers proper accessibility support for users that take advantage of Apple’s excellent VoiceOver technology.

VoiceOver is amazing because it enables vision-impaired users to use a device that has just a single physical button like the iPhone or iPad almost as easily as someone with full use of their sight. Tapping a button or label reads aloud just enough information for the user to perform the action intended.

Beyond the noble reasons for improving the accessibility of the HotelTonight iOS app, it will also help us as we start adopting automated testing and continuous integration as part of our build workflow. We’re still evaluating what solution we will adopt, but each one we’ve analyzed makes extensive use of Apple’s accessibility APIs to reference parts of your interface. Bonus!

Apple gives you a lot for free if you use the standard widgets that are included with the UIKit frameworks. If you stray from the standard widgets and build your own controls, they also make it relatively simple to adopt their UIAccessibility informal protocol.

Accessibility Testing and the Almighty Triple-Tap Shortcut

Before you can actually begin working on improving the accessibility of your app, you need to see where it stands in its current incarnation. The easiest way to do this is by enabling VoiceOver on your own device, running through each screen and seeing what works and what needs to be fixed.

If you are going to be working frequently with VoiceOver (and you will be if you plan to do this), you can quickly enable or disable it via a triple-tap of the Home button. Triple-tapping is not enabled by default, but you can turn this feature on by performing the following steps on your device:

  1. Launch the Settings app.
  2. Tap on General.
  3. Tap on Accessibility.
  4. Scroll down to the bottom and set “Triple-click Home” to “Toggle VoiceOver”.

Triple-tap

The iOS simulator also has an Accessibility Inspector you can enable that will show you traits, labels and other attributes related to accessibility. I used it a few times, but generally found it easier to do all the testing on a device with VoiceOver enabled. Just make sure you’ve got a set of headphones or your co-workers may try to smash your iPhone to silence the repeated quips of the sweet VoiceOver lady.

Table Cell Improvements

Our accessibility testing showed that the existing version of HotelTonight for iOS had a good head start thanks to how much free stuff Apple gives in terms of VoiceOver support in the frameworks. Our tab bar items each had labels that were properly read aloud. Our hotel and booking table cells each read out most of the relevant information to the user as they were browsing through the app.

Hotel-cell

In the case of our hotel cells, I wanted to make sure that all of the information that a non-VoiceOver user could see was accessible to anyone. In our case, that meant reading aloud which category the hotel was rather than just displaying an image. I also wanted to make sure that the accessibility label had a good delivery to it rather than being several short, robotic sounding sentences. When you tap on one our the hotel cells, it now reads something along the lines of:

Hotel Rex, a charming hotel. $69 dollars. Regularly $109 dollars. Available for one night. 2 Rooms Left.

Adding this was trivial.

In our UITableViewCell subclass (HTHotelCell) I added two methods that are part of the UIAccessibility informal protocol: isAccessibilityElement and accessibilityLabel. The isAccessibilityElement method just returns YES to let VoiceOver know that our table cell is indeed something that should be supported. The accessibilityLabel is where we define what is spoken by the lovely VoiceOver lady. We adjust that output based on room availability, how many nights are available to book and whether the hotel has sold out for the evening.

Detail Tab Improvements

When a user taps on a specific hotel to check out the details of it, we have a segmented control along the top with separate segments for “Book”, “Info” and “Map”. We use the open source SSSegmentedControl to build our stylized view and what I discovered was that it was completely inaccessible to anyone using VoiceOver.

Detail-tabs

With a standard segmented control such as the one in the YouTube app’s “Most Viewed” tab, each segment is individually tappable and will have its label read aloud. Unfortunately for us, our custom control didn’t allow any sort selection under VoiceOver. This meant our VoiceOver users could only work within the “Book” tab and would never be able to see our additional information about the hotel or view it on a map from within the app.

Apple has a second informal protocol called UIAccessibilityContainer to handle situations like this. UIAccessibilityContainer allows developers to group separate UIAccessibilityElements that are part of a single visual control, but have different selectable parts. The UIAccessibilityContainer protocol has three required methods to implement:

  1. accessibilityElementCount returns the number of UIAccessibilityElements.
  2. accessibilityElementAtIndex: returns a specific instance of UIAccessibilityElement.
  3. indexOfAccessibilityElement: returns an NSInteger related to a specific UIAccessibilityElement.

As part of UIAccessibilityContainer you are also required to set the isAccessibilityElement method we discussed earlier to NO. Since each individual element will be selectable, the parent control shouldn’t be otherwise the others wouldn’t be tappable.

To get the tabs selectable in VoiceOver, we sprinkled a few extra lines of code into the drawRect: method of SSSegmentedControl. This snippet creates the UIAccessibilityElement for each tab and adds it to a separate accessibleElements array that we then work with in the protocol methods from above.

You can see our fork of SSToolkit with the accessibility improvements on our Github profile.

Night Selector Improvements

Another piece of our hotel detail view controller needed a bit of improvement in VoiceOver: our HTNightSelectorView control. When deciding to book a specific hotel, we have a custom control that will slide up with the number of nights you are able to book (usually between 1 and 5 nights). Each of those options are rendered as a separate instance of a UIView subclass called HTNightsRow.

Night-selector

The previous version of the HotelTonight app would allow a VoiceOver user expose the custom night selection UI, but they couldn’t actually change the number during the booking experience. Instead the number of nights and the price were shown as separate labels that were read aloud.

To workaround this we set each instance of HTNightsRow to be an accessible element by setting isAccessibilityElement to YES and adjusting the accessibilityLabel to output the number of nights and the price as a single string. In situtations where a specific number of nights were unavailable, non-VoiceOver users are shown “N/A” rather than a price. VoiceOver reads that as “N Slash A” accessibilityLabel will instead read that out as “not available”.

We also adjusted each HTNightsRow instance to have a custom set of UIAccessibilityTraits. Traits are what help VoiceOver understand what each specific view or control is capable of doing. Apple offers a variety of different traits you can apply to your controls. By default we wanted to treat each HTNightsRow row as a tappable button that contains static text and allows manipulation by the user.

If the night option is unavailable, we also set the UIAccessibilityTraitNotEnabled trait. Here’s the snippet that does that.

Useful Links

These were the links I frequently referenced working on this project.

In all, getting the HotelTonight iOS app up and properly running with VoiceOver enabled was a two-day project. It wasn’t the most difficult problem we have ever tackled, but it could be one of the most rewarding. We want HotelTonight to be usable by anyone with an iPhone and we can happily say that our newest version is.

You can check out our improved VoiceOver support in the upcoming 2.2 release of HotelTonight for iOS.

Pure CSS3 Buttons in Mobile WebKit

Comp

Pure CSS3 buttons in Mobile WebKit

Recently, we introduced a lower friction pathway from the wilds of the world wide web into our app, HotelTonight.  We see a lot of traffic coming on mobile devices and decided to try to put as much of the HotelTonight experience in front of visitors as possible.

In addition to implementing a deal sample screen, which you can see by visiting our homepage with an iPhone, Android or Blackberry device, we have a signup page which allows users to quickly create an account from scratch or by linking up with their facebook profile.

It was important for us to create a seemless transition between the web-based signup and the app.  We're obscenely proud of our apps, and the faster we can show you why the happier we are.  We approached the signup screen as a trailer for the native apps, and to accomplish that we needed to nail the native-looking elements.

 

Why pure CSS3?

There are quite a few techniques for emulating native iphone app controls, some incredibly creative, using images in different ways.  This approach has a lot of advantages, primary among them being that you can capture all the subtlety employed in the native elements using tools well suited to the task (Photoshop, Fireworks, maybe Pixelmator).

But there are drawbacks as well.  Using images in almost any capacity can severely limit your design.  Characteristics like color, opacity and dimension can be pegged down when using images.

An image also increases the load time of your page, which is a first-order offense in mobile web development.  Yes, it doesn't have to increase it by much, but we approached adding a resource to load as a last-resort.  The signup page needed to be as fast to load as we could push it, and push it we did.

 

Implementation

One key aspect for our implementation of these iphone-style buttons was the semantic structure of our forms.  We're pretty big into semantic markup, and as it happened that worked out very much in our favor.  The following is a snippet that shows how the button is rendered into our form:

 

You can see that the list item tag is classed as a "button", which we use to achieve the outset border look of the native button by styling the list item tag *as* the outset border.  We probably would have needed an extra[neous] div tag around the button without this.

You can see the effect below, first in the native implementation and then in our HTML5 implementation:

Native-button-closeup
Html-button-closeup

 

CSS

The styling leverages some key features of the CSS3 spec that are generally well supported across WebKit browsers including box shadows (and inset box shadows), linear gradients and text shadows.

 

Quick iOS App for Comparing All Fonts

I threw together a quick iOS app for viewing all of the fonts on the device. There are some more featureful apps for looking at each of the device's fonts, such as Fonts, but we didn't see any that let us view a custom string in every font, all in one table. But it only took 20 minutes or so to create exactly what we needed. I've shared the source on GitHub at https://github.com/raylillywhite/FontTest, in case it sounds useful to anyone. As you can see below, it was quick and dirty ;)

Screen_shot_2011-12-06_at_6

A hack/ship git workflow with shared remote branches

This post describes how we've come to develop some git scripts and shortcuts to help manage our development process at Hotel Tonight. All the aliases/functions I use in the examples below are documented there.

Some History

When we first started working on Hotel Tonight, we adopted the hack && ship workflow to facilitate performing all work on feature-specific branches. As long as we had fewer developers than software projects, that worked great, because it was very unusual for more than one person to work on a feature before it was ready to ship to master.

Somewhat similar to GitHub's workflow, we don't have formal releases, and like to keep master always in a deployable state. So that means that when we want to collaborate on a new feature, we do it in a remote branch. And that's where things become tricky.

The dark side of rebasing

The original hack script pulls in other people's changes from master, by rebasing your branch onto master each time. This presents two problems if you're working on a shared remote branch with others -- first, it's not what you're most interested in. That is, while you'd like to keep up with changes on master, what you really want is to keep up with other people's changes on your branch.

Second, and this is what really bit us, if you rebase commits you've already shared with others onto master, you're in effect rewriting history, and chaos ensues. Witness:

I create a new branch and start working on some feature--

[chris example (master)]$ gcb disco_ball
 Switched to a new branch 'disco_ball'

[chris example (disco_ball)]$ vi ball.rb

[chris example (disco_ball %)]$ gac
[disco_ball 73c2c53] Some initial thoughts on implementing the ball
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 ball.rb

[chris example (disco_ball)]$ git push origin disco_ball
 To /home/chris/example      * [new branch]      disco_ball -> disco_ball

Now my coworker checks it out, enters some of his ideas--

[patrick example (master)]$ git checkout -t origin/disco_ball
 Branch disco_ball set up to track remote branch disco_ball from origin.
 Switched to a new branch 'disco_ball'

[patrick example (disco_ball)]$ vi ball.rb

[patrick example (disco_ball *)]$ gac
 [disco_ball 819d41c] Some alternate disco-ball strategies
 1 files changed, 1 insertions(+), 1 deletions(-)

Meanwhile, say there have been some changes on master, and I want to pull those in, and then do some more work on the ball, then share that. Our old hack would do a rebase onto master..

[chris example (disco_ball)]$ git rebase master
 First, rewinding head to replay your work on top of it...
 Applying: Some initial thoughts on implementing the ball

[chris example (disco_ball)]$ vi ball.rb

[chris example (disco_ball *)]$ gac
 [disco_ball a76373e] Lighting-related work
 1 files changed, 1 insertions(+), 0 deletions(-)

[chris example (disco_ball)]$ git push
 To /home/chris/example
   0ae267e..3fc53df  master -> master
 ! [rejected]        disco_ball -> disco_ball (non-fast-forward)
 error: failed to push some refs to '/home/chris/example'
 To prevent you from losing history, non-fast-forward updates were rejected
 Merge the remote changes before pushing again.  See the 'Note about
 fast-forwards' section of 'git push --help' for details.

And here's where things break down. My history doesn't match up with the remote history anymore (3fc53df is from master):

[chris example (disco_ball)]$ gl
 a76373e Lighting-related work
 06bd2d1 Some initial thoughts on implementing the ball
 3fc53df An important component to keep the people upstairs away from us
 0ae267e Create example repo

[chris example (disco_ball)]$ gl origin/disco_ball
 73c2c53 Some initial thoughts on implementing the ball
 0ae267e Create example repo

Git suggests merging the remote changes, but that isn't pretty--

[chris example (disco_ball)]$ git pull origin disco_ball
 From /home/chris/example
  * branch            disco_ball -> FETCH_HEAD
 Auto-merging ball.rb
 CONFLICT (add/add): Merge conflict in ball.rb
 Automatic merge failed; fix conflicts and then commit the result.

[chris example (disco_ball *+|MERGING)]$ cat ball.rb
# There should be a whole ton of little mirrors on it!
<<<<<<< HEAD
# Also, important that we shine light at it.
=======
>>>>>>> 73c2c5395095d5f0502db8d397d80f3aa029f1ec

Alternately, we could force push, but then look what happens when Patrick tries to update:

[patrick example (disco_ball)]$ git pull
 From /home/chris/example
  + 73c2c53...a76373e disco_ball -> origin/disco_ball  (forced update)
 Auto-merging ball.rb
 CONFLICT (add/add): Merge conflict in ball.rb
 Automatic merge failed; fix conflicts and then commit the result.

[patrick example (disco_ball *+|MERGING)]$ cat ball.rb
<<<<<<< HEAD
# What if it's just one big shiny sphere?
=======
# There should be a whole ton of little mirrors on it!
# Also, important that we shine light at it.
>>>>>>> a76373ed377efdc65849e887e8694eeca0e23730

What a mess! So, clearly, this doesn't work. And, if you start squashing stuff, which we like to do sometimes, so that we don't have to spam github with all our intermediate work, the history-rewriting merge-conflict-creating situation becomes even uglier.

A kinder, gentler way to share

In order to control this potential for chaos, we needed a better strategy for how branches are shared. This is the procedure I came up with:

  1. When you 'hack' on a shared branch, you rebase your local changes onto the remote branch, not onto master. New commits from master are not pulled in during hack.
  2. When you 'share' your changes to an existing shared branch, first do an interactive rebase onto the remote, so you can squash, then merge in anything new from master, right before pushing. This is the only safe time to merge in from master, because you don't want to squash changes from master in amongst your local changes.
  3. When you 'ship' to master, do an interactive rebase onto master, to do a big squash of all the branch work -- and at that point, the remote branch is considered deprecated and should be removed, since its history no longer matches.

Here's what that process looks like, starting over with a new disco ball branch:

[chris example (master)]$ gcb disco_ball
 Switched to a new branch 'disco_ball'

[chris example (disco_ball)]$ vi ball.rb

[chris example (disco_ball %)]$ gac
 [disco_ball 77f1fa3] Some initial thoughts on implementing the ball
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 ball.rb

[chris example (disco_ball)]$ share
 Successfully rebased and updated refs/heads/disco_ball.
 To /home/chris/example
  * [new branch]      disco_ball -> disco_ball

OK, so 'share' at this stage basically just created a new remote branch for us. Now Patrick wants to check that out; we'll give him a shortcut to see what's available and 'borrow' it:

[patrick example (master)]$ borrow
 Already on 'master'
 Already up-to-date.
   disco_ball
   master

[patrick example (master)]$ borrow disco_ball
 Branch disco_ball set up to track remote branch disco_ball from origin.
 Switched to a new branch 'disco_ball'

Now let's say Patrick makes a few changes, and wants to share them with me.

[patrick example (disco_ball)]$ vi ball.rb

[patrick example (disco_ball *)]$ gac
 [disco_ball 840074c] Another thought on the disco ball
  1 files changed, 1 insertions(+), 0 deletions(-)

[patrick example (disco_ball)]$ vi ball.rb

[patrick example (disco_ball *)]$ gac
 [disco_ball bb06e68] No, wait, that was stupid, this instead.
  1 files changed, 1 insertions(+), 1 deletions(-)

[patrick example (disco_ball)]$ share -notest

At that point he'll have a chance to squash:

pick 021e621 Another thought on the disco ball
pick f3b8218 No, wait, that was stupid, this instead.

# Rebase 77f1fa3..f3b8218 onto 77f1fa3
...

The initial commit on the branch, which already exists in the remote, isn't listed here; that wouldn't be safe to squash.

When the rebase is done the share will merge in the latest from master, and push to the remote. Note the '-notest' parameter; normally 'share' would notice that there was new stuff from master merged in, and run tests before pushing to make sure they hadn't broken anything -- since there aren't any tests set up for this example repo, we'll just skip that. In practice, you'll likely need to modify the 'runtests' script to actually run your project's tests correctly.

When I next 'hack', I'll get both Patrick's changes, and the latest from master that was merged in when he shared:

[chris example (disco_ball)]$ hack
 From /home/chris/example
  * branch            disco_ball -> FETCH_HEAD
 Updating 77f1fa3..0bf7d4c     Fast-forward
  ball.rb    |    1 +
  ceiling.rb |    1 +
 2 files changed, 2 insertions(+), 0 deletions(-)
  create mode 100644 ceiling.rb
 Current branch disco_ball is up to date.

Ship It

So now if I'm ready to release the feature to master, I can just 'ship' normally. I'll get a chance to squash everything on the branch:

pick 77f1fa3 Some initial thoughts on implementing the ball
pick dd47d48 Another thought on the disco ball

# Rebase a6ed99a..0bf7d4c onto a6ed99a
...

And then it's merged into master, and I'm warned to get rid of the remote. The 'gbdone' script will remove the shipped local and remote branches.

[chris example (disco_ball)]$ ship
 From /home/chris/example
  * branch            disco_ball -> FETCH_HEAD
 Already up-to-date.
 Current branch disco_ball is up to date.
 [detached HEAD 00db9d0] A basic disco ball
  1 files changed, 2 insertions(+), 0 deletions(-)
  create mode 100644 ball.rb
 Successfully rebased and updated refs/heads/disco_ball.
 Switched to branch 'master'
 Updating a6ed99a..00db9d0     Fast-forward
  ball.rb |    2 ++
  1 files changed, 2 insertions(+), 0 deletions(-)
  create mode 100644 ball.rb
 To /home/chris/example
    a6ed99a..00db9d0  master -> master
 Switched to branch 'disco_ball'
 IMPORTANT: Please remember that your local branch has a conflicting
 history with its remote now, due to rebase onto master. Don't let anyone
 keep working on the remote; it needs to be blown away via either outright
 removal or forced push.

[chris example (disco_ball)]$ gbdone
 Switched to branch 'master'
 Deleted branch disco_ball (was 00db9d0).
 To /home/chris/example
  - [deleted]         disco_ball

A slightly more cautious way to ship

Alternately, you may want to do a code review and/or some extra testing, after rebasing onto master and squashing your changes, but before shipping. So we added a convenience method to 'pack' your code into another branch before release. Then you can 'share' that for code review, or just play with it locally, and ship it normally when you're done.

[patrick example (disco_ball)]$ pack
 Switched to a new branch 'disco_ball_release'
 [detached HEAD f77edf3] A basic disco ball
  1 files changed, 2 insertions(+), 0 deletions(-)
  create mode 100644 ball.rb
 Successfully rebased and updated refs/heads/disco_ball_release.

[patrick example (disco_ball_release)]$ gl
 f77edf3 A basic disco ball
 a6ed99a An important component to keep the people upstairs away from us
 0ae267e Create example repo

In summary

I guess that all sounds a bit complicated. But once you get into the routine, it's pretty frictionless. When you're not sharing a branch, hack and ship still work the same as before. When you are, all you need to do most of the time is 'share' periodically to keep everybody in sync, or 'hack' occasionally if you want to pull in a coworker's changes before yours are ready to share.

There are still a few edge cases, mostly around what happens when there are legit conflicts -- some of those the scripts will detect and help you out with, others we might still need to work on. Again, all of it's available here:

http://github.com/hoteltonight/git-scripts

Let us know what you think!

Rails and Mobile and Hotels Oh My!

Welcome to the HotelTonight engineering blog. We've spent the last year creating an awesome mobile app and an extensive back end to power our apps and support our hotel partners. Along the way we've employed and built some very interesting technology. The first version of the iPhone app, and corresponding server side components, were developed in about 2.5 months by two developers, with help from a designer, and business folks. During this time we built the first mobile-only online travel agency, and believe the most major innovation in travel since opaque deals.  This has delighted both end users and hotels. We didn't stop there.

HotelTonight has continued to innovate. We just released the world's first mobile extranet for hotels. In addition, we recently rewrote our entire iPhone app to get from 4.5 to 5 stars (watch for a future blog entry). We could be biased, but we believe it is the best travel app in the App Store. We have several cool projects in the works right now that we'll share here as they get released. We use a lot of open source, are working on releasing some of our own, and contributing back changes (some of which are available now in our GitHub forks).  

In the near future on this blog, some of the topics we're planning to cover include:

  • Our Git workflow and scripts
  • How we leverage "virtual credit cards"
  • Cool code and techniques used in our iOS app
  • Some fun CSS tricks
  • CoffeeScript, Haml, and templating
  • Interesting details on the code that actually books a room

Stay tuned for our first real blog entry. And, please tell us what you'd like to know about HotelTonight engineering and technology.