ChairNerd

Code, Design & Growth at SeatGeek

Jobs at SeatGeek

We currently have more than 10 open positions.

Visit our Jobs page

Employee Spotlight: Jamie Hooker, Director of Talent

Welcome to SeatGeek Employee Spotlights - an opportunity to meet the fantastic folks on our world-class team.

By day, we’re a group of talented developers, designers, marketers, and businessfolk working together to build something new and different. We represent live event junkies of every kind: diehard sports fans, passionate concert-goers, sophisticated theater enthusiasts, and more. From our lives outside the office and before SeatGeek, we all have interesting stories to tell.

Up next: Jamie Hooker, our Director of Talent.

Jamie Hooker

Name: Jamie Hooker

Role: Director of Talent

Where were you born?
I was born in Georgia - outside of Atlanta.

Have you always lived in NYC?
No — I’ve lived in close to 10 places, but have been in New York for almost three years. I came to New York from Wisconsin, and was in Tennessee before that, and Colorado before that — Colorado will always be home, though.

Where did you go to school?
I went to Vanderbilt — go ‘Dores! I’m one of a few Vanderbilt folks here, and actually wasn’t the first Vandy hire — Ben Clark holds that title. There are four total now, and hopefully we can add more to that count!

Where is the weirdest place you’ve ever lived?
I don’t think I’ve lived anywhere very weird, but when I was little, we lived in France and Belgium, which is unique I suppose. I was there for a couple years when I was a toddler so my memories are pretty limited — we moved back to the states when I was about 5 years old.

Any funny roommate or apartment stories in NYC?
When I first moved to New York, we decided to turn our living room into an additional bedroom, as many people do, to make our apartment more affordable. I was lucky enough to be able to live in the extra room — my “lean-to.” I had to get a wall built, and for some reason there was a really big disconnect between my landlord and the wall company, and I ended up living without a wall for my first two months here. Basically, you’d walk into the apartment and be in the living room, the kitchen, and my bedroom at the same time. The wall is still there, and I’m still in the same apartment, but I’ve since upgraded rooms.

What’s the best project you’ve worked on at SeatGeek?
Being involved with hiring so many people in one year was really exciting — we hired around 50 people, more than doubling the size of SeatGeek. Of course, there were a lot of people who were a part of that, but being able to be involved was really exciting. Seeing that SeatGeek had gotten to a point that we were really taking off was so cool.

What are three “fun facts” about yourself that people would be surprised to know?
- I have not always been a Broncos fan — shocking, I know. I did not become a Broncos fan until I moved to Colorado in the fifth grade — prior to that, I have a vivid memory of the Broncos and Falcons playing in the Super Bowl and rooting for the Falcons. I try not to remember my pre-Broncos era — I think I’ve seen the light since then. - I really, really love the Nathan’s Hot Dog Eating Contest on the 4th of July — it’s one of my favorite American pastimes. I went to the event the first 4th of July I spent in New York, and Joey Chestnut won that year, but he didn’t break the record. It was the first time they split the contests into Men’s and Women’s competitions, and Sonya “The Black Widow” was the female champ. - I lived in Europe as a child, but I guess you knew that already.

Any favorite place(s) to hang out in NYC?
I like to watch sports at my favorite sports bar, Triona’s — any time there’s a major game going on or if it’s an NFL Sunday, you can find me there. It’s become a really fun gathering place for me and my friends. Other than that, I’m probably eating out. I do that a lot, and keep a running list of places so that I can get to as many different restaurants as possible. I’m also really lucky that my building has a nice courtyard, so I spend a lot of time eating dinner outdoors or drinking a glass of wine out there.

What’s the best vacation you’ve ever taken?
Last fall I went to Thailand and we split up time between several different cities — they were all pretty amazing and wonderful in their own right. But the bulk of the trip was spent on an island for some beach time — it was really nice to be literally on the other side of the world. And I got to play with elephants, which was a big highlight.

Do you have a favorite SeatGeek snack?
I have two favorite snacks — one is Babybel cheese and the other is chocolate, which we always have a really nice selection of. They serve very different snack purposes in my life, but they’re equally important.

Why do you love SeatGeek?
I love SeatGeek because of the people who work here. Everyone really believes in SeatGeek as a product, and you can tell that people here are really passionate about what we do. People are really driven and work really hard, and it’s very easy to believe in the mission and what our product stands for. People are also really great from a social aspect as well — they hang out with each other, get along well, and I enjoy spending time with my coworkers. It’s exciting that we can all work together on something that’s so cool.

What is your favorite part of the new office?
I still need to do some more exploring — I like so much of it — but I think the stadium seating is really cool. Not only is it great because it gives everyone in the company a really nice spot to congregate, but I think it will be really great for hosting events. It’s perfect for hosting meetups or recruiting events.

Using GIS Technologies for Venue Mapping

Event Page

At SeatGeek, our interactive venue maps are something we take a lot of pride in. We are constantly striving to make our maps more useful, performant, and beautiful. Whether this involves us opening up the MLB rulebook in order to get the pitcher’s plate on baseball fields correct, or implementing a custom canvas tile layer to achieve buttery smooth, 60FPS map interactions, we are always looking for ways to improve.

