Apr 18, 2012
Apple has included integration with Twitter in iOS5, which can be really handy to allow your users to easily tweet or log in. The only problem is that, as far as documentation of this feature is concerned, you're largely on your own. This blog post is an attempt to correct that, at least in the case of sign-on.
Click on your project file, and then on your build target. Make sure you're on the "build phases" tab. Under the "Link Binary With Libraries" section, click the plus symbol to the bottom left, and search for Accounts.framework and add it. Then do the same for Twitter.framework. This will link all of the necessary libraries into your project so that we can use the Twitter integration.
The first thing to do when you want to let a user sign-on with Twitter is to create a long-lived (save it as an instance variable, for example) instance of ACAccountStore and request access to the Twitter accounts contained within:
ACAccountStore *store = [[ACAccountStore alloc] init]; // Long-lived ACAccountType *twitterType = [store accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]; [store requestAccessToAccountsWithType:twitterType withCompletionHandler:^(BOOL granted, NSError *error) { if(granted) { // Access has been granted, now we can access the accounts } // Handle any error state here as you wish }];
What do we do once we have access to the accounts? Well, we get a list of 'em. If they don't have any accounts, then we can show a dialog asking them to connect one in the iOS settings app:
// Remember that twitterType was instantiated above NSArray *twitterAccounts = [store accountsWithType:twitterType]; // If there are no accounts, we need to pop up an alert if(twitterAccounts != nil && [twitterAccounts count] == 0) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No Twitter Accounts" message:@"There are no Twitter accounts configured. You can add or create a Twitter account in Settings." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; [alert release]; } else { ACAccount *account = [twitterAccounts objectAtIndex:0]; // Do something with their Twitter account }
Well, now you have an access token to read some information about the user from their Twitter stream. The vast majority of apps will just want to grab the user's basic info to get things like a username, real name, and maybe their location. Here's how that would look:
NSURL *url = [NSURL URLWithString:@"http://api.twitter.com/1/account/verify_credentials.json"]; TWRequest *req = [[TWRequest alloc] initWithURL:url parameters:nil requestMethod:TWRequestMethodGET]; // Important: attach the user's Twitter ACAccount object to the request req.account = account; [req performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) { // If there was an error making the request, display a message to the user if(error != nil) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Twitter Error" message:@"There was an error talking to Twitter. Please try again later." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; [alert release]; return; } // Parse the JSON response NSError *jsonError = nil; id resp = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&jsonError]; // If there was an error decoding the JSON, display a message to the user if(jsonError != nil) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Twitter Error" message:@"Twitter is not acting properly right now. Please try again later." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; [alert release]; return; } NSString *screenName = [resp objectForKey:@"screen_name"]; NSString *fullName = [resp objectForKey:@"name"]; NSString *location = [resp objectForKey:@"location"]; // Make sure to perform our operation back on the main thread dispatch_async(dispatch_get_main_queue(), ^{ // Do something with the fetched data }); }];
Most of the code here is actually error handling code. The meat of what we're doing is simply fetching the user's credentials from Twitter, parsing the JSON, and doing something with it. (What exactly you want to do with the username and name data is left as an excercise for the reader.)
Yep, that's it. You should be able to implement Twitter sign-on for your app with the simple code I've shown here. The only bummer is the case when the user doesn't have a Twitter account registered. We're not allowed to help the user out by sending them to the Settings app. All we can do is tell users to go there and hope they can figure it out.
Any other tips or tricks you have for implementing sign-on with Twitter on iOS? Be sure to tweet me about it!
P.S. If you're building mobile apps, my startup clutch.io can help you build and iterate faster on them. Check us out :)
Jan 02, 2012
"This guy's code sucks!" It's something we've all said or thought when we run into code we don't like. Sometimes it's because it's buggy, sometimes it's because it conforms to a style we don't like, and sometimes it's because it just feels wrong. Recently I found myself thinking this, and automatically jumping to the conclusion that the developer who wrote it was a novice. The code had a distinct property that I dislike: lots of nesting. But the more I think about it, the more I realized that it's not really something I've heard discussed much.
So let's talk about it. I'm going to first talk about what I mean by nesting, why I think it's a bad quality, and then I'm going to go over some tricks I've learned over the years to reduce it.
It's easier to demonstrate rather than talk about it. This is what I mean by deep code nesting, with my apologies for the contrived example:
def get_cached_user(user_id=None, username=None): """ Returns a cached user object either by their user_id or by their username. """ user = cache.get_user_by_id(user_id) if not user: user = cache.get_user_by_username(username) if not user: user = db.get_user_by_id(user_id) if not user: user = db.get_user_by_username(username) if not user: raise ValueError('User not found') cache.set_user(user, id=user.id, username=user.username) return user
You can see in this Python code just by looking at the indentation level that there's lots of nesting. Before we can determine that the user was not found, we must pass through four conditionals, and each conditional is nested within the previous conditional.
I argue that this is bad code. Every added level of nesting is another piece of context that your brain has to keep track of. Each nested block is one you have to line up by eye to see what conditional it lines up with (even if your editor helps at this with visuals, it doesn't remove the issue entirely.) And this is just a straightforward example where we just return the user at the end, let's take a look at an example that does something more complicated:
def get_media_details(media): """ Returns a dictionary of extra data about the given media object. """ data = {} if media.is_video: data['kind'] = 'video' if media.is_youtube: data['url'] = 'http://youtube.com/' if media.is_vimeo: data['vimeo'] = True if media.vimeo_version == 2: data['url'] = 'http://vimeo.com/v2/' if 'url' in data: data['secure_url'] = data['url'].replace('http:', 'https:') elif media.is_audio: data['kind'] = 'audio' elif media.is_text: data['kind'] = 'text' if 'kind' in data: data['kind_verbose'] = { 'video': 'Video Stream', 'audio': 'Audio File', 'text': 'Text Content', }[data['kind']] return data
It was unbelievably hard for me to even write that last example. It's obviously contrived and such, but the point is that it's so difficult to even understand what it's doing. Unlike the previous example, this doesn't simply nest and then return; it nests and then un-nests, and then nests again, and then finally returns.
The best way that I've discovered to avoid nesting is to return early. Caching is the perfect example of this. Instead of testing for a cache failure and fetching from the database inside the conditional, check for cache success and return that early.
So this code:
def get_cached_user(user_id): user = cache.get_user_by_id(user_id) # The main logic all happens in this nested block if not user: user = db.get_user_by_id(user_id) cache.set_user_for_id(user_id, user) return user
Becomes this:
def get_cached_user(user_id): user = cache.get_user_by_id(user_id) if user: return user # The main logic happens outside of the nested block user = db.get_user_by_id(user_id) cache.set_user_for_id(user_id, user) return user
In the simple case, it doesn't seem to improve much, but what happens if we apply this technique to our first example? It's dramatically improved:
def get_cached_user(user_id, username): # First check the cache by id user = cache.get_user_by_id(user_id) if user: return user # Now check the cache by username user = cache.get_user_by_username(username) if user: return user # Both caches failed, so try hitting the db for the id user = db.get_user_by_id(user_id) if user: cache.set_user(user, id=user.id, username=user.username) return user # Looks like that didn't exist, try the username user = db.get_user_by_username(username) if not user: raise ValueError('User not found') # Cache our final user value for future use cache.set_user(user, id=user.id, username=user.username) return user
Not only does it make it easier to read top-to-bottom, and force us to keep track of way less context, and make our code editors do less line wrapping, but it also makes it easier to separate the blocks of code and more easily comment them.
So what other techniques can we use? It starts to depend more on the situation. Are you nesting because you're writing a bunch of callbacks? If so, you can usually restructure your code to use named functions instead of anonymous functions. Here's how that would might look before refactoring:
function getCachedUser(userId, callback) { cache.getUser(userId, function(user) { if(user) { return callback(user); } db.getUser(userId, function(user) { cache.setUser(userId, user, function() { callback(user); }); }); }); }
Note that in this example we even applied the technique of returning early in the first callback function, but as you can see there's still a bunch of nesting going on. Now if we switch to using named functions?
function curry(fn) { var slice = Array.prototype.slice; var args = slice.apply(arguments, [1]); return function () { return fn.apply(null, args.concat(slice.apply(arguments))); }; } function final(callback, user) { callback(user); } function dbResult(callback, userId, user) { cache.setUser(userId, user, curry(final, callback, user)); } function cacheResult(callback, userId, user) { if(user) { return callback(user); } db.getUser(userId, curry(dbResult, callback, userId)); } function getCachedUser(userId, callback) { cache.getUser(userId, curry(cacheResult, callback, userId)); }
This is a lot better in terms of nesting. Unfortunately we had to write a helper function called curry, but that only has to be written once and can be re-used for all code written in this style. Also unfortunately I still find this kind of code difficult to follow, which is why I avoid writing much callback-style code. However, at least you can reduce the nesting. In all honesty, there are probably better ways of reducing nesting that I'm not aware of. If you can rewrite the getCachedUser function in JS in a better way, please blog it!
Another way to reduce nesting is to assign an intermediate variable. Here's an example in Erlang of some file function that nests a case statement within another case statement.
do_some_file_thing(File) -> case file:open(File, [raw, binary, read]) of {ok, Fd} -> Start = now(), case process_file_data(Fd) of {ok, Processed} -> {ok, Start, now(), Processed}; Error -> Error end; Error -> Error end.
We can assign to an intermediate "Resp" variable, and bring that second case statement out into the function's main code block, like so:
do_some_file_thing(File) -> Resp = case file:open(File, [raw, binary, read]) of {ok, Fd} -> {timestamp, now(), process_file_data(Fd)}; Error -> Error end, case Resp of {timestamp, Start, {ok, Processed}} -> {ok, Start, now(), Processed}; {timestamp, Start, Error} -> Error; Error -> Error end.
At the end of the day, this isn't going to make or break you as a programmer. In fact, nothing I've mentioned even changes the code's logic, but simply its implementation. It's simply something to think about as you code, as you read other people's code. Hopefully you agree with me that less nesting is an admirable goal, and you find more and more ways to achieve it.
Discuss this post on Hacker News.
Feb 16, 2011
We launched Convore last week, and the first question developers tend to ask when they find Convore is "what technology powers this site?" It is asked so often, in fact, that we have started to copy and paste the same short response again and again. That response was good enough to satisfy people who simply wanted to know if we were Rails or Django, or whether we were using node.js for the real-time stuff, but this article will expand upon that-- not only giving more details for the curious, but also giving us a link to point people at when they ask the question in the future. I always wish other people were totally open about their architectures, so that I can learn from their good choices and their bad, so I'd like to be as open as possible about ours. Let's dive in!
All of our application code is powered by Python. Our front-end html page generation is done by Django, which we use in a surprisingly traditional way given the real-time nature of Convore as a product. Everything is assembled at once: all messages, the sidebar, and the header are all rendered on the server instead of being pulled in after-the-fact with JavaScript. All of the important data is canonically stored in PostgreSQL, including messages, topics, groups, unread counts, and user profiles. Search functionality is provided by Solr, which is interfaced into our application by way of the handy Haystack Django application.
When a new message comes into the system, first it's parsed by a series of regular expressions designed to pull out interesting bits of information from the message. Right now all we're looking for is username references and links (and further, whether those links point at images which should be rendered in-line.) At the end of this parsing stage, we have a structured message parse list, which is converted into JSON.
So, for example if someone posted the message:
@ericflo @simonw Here's how we connect/disconnect from Redis in production: http://dpaste.com/406797/
The resulting JSON parse list would look like this:
[
{
"type": "username",
"user_id": 1,
"username": "ericflo",
"markup": "<a href=\"/users/ericflo/\">@ericflo</a>"
},
{
"type": "username",
"user_id": 56,
"username": "simonw",
"markup": " <a href=\"/users/simonw/\">@simonw</a>"
},
{
"type": "text",
"markup": " Here's how we connect/disconnect from Redis in production: "
},
{
"type": "url",
"url": "http://dpaste.com/406797/",
"markup": "<a href=\"http://dpaste.com/406797/\" target=\"_blank\">http://dpaste.com/406797/</a>"
}
]
After this is constructed, we log all our available information about this message, and then save to the database-- both the raw message as it was received, and the JSON-encoded parsed node list.
Now a task is sent to Celery (by way of Redis) notifying it that this new message has been received. This Celery task now increments the unread count for everyone who has access to the topic that the message was posted in, and then it publishes to a Redis pub/sub for the group that the message was posted to. Finally, the task scans through the message, looking for any users that were mentioned in the message, and writes entries to the database for every mention.
On the other end of that pub/sub are the many open http requests that our users have initiated, which are waiting for any new messages or information. Those all simultaneously return the new message information, at which point they reconnect again, waiting for the next message to arrive.
Our live updates endpoint is actually a very simple and lightweight pure-WSGI Python application, hosted using Eventlet. It spawns off a coroutine for each request, and in that coroutine, it looks up all the groups that a user is a member of, and then opens a connection to Redis subscribing to all of those channels. Each of these Eventlet-hosted Python applications has the ability to host hundreds-to-thousands of open connections, and we run several instances on each of our front-end machines. It has a few more responsibilities, like marking a topic as read before it returns a response, but the most important thing is to be a bridge between the user and Redis pub/sub.
There are so many places where our architecture can be improved. This is our first version, and now that real users are using the system, already some of our initial assumptions are being challenged. For instance, we thought that pub/sub to a channel per group would be enough, but what that means is that everyone in a group sees the exact same events as everyone else in that group.
This means we don't have the ability to customize each user's experience based on their preferences--no way to put a user on ignore, filter certain messages, etc. It also means that we aren't able to sync up a user's experience across tabs or browsers, since we don't really want to broadcast to everyone in the group that one user has visited a topic, thereby removing any unread messages in that topic. So going forward we're going to have to break up that per-group pub/sub into per-user pub/sub.
Another area that could be improved is our unread counts. Right now they're stored as rows in our PostgreSQL database, which makes it extremely easy to batch update them and do aggregate queries on them, but the number of these rows is increasing rapidly, and without some kind of sharding scheme, it will at some point become more difficult to work with such a large amount of rows. My feeling is that this will eventually need to be moved into a non-relational data store, and we'll need to write a service layer in front of it to deal with pre-aggregating and distributing updates, but nothing is set in stone just yet.
Finally, Python may not be the best language for this real-time endpoint. Eventlet is a fantastic Python library and it allowed us to build something extremely fast that has scaled to several thousand concurrent connections without breaking a sweat on launch day, but it has its limits. There is a large body of work out there on handling a large number of open connections, using Java's NIO framework, Erlang's mochiweb, or node.js.
We're pretty proud of what we've built in a very short time, and we're glad it has held up as well as it has on our launch day and afterwards. We're excited about the problems we're now being faced with, both scaling the technology, and scaling the product. I hope this article has quenched any curiosity out there about how Convore works. If there are any questions, feel free to join Convore and ask away!
(Or discuss it on Hacker News)
Oct 10, 2010
In the moments leading up to my DjangoCon keynote this year (called Why Django Sucks, and How We Can Fix It), I pictured the room an hour in the future and imagined what things would be like. I envisioned all kinds of scenarios: one where people were literally booing, or another where I needed to sneak back to my hotel room to avoid embarassment. I imagined all of these scenarios because I was about to level some harsh criticism at a technology that everyone in the room was enthusiastic about.
And then I went on stage and gave the talk.
When it was finished, I braced myself for one of these scenarios to unfold. Instead, what happened in the following moments, the following days, and indeed the following month since then has been what I consider to be the most gracious and useful response to criticism that I've ever seen. And I think it deserves to be highlighted, because I believe that reacting well to criticism is vital for any successful community.
So what happened in the moments directly following the talk? Russell Keith-Magee, arguably the most active core developer at the time, made a special point to march up to the front of the stage and publicly shake my hand. This gesture not only legitimized some of the things that I said (some of which were quite extreme), but it also set the tone for the discourse around my talk; civility.
Afterwards, privately, I apologized to several of the Django core developers. These are people I respect and admire, and I wanted to make sure that they knew that, at least on my end, it wasn't personal. I was expecting forgiveness, but instead I received thanks. Thanking me for criticising their project? Not what I was expecting.
One of the things that I suggested was to make Alex Gaynor a core developer. A few people at the conference made comments wondering how many days it would take before that would happen. Days went by, and the conference ended without this happening. A week went by. A few more. But I knew better, because the Django core committers don't do anything brashly. They took their time, let all dust and emotions settle down, and listened to the discussion.
And then Jacob Kaplan-Moss issued an official announcement. I particularly agreed with one of the top comments on hackernews:
"I don't know enough about the specifics of the case to assess the new policy on the merits, but I will say that as a piece of writing this is a model of how to change policy gracefully: clear, forthright, and non-defensive."
Clear. Forthright. Non-defensive.
Some people saw this as an opening to change even more about Django, and even when some of the discussion was phrased rudely or accusatory, the Django team's responses were thoughtful and calm. Now, over a week later (again demonstrating that things aren't done brashly), we see that a half-dozen new people have been added to the core team. Yes, that includes Alex Gaynor. So not only was the new policy clear, forthright, and non-defensive, but there was concrete follow-through so that everyone can see that it wasn't just words.
And even in that short time since the new committers were added, Django's code is already reaping the benefits--we've seen a flurry of bugs fixed and tickets closed. Django's future has never looked so promising!
Why do I think this is so important? Because a community which welcomes constructive criticism is one in which people feel they can make a difference. And it's because they can make a difference. It's a community that's never satisfied with the status quo. It's a community that can grow and change and adapt, when the world around it changes. It's a community that we can be proud of.
NB: I was not the only one to criticize Django at DjangoCon. I was merely one voice. This is just written from my perspective.
Sep 27, 2010
Node.js is currently at the center of a huge cyclone of hype. At this point it's clear that Node is going to be a major player in the next few years of web development. It's no wonder, either! It represents a fresh start, one with no legacy synchronous baggage in our increasingly asynchronous, increasingly real-time web. And it's accessible to anyone who's written JavaScript (read: all web developers.)
Oh, and those benchmarks! Compared trivially to the currently popular web technologies, it seems an obvious leap forward.
Yet one of Node's advantages that you'll hear repeated again and again by its proponents is that you can now code in One Language, and you won't have to deal with the cognitive load of context switching between different languages. Especially as ORMs and NoSQL continue to rise in popularity, there's no need to even deal with SQL. At the end of the day you're writing JavaScript, HTML, and CSS, and that's it.
Seeing this happen with all of these pieces falling into place--a fresh start with a unified language--I started to get excited. This would change the way we thought about our web code. The frontend is the backend is the query language is the storage layer is JavaScript! It's a revolution.
And then I saw what people did with this opportunity. They effectively ported Sinatra/Django/Rails to JavaScript--and did it in such a way that it would only run on the server, with a specific feature set of JavaScript that only Node can reasonably understand.
Not exactly the revolution I was hoping for.
Instead of coding in one language, we're actually coding in two. One is the subset JavaScript that can be run in all browsers, and another is the set of JavaScript that can be run by Node. Knowing the difference between the two languages and context switching between them is simply a required skill.
You know what would be awesome? If we wrote our libraries so that they could run either on the server or on the client, and they did so in a transparent way. Maybe it would help to give a concrete example of how this could be awesome. Let's talk about HTML templating.
Imagine a framework where the first page-load was always rendered server-side, meaning the client gets a single fully-rendered page. Then for desktop browsers, browsing around the site just made calls to API endpoints returning JSON or XML, and the client rendered the templates for the changed portions of the page. For mobile browsers with less power or for search engines, the rendering would always be done on the server. Imagine that the templating library could record some key metrics to determine how long things were taking to render, and dynamically switch between rendering on the server and client based on server load or client speed.
Imagine a case where a back-end service fails temporarily. In this case the rendering of that particular component could be deferred, the browser could be told to poll a resource. When the back-end service is recovered, it could send the data for the client to render on its own.
How awesome would that be?
It's not just HTML templating, either. This same principle could be applied to any number of things: URL routing, form validation, hell even most application logic could be done using this style.
But it's going to take discipline. Instead of reaching for those fancy V8 features, code will need to be written in a strict subset of the JavaScript that's available. Maybe Node could detect incompatible code and throw warnings, that would be cool.
I just really hope that someday we stop re-inventing the same exact wheel, and instead build something substantially different and better.