Originally I heard about the idea of Travatar from Drew Meyers. A while ago he wrote about Gravatar for Travel which as the name implies is a very simple concept: instead of manually updating your current location on social media as you travel, it would be updated automatically for you. This would be particularly useful for people who travel frequently, such as travel bloggers for example.
At the time when we briefly discussed the idea, I found it quite peculiar, but it took me a while until I decided to implement a prototype for it. My main concern was that it would be challenging to find a good source of location data in the first place, app fatigue is a real thing and it would be tough to convince people to install yet another app which collects their location for no tangible benefit. <spoiler alert>: and this is exactly what I built.
Yet, as it appeared to be a very simple product to build, and I didn’t have any better idea on what to work on this weekend, I spent a couple of days building a prototype for it.
To figure out the key components of the product, we need to get down to the basic components. Here is what we need for a basic prototype:
- A way to collect user’s location
- A way for users to sign up
- A way to connect user’s social media accounts
- A public API which would return user’s location based on email
I think, figuring out how to collect user’s location is the most interesting problem to solve here, because the rest is pretty straightforward, after all I’ve built API’s with authentication multiple times before.
Getting user location
Ideally, we’d want to avoid installing any apps on user’s phone. For a user, adopting a new app these days is a dubious proposition, even considering that we are building a utility app here. What are the alternatives here? Here are a few which comes to mind:
- Collect location from social media (Twitter allows to assign location to tweets, Facebook has location-based checkins, Instagram allows you to tie a place to a photo as well)
- Use metadata from the photos taken by user’s phone (still requires an iOS app)
And of course, there are options to avoid building any mobile apps in the first place, but more on that later.
Creating an account
A user would need to create an account using his email as a unique identifier. Additionally it would be great to allow users to login/signup with 3rd-party authentication providers like Twitter and Facebook to speed up onboarding, and connect social media accounts which could use location updates all at once. Ideally, we’d want a backend framework which would provide user authentication via email and social networks, as well as facilitate linking multiple accounts across those providers.
Connecting social media accounts
This part is easy, most of social networks provide SDK for popular programming languages/frameworks. Again, the tricky part would be linking multiple accounts together.
A public API
The API would need to return a user’s location based on email. As simple a that. As this particular endpoint would be unauthenticated by design, we’d need to take good care about privacy. We don’t want to expose user’s exact location coordinates. At the same time, we probably do want to show user’s approximate location, including his current city and a country.
As I mentioned before, even though an iOS app is not a ideal source of location data, it would allow us to integrate location fetching, authentication and social media account managment in a single deliverable. Now that I think about it, collecting user location from social media checkins sounds like a better idea, and I would seriously consider building a web app instead, but the iOS is already build and would do the job for now.
When it comes to the backend, naturally my first instinct considering the tragic story of Parse would be to build a custom one, for example with Django + Postgres + Django REST Framework. I resisted that temptation, as I know that’s a slippery slope. Knowing myself, I know how easy it is to start obsessing with engineering details and lose track of the bigger picture, or even worse — end up not finishing the project. Besides, I’d need to spend some time building authentication, and even though solutions like Auth0 make life easier, I didn’t want to waste my time reinventing the wheel.
That’s why I chose using Firebase despite totally capable of building my own custom backend. If the product gets traction, you can always quickly build a new backend and seamlessly swap them preserving user data.
Making the logo
I’m not a designer, but I took a stab at making a logo myself. Here is a quick tutorial on how to approach logo design with a walkthrough using Sketch. I followed some of the advice there and using a couple of free icons from The Noun Project, I came up with this masterpiece combining icons for location and passport:
Building iOS app
I never worked with Firebase before, but as it appears to be an option for BaaS (Backend as service) for apps, I couldn’t help but compare it to Parse. My impression was that, even though it comes with a lot of features that are useful for most CRUD apps like authentication, persistence and analytics, the API is not as user-friendly and not as high-level as I’d want it to be. For example, the basic tasks like saving user data are confusing. Turned out that all the data is stored in a JSON format and authentication data like user’s email is stored separately from your custom fields. Concretely, if you want to store some data in Realtime Database, you’d need to do something like this in Swift:
Either way, using Firebase saved me some time with implementing authentication and linking multiple accounts together. On top of that, they even provide a drop in UI for iOS apps, which is pretty ugly (after all, it’s made by Google) and not very flexible when it comes to customization. With some little customization I’ve ended up with this:
Ideally, we want the user to sign up with Twitter right away. Signing in with email doesn’t make much sense in this context, because you want to have some social integrations enabled, otherwise you can only use your location data through a public API. Besides, signing up with Twitter is much faster.
When configuring the Twitter app, I also enabled additional permissions to collect user’s email (Permissions -> Additional Permissions -> Request email addresses from users). This is useful for linking accounts of users who signed up with Twitter, so that they can access the API by email.
When it comes to collecting user location, the first option that comes to mind is to use Significant Location Changes. From the Apple’s documentation and user experience using background location updates (you need to reauthorize it every few days with an annoying blocking popup), it’s clear than they want app developers to think twice before opting for it. On top of that if you read through their documentation, it’s not quite clear when and how those location updates occur:
Apps can expect a notification as soon as the device moves 500 meters or more from its previous notification. It should not expect notifications more frequently than once every five minutes.
From my personal experience testing significant location updates in the emulator, I received location update notifications every 0.98 km and about every 30 seconds.
For the purpose of our app, we don’t need this precision, nor do we need this frequency of updates. Yet, it’s the best option available for us if we want to use location manager from iOS. What we really need from the app is to send an updated location on a significant move, e.g. more than 100km.
So, if we decide to use CLLocationManager after all, we’d need to filter out location update notifications either on the app side, or on the backend. I’ve opted for the backend, because even though it would lead an increase of network requests to the backend, we’d have more flexibility to change the filtering algorithm, as we could do it instantly on the backend, rather than waiting for Apple to approve a new version of the app.
Another thing to consider is user privacy: by default, iOS provides an incredibly good location precision, up to a few meters of error. As the exact location of a user is not at all relevant for us, we need to randomize it before sending it to the backend:
I wanted to make requesting location access as unobtrusive as possible, and instead of presenting the usual location authorization popup right away, I made users explicitly opt-in for it. Thus, if you didn’t enable desired “Always on” background location updates, you’d see a large button on the top of the main screen:
If you originally signed up with email, you’ll be able to connect your Twitter here as well.
Finally, when I implemented the whole background location update system, it occurred to me that since I don’t care about location precision, I could’ve simply determined location by IP address. Duh!
Thus I implemented background fetch to obtain location by IP on top of that. So, even if the user doesn’t authorize location updates, we’d still get his location by IP in the background every day. NSA would be so proud!
A day after I submitted the app for App Store review I received a dreaded metadata rejection. This wasn’t my first safari, but I already started to second-guess myself: “This is not a real app”, “Why make such a trivial iOS app, this is useless”. I resisted the temptation and shipped it anyway by uploading new screenshots. Luckily, Apple review process has been rather fair in my experience. By the way, if you ever get metadata rejection for your apps don’t resubmit you binary or click “Submit for Review”, this would move your app to the back of the line. Intead, just reply with a comment in Resolution Center and your app should be approved very soon after.
Building the backend
On the backend, besides user credentials, we’d need to store Twitter authentication token and secret, so that we can update users’ Twitter profile location on their behalf. We’d also need to perform a background task to reverse-geocode user’s location and get information about their current city and country.
First, I’ve implemented an onWrite hook which would be triggered anytime the client pushes a new location. In this hook, I enabled request throttling: the hook is currently called every time the app receives a significant location update notification, which is way too often. We want to ignore location changes when the location has been changed for less than 100 km, for example. Now, an obvious corner case for this would be when someone travels continuously with consistent cellular data connection available. I’d consider this use-case negligible, as most of the trips involve a flight which would work just fine with this filter.
Then, on the backend, I’d need to figure out user’s city and country based on the coordinate. I used Google’s Geocoding API for that. In most cases you can get country and city from the API’s response like this:
From a brief test with multiple locations, this appeared to produce reasonable results like San Jose, US, United States. Although sometimes even though I used English locale (i.e. language=en), it would produce weird results for locality, like Moskva instead of Moscow. I just let it be for now considering it an easter egg feature, rather than a bug.
Additionally, as an icing on the cake, I’ve mapped countries to emoji flags 🏁.
Finally, once we reverse-geocoded our location, I used stored Twitter token to automatically update my Twitter location:
To summarize, here is how the stored user data looks like in Firebase:
This JSON is structured so that all the secret information like location and twitter tokens is separated from the information under the key public. This is done due to the specifics of access control implementation in Firebase. One would expect to be able to set general read permissions at the user’s root, and then provide more restrictive permissions for protected resources. Apparently, this is not possible with Firebase. Root permissions cannot be overridden in nested objects. That’s why I had to define my access permissions as follows:
This means that location and twitter fields are accessible only to the user they belong to, whereas everything at
public key is accessible to everyone to read.
Building the API
Unfortunately, Firebase Realtime database doesn’t allow you to use custom domains. Besides, I wanted to decouple the API interface from the current storage implementation using Firebase in case I want to switch to a custom backend later on.
So I created a simple Flask app and deployed it to Heroku. The API should provide its users, on top of a way to access user’s public profile by an obscure userId, a way to get user’s public profile by email.
For that purpose, I’d need to have admin access to the Realtime database again, which means… you guessed it! Another cloud function. Luckily Firebase’s Node.js SDK provides just that (
Now, in our Flask app, we need to make a REST call to this cloud function. Turns out all cloud functions are available at
https://us-central1-<firebase_app_id>.cloudfunctions.net. I’ve ended up making a network call to obtain Firebase’s user id, then making a call to Realtime Database to fetch user’s public profile. Potentially, I could’ve saved a network call and perform the database query from the cloud function as well.
In the end I came up with the following API format:
Building the landing page
All the code used by the app is available on Github:
- iOS app: https://github.com/Travatar/ios
- API: https://github.com/Travatar/api
- Cloud Functions/Website: https://github.com/Travatar/cloud
After building this quick prototype, I already see a lot of ways to develop this product further, such as:
- Enable drop-in integrations for Wordpress and other sites
- Try to eleminate the iOS app in favor of social media check-in hooks
- Web-app where users can login and manage their integrations/data
- /where page: a page for your personal website which simply shows where you are in the world. Inspired by Derek Sivers’ NOW page.