In this post, we will discuss something a little more hidden from the day-to-day customer interactions with our venue maps. Over the past several months, we have drastically changed the way our maps are created, updated, stored, rendered, and served. For now, this has very little effect on the appearance and usage of our maps, but it lets us produce new maps faster (turns out Taylor Swift fans don’t like waiting for maps) and opens up a multitude of ways in which we can use our maps data.

Background

Maps have been a core part of SeatGeek since the very early days. Initially, we used a variety of third-party venue map services. While these services were functional, we felt somewhat constricted by them. We had very little control over the styling and no direct control over fixing any mapping mistakes or prioritizing new maps. Additionally, most of these tools used older technologies, such as Flash, whereas we wanted to build our site using a more modern web stack. Therefore, we decided to bring map creation and interface development in-house. An early third-party map can be seen here in a throwback event page from 2010:

2010 Event Page

Original Mapping Pipeline

Our original mapping pipeline was conceived to launch by the 2010 NFL season and has remained mostly unchanged since then. Maps are created in Adobe Illustrator by a group of very detail-oriented people on our mapping content team. Aside from some scripts that assist with drawing rows and labels, this is mostly a manual process. These SVGs are uploaded to a web service that pulls out some simple geometry data which is used for drawing highlights as well as placing the DealScore markers. It then takes the vector SVG and performs tile-cutting, producing a set of square raster tiles for each zoom level that we support in our clients.

The geometry data and associated raster tiles are stored in Amazon S3. When map data is requested, whether for an event page, venue page, or visualization, a lightweight (and heavily cached) service serves this data from S3 without any modification.

Advantages

This system is really nice due to its simplicity. Mapping outages are exceedingly rare due to the fact that we are just serving from S3. A map SVG ingested into the system is effectively guaranteed to look exactly as planned when a user requests that map.

Disadvantages

The downside of this system is a complete lack of flexibility. We have thousands of rendered maps sitting in flat files on S3. This means things as simple as “change the color of the sections” or “change the fonts of the labels” would require thousands of files to be altered and reprocessed. Additionally, it is hard to answer any questions about our data, e.g. “how many rows does section MVP 117A have at Yankee Stadium?”

Goals

Our primary goal has been to essentially “unlock” all of this valuable and interesting information we had sitting in thousands of flat files. Therefore, we needed some sort of persistent datastore in which to store our map geometry and associated metadata. Additionally, we really wanted to separate the data from the presentation. For example, instead of storing the color and stroke of a section object alongside the geometry, as was done in the SVG, we wanted to style objects, labels, and fields based on a series of rules. Finally, we wanted to be able to do this on the fly, rather than pre-baking a set of image tiles for every map. This would let us more quickly and dynamically make changes to our maps.

GIS Pipeline

When looking for inspiration for our new pipeline, we looked towards the world of GIS (Geographic Information Systems). There is very impressive work being done in this field and a substantial number of mature open source projects as well. In particular, the Mapbox team has made some great open source contributions. Many of the most difficult aspects of creating a map pipeline and serving geospatial information have been solved in these projects and systems, so we were eager to use and mimic them heavily.

One of the inherent complications of our maps is that they are 2D projections of a 3D, overlapping structure. When creating our maps, we have several constraints that we try to balance: the map should be generally the shape of the venue, each section should be fully visible, and sections should generally have the correct spatial orientation with respect to each other and the field. There is a lot of artistic leeway in how these sections are portrayed.

The Treachery of Maps

Due to this, we are unable to use true latitude/longitude when storing our maps. However, given that none of these tools are designed to work within a local coordinate system, we have to do a little customization (hacking) to make these tools work for us.

GIS Pipeline Overview

Map Creation

Similar to our original pipeline, our maps are initially created using Illustrator as well as Rhino3D. We have a team that is very comfortable and efficient in these tools, so we did our best to try and fit the pipeline into their workflow rather than the other way around. One of the major changes, however, is that the maps produced in Illustrator or Rhino3D only need to contain the geometry and metadata. The mapping content team no longer needs to worry about section colors, label placement, etc, which greatly speeds up their workflow.

OSM Database

Once a map has been created in Illustrator, it is uploaded to a web service that converts the geometry encoded in SVG paths into Open Street Map (OSM) elements stored in a Postgres database. One of the first challenges is that OSM supports only polygonal data, whereas Illustrator supports Bézier curves. Therefore, we first perform a piecewise approximation of the curves as described by Fisher (2000) [PDF]. Then, we parse the IDs of the SVG elements, which we overload to support inputting arbitrary key-value metadata in Illustrator (e.g. this row is wheelchair accessible).

In OSM, objects are generally classified into 3 categories: “nodes,” “ways,” and “relations.” We found this to be an incredibly elegant data model and it worked fantastically for our purposes. In our world of venue maps, a “relation” would be used to represent a section. This “relation” would have multiple polygons which are the “ways” (many sections consist of distinct shapes in our maps). Each of these ways would have ordered “nodes” that define the shape of the polygon. We use this approach for defining many things, such as rows in a section, sections in a group or level, etc.

Further drawing inspiration from the OSM model, any changes to a map are stored as a changeset, allowing us to maintain an archive of maps at any given point in time without storing an excessive amount of data.

PostGIS Database

While the OSM data model is very flexible and great for maintaining a full history of all the maps, it is not a great format for rendering map data in production. Therefore, we use osm2pgsql to dump current versions of each map into a more “flat” format that is more useful for rendering and geospatial operations.

Map Rendering

Image Tiles

For all of our interactive maps, we generate image tiles that the clients stitch together so they appear as one large image. Upon receiving a request for a tile, the tile server calculates the bounding box defined by that tile. The server then queries our PostGIS instance for the relevant geometry and metadata, a particularly efficient query due to spatial indexing.

Given the geometry data, mapnik renders the map tile based on styles that we define in CartoCSS. This lets us style maps using CSS-like style sheets, achieving our goal of separating data from its visualization. Here is an example of what one of our style layers might look like:

CartoCSS

Mapnik performs complex operations, such as label collision detection and high-quality antialiasing, and does them very quickly. In fact, the entire chain, from querying PostGIS through responding to the client HTTP request with a tile, takes less time than pulling a static tile in the old pipeline from S3 (all under 100-200ms). Now, we heavily cache these tiles so as to make them load even faster for clients, but it goes to show just how performant these open source GIS technologies are.

Next Steps

To date, we have been working on just achieving parity with our old pipeline, at least from the point of view of a user. However, now that we have all of this geometry data for our maps in a datastore, we are looking towards new ways to use this. Whether it is displaying data visualizations, points of interest, or new ways to interact with our maps on our event pages, we have a lot of possibilities to explore.

Final Thoughts

One of the themes that kept coming up in this project was when to use existing software vs. when to build it in-house. More than most projects we have worked on, this consistently straddled the line of “very similar to existing use cases” but “just different enough to make it difficult.” Although it didn’t always result in the most “elegant” software, we found that the gains from using high-quality open source libraries were more than worth any difficulties. It’s natural for a software engineer to want to write code to solve a problem, but sometimes it’s worth taking a step back and seeing if there are any shoulders you can stand on.

And now a quick plug: If these are the types of problems that excite you, we have a lot more of them in the pipeline. Check out our jobs page and come join us!

SeatGeek Android 6.0: Sell With SeatGeek

It seems like just a few weeks ago we were working on adding native SeatGeek Checkout to make buying tickets to live events simpler. Alas, we’ve actually spent the past nine months—and 3 major version changes—working on our ultimate goal of making SeatGeek for Android the easiest-to-use live event app ever.

Today marks the release of SeatGeek for Android 6.0: The One With Ticket Selling.

Before we get to the selling part, let’s first take a look at some of the things we’ve released in the interim.

An amazing new look and feel

An amazing new look and feel. SeatGeek is constantly putting a ton of thought into making the best possible experience for users, and part of that is how we convey SeatGeek as a whole. SeatGeek does this by creating a comprehensive and cohesive platform for design that users absolutely love. SeatGeek’s new brand for Android combines Google’s material design with SeatGeek’s core aesthetic to present a truly engaging experience that makes sense to first-time and long-time users.

View your tickets in-app

View your tickets in-app. After we integrated in-app checkout in the fall of last year, viewing your tickets and scanning in seemed like the natural next step for us. We took time to carefully cater the experience to make it one that would get you excited for your event, whether it’s seeing the Flyers play the Islanders at Barclays Center or Justin Bieber at Madison Square Garden. We employ a heavy use of performer imagery and complementing colors to build a ticketing experience that feels more like you’re diving into the event than just getting out a flimsy piece of paper to show the concierge. One of the best parts about our in-app ticketing experience: you don’t have to have an internet connection when you get to the venue… we automatically sync your tickets to your phone before the event so you don’t have to worry about too many people connecting to the network in the same area!

On top of that, we’ve included a bonus feature! When you’re approaching the venue for your event, we’ll automatically notify you to give you an incredibly convenient way to open your tickets from anywhere on your phone—a feature users already love.

Go with friends

Go with friends. One of the best parts of going to a live event is getting to experience it with people you love. One of the worst parts can be figuring out where to meet up before the concert to give each of your friends their ticket—especially if any of your friends are running late. Despite how much you love ‘em, you don’t wanna miss the opening ceremony! To make getting in easier for everyone, we added the ability to send tickets directly to anyone with an email address, phone number, or SeatGeek account from directly within the app!

Finally, it’s what 6.0 is all about… Friend can’t make it? Save some $$$… sell tickets! We want going to live events to be a great experience, which includes not losing money on tickets you or your friend can’t use. With SeatGeek Marketplace you can now list your tickets using the new in-app Sell feature from the My Tickets section. You’ll get suggested pricing based on our world-renowned Deal Score™ algorithm, notified when your tickets sell, and paid by direct deposit or Venmo!

Get it now!

The updated Android app is rolling out today in the Play Store, so check it out and let us know what you think!

SeatGeek’s 14th Hackathon

We recently wrapped up the 14th SeatGeek Hackathon, a quarterly company event focused on innovation and collaboration. It’s a caffeine-fueled sprint where all that matters for two days is building something cool – and for many of our team members, it’s their favorite thing about working at SeatGeek.

What is a Hackathon?

A hackathon is an event where people work in teams to develop an idea or concept. They’re generally short – short enough to allow the participants to work in a sustained burst of energy. The time limit encourages teams to come up with solutions to problems they might not otherwise have thought of.

Why do we hold Hackathons?

The point of these events is to explore “out there” ideas that might not otherwise make it into our daily workflow. It’s uninterrupted time to quickly develop a proof of concept for ideas that are meaningful to SeatGeek, whether they’re business-related or not. The big picture view is that our Hackathons are time to learn and to teach. A project might involve someone’s first line of code, graphic design work, or marketing pitch. While we often remember and celebrate the end products developed during a hackathon, we think the knowledge sharing that occurs throughout the process is even more important.

What do people work on?

Hackathons were originally all about software, but the concept has since expanded to include a variety of different types of events and projects. Software is still usually a major component of the products developed, but it’s not required to be.

Here are a few of the ideas that emerged out of Hackathon 14:

An improved interactive buying experience

SeatGeek offers fans interactive seating maps and a stadium view from each section to allow for the most informed buying experience possible. But the winners of this quarter’s Hackathon took that concept a step further, building an answer to the following question: what if SeatGeek users could experience what a live event would actually look and sound like from each seat – with a 360 degree view – as they browsed listings?

Programmatically generated marketing assets

We have some very talented designers on our team, who work on SeatGeek campaigns both online and offline. After noticing some success with team-specific advertising on social media, a Hackathon group of marketers and engineers built a tool that edits the colors and text on previously developed marketing assets with the click of a button. Now, SeatGeek can let fans know about the best deals on every game – in proper style.

Easily expensed company perks

Everyone’s favorite company benefit is the monthly ticket budget to use on attending live events. The dark side of this perk is the end-of-month scramble to file the expense for reimbursement. Enter “Get Tickets” – a software tool that not only recommends event tickets to buy within the perk budget, but also automatically files your expense reports after purchase.

New hire survival kit

New hires at SeatGeek have a lot to learn: meeting new teammates, finding the bathroom, and understanding the snack landscape are all keys to week one success. A scavenger hunt to aid in this employee transition makes the onboarding process worry-free – and a whole lot more fun.

Removing the bias in candidate screening

Hiring is hard, and the challenges present themselves even before the interview stage begins. Candidate screening is rich with opportunities to let biases influence decision making, and a team of recruiters and engineers teamed up to hack our software and create the most impartial process possible.

SeatGeek’s New Look

At SeatGeek, our mission is to make it easier to experience the thrill of live events. Today we’re excited to unveil a new brand that helps us achieve this goal.

Our new brand communicates what we believe a ticketing experience should be like: simple, smart, trustworthy, and fun. We work day in and day out to create this kind of experience and now have an identity that reflects these core values.

Simple

Buying good tickets shouldn’t be a struggle. On SeatGeek you can buy and sell tickets in just a few taps. By removing friction we hope to empower more people to see their favorite live events.

Smart

Deal Score™ analyzes thousands of tickets to help you easily spot the best deal. Carefully crafted maps let you see the view from your seat from the palm of your hands. These are just a few of the ways we leverage data and technology to help you enjoy more live entertainment.

Trustworthy

We pride ourselves on making the ticketing experience as trustworthy and transparent as possible. Every ticket on SeatGeek is backed by a 100% guarantee. Additionally, all of our prices include fees and shipping costs so there are no surprises at checkout.

Fun

While buying and selling tickets won’t ever be quite as fun as the main event, we do our best to add playful elements where we can.

The ticketing industry has come a long way over the past several years, but there is still a long way to go. By communicating our core values through our new brand we hope to spread our fan-first mentality and continue to push the industry forward for music junkies, sports fanatics, casual event goers, and everyone in between.

See the new look in action.

SeatGeek Marketplace

We started SeatGeek out of personal frustration. Buying tickets was so consumer-antagonistic that we went to far fewer events than we’d like. Today, millions of fans shop on SeatGeek. We hope that by creating a simple, transparent way to buy tickets we’ve empowered people who would have stayed home to instead get out and see a show or game [1].

Today we’re taking a big, exciting leap towards further democratizing live entertainment: we’re introducing SeatGeek Marketplace, a new way to sell tickets. As a component of Marketplace, we’re also launching peer-to-peer ticket transfer and sales.

Selling on SeatGeek

We set out to create a selling experience as easy as the SeatGeek buying experience. The status quo for mobile ticket selling is a pain; as a result, fans often throw out unused tickets or simply don’t buy them in the first place [2]. For years, we’ve been getting emails from users asking how they can list their tickets on SeatGeek. That feature has been requested more than all other features combined.

Selling on SeatGeek is dead simple – upload your tickets and we take care of the rest. After uploading, we automatically parse the file for all relevant details, such as the event, section, row, etc. We use our valuation engine – the same one that powers Deal Score – to recommend a price that maximizes your expected earnings.

Suppose you bought tickets to see the Raptors play the Clippers this weekend, but your spouse is sick, so the two of you decide to stay home and catch up on Netflix. The money you paid for those tickets is not lost! In 17 seconds you can have them listed on SeatGeek [3].

Sharing & Peer-to-Peer Sales

Rather than selling your Clippers tickets on the Marketplace, suppose you want to give them to a friend. The SeatGeek app now allows you to send tickets to anyone else, right from your phone. Your friend will be able to get into the game with the app – no printing, no meeting in front of the venue.

You also have the option to request money as part of your ticket transfer. In these cases, the recipient won’t be able to access the ticket until they pay you (protection against that friend who keeps telling you she’ll “pay you soon” for a dinner bill from four months ago). Even though money is changing hands, SeatGeek charges no fee for these transfers. You can get the money paid into your bank account or sent to you on Venmo.

Try It Out

Sharing and Peer-to-Peer Sales are available today on our website and iOS app. Marketplace is available on our website today and will be available on iOS & mobile web within the next few days. We are busily working on adding these features to our Android app, and will be rolling out Android support over the next few months.

Get Out There

We believe that experiences – more than physical things – bring happiness to people’s lives; our mission at SeatGeek is to build software that enables that happiness.

We created Marketplace to empower you to have more fun. Getting to a game or show doesn’t have to be a pain, and yet often still is. We hope that by creating a simple, liquid marketplace for tickets, live entertainment becomes a bit more accessible to everyone. We hope you love it.

Notes

[1] Venues and teams we’ve partnered with tell us that 80% of the users who bought tickets on SeatGeek were completely new to their arena.

[2] By way of analogy, suppose there were no way to resell or give away an iPhone – when you bought a new phone, you simply had to discard the old one. People’s willingness to pay for new iPhones would decrease. I know I’m far more willing to buy products online when it’s free and easy to return whatever I can’t use.

[3] The 17 second datapoint comes from this video, but single digit times should be attainable! Standard marketplace disclaimers apply here: we can’t guarantee your tickets will sell. The ticket resale market is dynamic, so prices may have moved down since your purchase, meaning you only get back a fraction of what you paid (on the other hand, prices may have increased, giving you some extra spending money).

Solving Address Entry on Android: PlacesAutocompleteTextView

An address-autocompleting text field for Android

When we started building our mobile checkout experience on Android, we wanted to ensure our mobile data entry experience was as smooth as possible. At its core, checkout is a painful experience riddled with text entry fields, lost data and obscure errors. There have been many great posts about optimizing the checkout experience for mobile that we drew inspiration from, each with their own set of best practices, tips and tricks. We tried our best to distill that pool of information into a few key user stories to focus on:

  1. I do not want to enter data that you should be able to infer

  2. I do not want to type the same information more than once

  3. You should already know where I am when I need to enter my address

Using recommendations, best practices and imitating the best checkout forms we saw, we built what we think is a great component to any checkout form: an address autocompletion view. Today, we’re making it open source: PlacesAutocompleteTextView

Text entry on mobile

Mobile devices have many strategies for simplifying text entry; autocorrection, input suggestions, and swipeable keyboards all do wonders to simplify the user experience. Despite all that, it remains true that physically typing text is challenging: digital keyboards generally have smaller keys, users do not hold the phone in both hands all the time and autocorrect can only correct so well.

The text entry problem is then exacerbated by needing to type postal addresses for billing and shipping information. Addresses often switch between text and numeric input, have pronouns not in autocorrect dictionaries and are often broken up into several different input fields that the user has to navigate.

We looked at how other sites and apps were limiting the amount of data that a user needed to enter to complete a checkout form and saw that an autocompleting address entry field was far above the rest in terms of reducing keystrokes.

PlacesAutocompleteTextView

PlacesAutocompleteTextView is an extension of the Android framework AutocompleteTextView that interacts with the Google Maps Places Autocomplete API to suggest addresses as soon as a user starts typing. Here’s a demo of the view in action in the checkout form of the SeatGeek for Android app:

By autocompleting addresses as a user enters them, we’re already pretty far along the path to achieving the goal of our first story: not entering data that you should be able to infer. But let’s take a deep dive into a few of the unique features that take the PlacesAutocompleteTextView experience to the next level.

Place history

Repeating yourself is the worst. Sure there are probably more frustrating things than repeating yourself, but in the context of checkout forms having to type the same address more than once is ludicrous, even if there’s full autocompletion for it. Fortunately, PlacesAutocompleteTextView maintains a full history of selected results from the Places API so that entering past results is a breeze.

Normally, the threshold for queuing the Places API to get results is 3 characters entered, but if the user has already previously selected a Place from the popup, the PlacesAutocompleteTextView will suggest past results. By default, it will save up to 5 places and offer them as suggestions as soon as the user starts typing. This history is stored in a file on the filesystem, so previously entered addresses will be remembered across sessions as well.

Remembering the locations that the user has previously selected, the PlacesAutocompleteTextView is checking the box next to our second user story, “I do not want to type the same information more than once”.

Location biasing

154 million. That’s the number of postal addresses in the United States [source]. That’s a lot. When the user starts typing their address, they’re reducing the set of potential addresses significantly, but not nearly enough to provide meaningful results. How can we ensure that the most relevant addresses are surfaced to the user? There are probably a few good heuristics for this problem, but by default the PlacedAutocompleteTextView biases the place results to the location of the user. So if you’re in New York at the SeatGeek office, “235 Park” will complete with 235 Park Avenue South, New York, NY.

Location biasing can be done two ways. By default the Google Maps API will bias autocomplete results based on the IP of the device. This is a reasonably good method for biasing, but IP address lookup might not be more accurate than a few miles and lookup can break completely when used in conjunction with Proxies/VPN’s/etc. Fortunately, most Android devices provide an easy (and more accurate) mechanism for determining location: GPS.

The PlaceAutocompleteTextView exposes an API for biasing location results to an Android Location returned from the Android location APIs. This allows for highly accurate biasing of location results and ensures that the most relevant addresses are at the top of the results list. This nails the user expectations outlined in the third user story, “you should already know where I am when I need to enter my address”.

What about Google Play Services’ Places API?

Google recently launched Place APIs as part of Google Play Services. They provide a pretty clean and similar API to accessing the Places Autocompletion API as our library does, but there are two primary reasons we haven’t migrated to using it yet:

  1. It’s still impossible to fetch the full Place Details from the Google Play Services implementation, without which you cannot get the full breakdown of address components, just a human readable address string
  2. Our implementation handles binding the data to UI components to accelerate the development process by not having to implement all the UI yourself. With Google Play Services, you would still need to create your own FilterAdapter, AutocompleteTextView, etc.

Wrap up

By combining Google’s Places Autocomplete API with the power of Android’s local storage and location features, we’re able to provide a great and painless address entry experience in our checkout flow. We’re also able to meet all of our goals of minimizing the character entry count, remembering previously entered addresses and suggesting the best addresses with the highest degree of relevancy to our users.

PlacesAutocompleteTextView is open source today and available on maven, so add it to your checkout flow and improve the form experience for your users tomorrow.

– SeatGeek Android Team

P.S. If a UX-driven product development process like ours is something that appeals to you, we’re hiring.

On the Train to Success: SeatGeek’s Experience With NYC Subway Ads

New York is packed with live events in the fall; the U.S. Open takes place in Flushing and the Jets and Giants kick off their seasons. With over six million daily riders, the NYC subway offered us an interesting advertising opportunity to reach fans in the largest entertainment market at the best time of the year for live events.

We’ve experimented with out of home advertising before, but nothing at the scale of the New York City subway. It was a big investment for us, but one that we’re ultimately glad we made, and we want to share a few things we learned along the way…

Creative and Design – Putting Fans Front and Center

We care an awful lot about design here at SeatGeek. Our ads had to stand out while at the same time conforming to rules and laws around rights and marks. In most cases we can’t use team names or logos. So, in response, we turned our attention to the crowd for this campaign, and it didn’t take long to find inspiration in the form of the fans themselves. Fans (and their enthusiasm) are what make an event extra special, so we focused our work on showcasing the craziest fans – those extra passionate people that stand out of the crowd.

Campaign Performance

Subway campaign performance was stronger than we expected. Like most marketers, we use a handful of attribution techniques for offline marketing, but one easy-to-explain method involves simply surveying users after they have completed their first SeatGeek transaction. Predictably, users did not credit the subway before the ads went up, but upon launching our campaign, 20% of new buyers attributed the subway as the way they first heard about SeatGeek. Even since the our ads came down, we haven’t seen much of a drop – subway ads are still among our largest attributed source of new customers in New York City.

After seeing how things went with this campaign, we’ll be likely to re-invest in the subway again. However the effects of the subway campaign have stretched beyond pure dollars and cents for us. We’ve received dozens of job applications from candidates who said they applied after seeing our ads. Our brand-tracking services show a nice uptick in New York City. And on a more personal level, one of our employees even scored a date with someone who recognized our ads!

If working on a campaign like this is the sort of thing that would get you fired up, consider applying for one of our open jobs! We’ve got a number of openings in design, marketing and more all listed on our jobs page.

Managing Application Server Dependencies With Aptfile

a simple method of defining apt-get dependencies for an application

A common pattern in application development is to create a file that contains dependencies for running your service. You might be familiar with some of these files:

  • package.json (node)
  • requirements.txt (python)
  • Gemfile (ruby)
  • composer.json (php)
  • Godeps.json (golang)

Having a file that contains dependencies for an application is great1 as it allows anyone to run a codebase and be assured that they are running with the proper library versions at any point in time. Simply clone a repository, run your dependency installer, and you are off to the races.

At SeatGeek, we manage a number of different services in various languages, and one common pain point is figuring out exactly how to build these application dependencies. Perhaps your application requires libxml in order to install Nokogiri in Ruby, or libevent for gevent in Python. It’s a frustrating experience to try and setup an application, only to be given a Cthulu-like2 error message about how gcc failed at life:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Installing collected packages: gevent, greenlet
Running setup.py install for gevent
building 'gevent.core' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -I/opt/local/include -fPIC -I/usr/include/python2.7 -c gevent/core.c -o build/temp.linux-i686-2.7/gevent/core.o
In file included from gevent/core.c:253:0:
gevent/libevent.h:9:19: fatal error: event.h: No such file or directory
compilation terminated.
error: command 'gcc' failed with exit status 1
Complete output from command /var/lib/virtualenv/bin/python -c "import setuptools;__file__='/var/lib/virtualenv/build/gevent/setup.py';exec(compile(open(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --single-version-externally-managed --record /tmp/pip-4MSIGy-record/install-record.txt --install-headers /var/lib/virtualenv/include/site/python2.7:
running install

running build

running build_py

running build_ext

building 'gevent.core' extension

gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -I/opt/local/include -fPIC -I/usr/include/python2.7 -c gevent/core.c -o build/temp.linux-i686-2.7/gevent/core.o

In file included from gevent/core.c:253:0:

gevent/libevent.h:9:19: fatal error: event.h: No such file or directory

compilation terminated.

error: command 'gcc' failed with exit status 1

----------------------------------------
Command /var/lib/virtualenv/bin/python -c "import setuptools;__file__='/var/lib/virtualenv/build/gevent/setup.py';   exec(compile(open(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --single-version-externally-managed --record /tmp/pip-4MSIGy-record/install-record.txt --install-headers /var/lib/virtualenv/include/site/python2.7 failed with error code 1 in /var/lib/virtualenv/build/gevent
Storing complete log in /home/vagrant/.pip/pip.log.

While projects like vagrant and docker can help alleviate this to an extent, it’s sometimes useful to describe “server” dependencies in a standalone file. Homebrew users can use the excellent homebrew-bundle - formerly brewdler - project to manage homebrew packages in a Brewfile like so:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env ruby

tap 'caskroom/cask'
cask 'java' unless system '/usr/libexec/java_home --failfast'
brew 'elasticsearch'
brew 'curl'
brew 'ghostscript'
brew 'libevent'
brew 'mysql'
brew 'redis'
brew 'forego'
brew 'varnish'

Simply have a Brewfile with the above contents, run brew bundle in that directory on your command-line, and you’ll have all of your application’s external dependencies!


Unfortunately, this doesn’t quite solve the issue for non-homebrew users. Particularly, you’ll have issues with the above approach if you are attempting to run your application against multiple non-OS X environments. In our case, we may run applications inside both a Docker container and a Vagrant virtual machine, run automated testing on Travis CI, and then deploy the application to Amazon EC2. Re-specifying the server requirements multiple times - and keeping them in sync - can be a frustrating process for everyone involved.

At SeatGeek, we’ve recently hit upon maintaining an aptfile for each project. An aptfile is a simple bash script that contains a list of packages, ppas, and initial server configurations desired for a given application. We can then use this to bootstrap an application in almost every server environment, and easily diff it so the operations team can figure out whether a particular package is necessary for the running of a service3.

You can install the aptfile project like so:

1
2
3
# curl all the things!
curl -o /usr/local/bin/aptfile https://raw.githubusercontent.com/seatgeek/bash-aptfile/master/bin/aptfile
chmod +x /usr/local/bin/aptfile

We also provide a debian package method in the project readme for those who hate curling binaries.

To ease usage across our development teams, a dsl with a syntax similar to bundler was created. The aptfile project has a few primitives built-in that hide the particulars of apt-related tooling:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env aptfile
# ^ note the above shebang

# trigger an apt-get update
update

# install a packages
package "build-essential"

# install a ppa
ppa "fkrull/deadsnakes-python2.7"

# install a few more packages from that ppa
package "python2.7"
package "python-pip"
package "python-dev"

# setup some debian configuration
debconf_selection "mysql mysql-server/root_password password root"
debconf_selection "mysql mysql-server/root_password_again password root"

# install another package
package "mysql-server"

# we also have logging helpers
log_info "🚀  ALL GOOD TO GO"

One potential gripe behind a tool like this is that it would lock you into the dsl without being very expressive. Fortunately, an aptfile can contain arbitrary bash as well:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env aptfile

update

# pull down a debian package from somewhere
curl -o /tmp/non-repo-package.deb https://s3.amazonaws.com/herp/derp/non-repo-package.deb

# install it manually
dpkg -i /tmp/non-repo-package.deb

# continue on

Another issue we found is that tooling like this can either be overly verbose 4 or not verbose enough 5. The aptfile project will respect a TRACE environment variable to turn on bash tracing. Also, if there is an error in any of the built-in commands, the log of the entire aptfile run will be output for your convenience.

For the complete documentation, head over to the Github repo. We hope you’ll be able to use bash-aptfile in creating a better, faster, and smoother developer experience.


  1. Though having multiple files for each language can be frustrating ;)

  2. https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454

  3. We actually manage service OS dependencies in a separate manifest for each service. In our case, this isn’t currently stored with the service being managed, leading to cases where a service is deployed but an underlying OS dependency doesn’t exist. The other issue we have is while we do have good records as to what extra dependencies a service needs - such as libmysqlclient-dev - it’s not always clear what initial packages are needed - such as a specific version of php. It’s very easy to install a library globally across your infrastructure and then forget where it’s used when migrating CI services :)

  4. Ever try figuring out what npm install is actually doing on verbose logging? Sometimes tooling outputs to stdout when it should go to stderr or vice-versa, resulting in a painful debugging experience.

  5. Suppressing output via -q flags should never suppress errors, and while this is almost never the case, sometimes you need to redirect all output to /dev/null in order to get rid of stuff like WARN deprecated gulp-clean@0.2.4: use gulp-rimraf instead. Not that you should ever pipe anything of importance to /dev/null.

Introducing Sixpack-java: A/B Testing for Android and Java Apps

A Java client for the Sixpack A/B testing framework

The SeatGeek engineering team is excited to announce the latest addition to the Sixpack A/B testing framework’s client list: sixpack-java. Designed with the goal of making A/B testing Android applications easy and painless, sixpack-java has a straightforward API and an easy setup process that should make measuring and analyzing your application design decisions a breeze.

If you’re unfamiliar with Sixpack or A/B testing in general, you can read more about it here.

Let’s take a look at how you might integrate sixpack-java into your Android app.

Android app integration

Note: it is assumed that before integrating the java client into your app that you have set up a running instance of Sixpack-server; for information on setting one up, check out the instructions here

First, you’ll need to add sixpack-java to your application dependencies. sixpack-java is available on the Sonatype snapshots repository while it’s in beta, so you’ll need to add the following to your build.gradle:

1
2
3
4
5
6
7
repositories {
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}

dependencies {
    compile 'com.seatgeek:sixpack:0.1-SNAPSHOT'
}

Now that you’ve resolved your dependencies, you can add an A/B test to your app. Let’s initialize a Sixpack client by creating a new Sixpack instance using the SixpackBuilder:

1
2
3
4
Sixpack sixpack = new SixpackBuilder()
        .setSixpackUrl("http://api.mycompany.com/sixpack")
        .setClientId(getCachedClientId())
        .build();

For reference, your getCachedClientId() method might look something like this:

1
2
3
4
5
6
7
8
9
public String getCachedClientId() {
    SharedPreferences prefs = context.getSharedPreferences("sixpack", Context.MODE_PRIVATE);
    String clientId = prefs.getString("sixpack_client_id");
    if (clientId == null) {
        clientId = Sixpack.generateRandomClientId();
        prefs.edit().put("sixpack_client_id", clientId).apply();
    }
    return clientId;
}

There are a two important things to take note of here: - You need to replace the url used in the setSixpackUrl() call with the url that points to your Sixpack-server deployment - The client id. The client id is the identifier used by the Sixpack-server to keep track of which clients have received which alternatives in the experiments they’re participating in. It is very important that the client id doesn’t change between sessions or else your users may see one alternative one time visiting the app and then another the next. That will probably have a fairly significant impact on your results too, so, just don’t do it. We recommend generating the client id once (there’s a helper method available, Sixpack.generateRandomClientId()) and caching that value in SharedPreferences so that it can be used again later (see the above code for an example of how that might work).

Note: we recommend maintaining a singleton instance of Sixpack using your favorite DI implementation, we use dagger for this

Alright, now you can create an experiment for testing the color of a button in your UI:

1
2
3
4
5
Experiment buttonColor = sixpack.experiment()
        .withName("Button Color")
        .withAlternative(new Alternative("Red"))
        .withAlternative(new Alternative("Green"))
        .build();

This will create a new experiment called “Button Color” with two alternatives, “Red” and “Green”. Once your client starts participating in this experiment, you’ll see it show up in the Sixpack-web dashboard. So let’s start it!

You start the test by calling Experiment#participate() and passing in the appropriate callbacks. Java 8 lambdas are used here for brevity.

1
2
3
4
5
6
7
8
9
10
buttonColor.participate(
        (participatingExperiment) -> {
            // success! save the participating instance for later so that we can convert it and set our button color
            this.participatingExperiment = participatingExperiment;
            button.setBackgroundColor(participatingExperiment.selectedAlternative == redAlternative ? R.color.button_red : R.color.button_green);
        },
        (experiment, error) -> {
            // failure, check network connection and try to participate again, you should also likely fallback to a default
        }
);

Now your test is live in the dashboard!

Finally, when the user clicks the button (assuming that’s the action that you’re measuring in this example) you can go ahead and fire the convert() message to Sixpack.

1
2
3
4
5
6
7
8
9
10
public void onClick(View button) {
   participatingExperiment.convert(
            (convertedExperiment) -> {
                // success!
            },
            (experiment, error) -> {
                // failure, check network connection and try to convert again
            }
    );
}

And that’s it, you’ve now successfully tested that button color!

Advanced usage

You might take a look at this API and say that there are several calls that do not need to be repeated more than once, and you’re not wrong! For instance, if you have a heavily trafficked part of your app under test, it will be to your advantage to call participate early in your application’s lifecycle and cache the ParticipatingExperiment in a way that will make fetching the selected alternative and calling convert() as easy as possible as to not hold up the rest of your UI from initializing. That said, participating early is risky and isn’t appropriate for most cases because you don’t want to participate in a test that your user never gets a chance to convert. Be careful and be sure to validate your tests are calling participate and convert at the right times before going into production by using a proxy or logging.

Here are a few “advanced usage” scenarios that you may want to consider for improving the sixpack-java integration in your app:

  1. You could expose your ParticipatingExperiments as RxJava Observables and .cache() the results
  2. In addition to having a singleton Sixpack instance in DI, you can put your Experiments and ParticipatingExperiments in your dagger modules so that they can have a lifecycle outside some of your application’s components
  3. Instead of generating a random client id, for your users that are registered and have ids within your own system, you can use their user uuid as the Sixpack client id and later use Sixpack-server’s API to join your A/B testing results with your application’s users!

Conclusion

We can’t wait to see the great things you’ll build with sixpack-java! We’ve been using sixpack-java internally, but it is beta software at the moment, so please integrate it into your apps and let us know if you have any issues with it here at our github.

Have fun building!

P.S. If you’re interested in helping us create the best mobile event ticketing experience on Android, we’re hiring.