iPhone Facebook Integration on Rails
One the attractive features of the iPhone is the seamless integration of many essential daily functions in a cool, shiny package: your photos, your music, your email, your games and your social network – all in the palm of your hand. From the start, we wanted to take advantage of social networks in our applications and launched with two essentials: email and Facebook. Some have said Facebook is the new email, but from a user experience the difference is the social graph of Facebook is built-in and we wanted to leverage this. For us, this meant using your Facebook photo in our app as well as listing the high-scores of your Friends. This means both iPhone app and Rails server app integration with Facebook.
The user experience on the iPhone app needed to be seamless. You can simply click on this “Connect with Facebook” button in the settings screen and log into Facebook to grab your photo:
Also, when showing the top scores of your Facebook friends we wanted this to be seamless:
First, the system diagram. We need to use the iPhone app to register with Facebook Connect API to grab the user Facebook ID (fb_uid). With this, we can grab the Facebook user photo within the iPhone app. But to check the high scores of your Facebook friends, we needed to run a query on the Rails server to see which of your friends are also registered users of Play-N-Give. To do this, we needed to enable Facebook Connect on Rails. This allows us to use the same XML based global leader board view mechanism described earlier – and should offer a better user experience given the slow response time of the Facebook API and the asynchronous XML parsing support in the iPhone app.
We need to register our app with Facebook to get the Facebook Connect API keys, and then we can make a request for the users Facebook photo by implementing the
1.// FBSessionDelegate
2.
3.- (void)session:(FBSession*)session didLogin:(FBUID)uid {
4. //! Facebook session didLogin delegate, handle the login
5.
6. fb_uid = [[NSString stringWithFormat:@"%lld", session.uid] retain];
7.
8. NSString* fql = [NSString stringWithFormat:
9. @"select uid,name,pic_square_with_logo from user where uid == %lld"
10. , session.uid];
11.
12. NSDictionary* params = [NSDictionary dictionaryWithObject:fql forKey:@"query"];
13. [[FBRequest requestWithDelegate:self] call:@"facebook.fql.query" params:params];
14.}
Line #3 is the FBSession delegate which is called after the user successfully logs into Facebook Connect. We make an SQL query to the Facebook Connect API in line #8 to obtain the users photo. Later, we parse the results of the query in a
1.- (void)request:(FBRequest*)request didLoad:(id)result {
2. //! Facebook session request didLoad delegate, parse the response
3. NSArray* users = result;
4. NSDictionary* user = [users objectAtIndex:0];
5. NSString* pic = [user objectForKey:@"pic_square_with_logo"];
6. NSData *receivedData = [NSData dataWithContentsOfURL:[NSURL URLWithString:pic]];
7. image = [[UIImage alloc] initWithData:receivedData]; // Do something with the image
8. [_session logout];
9.}
When we’re ready to view the top scores of our Facebook friends we implement an
1.- (void)session:(FBSession*)session didLogin:(FBUID)uid {
2. //! Facebook session didLogin delegate, handle the login
3. stringWithFormat:@"%@/Facebook/%@.xml?fbsession=%@",feedURLString,customerID,session.sessionKey];
4. [NSThread detachNewThreadSelector:@selector(getCustomerFB:) toTarget:self withObject:url];
5.}
Note that in line #26 we pass the Facebook sessionkey to the Rails server in order to authenticate the server side query.
This triggers a controller on the Rails server to poll the Facebook Connect API and generate a list of Friends, their photos and their scores. The resulting data is rendered in XML and parsed by the asynchronous XMLparser on the iPhone:
1.def fcbk(method, params={})
2. params.merge! :method => "facebook.#{method}", :v => "1.0", :call_id => Time.now.to_f.to_s, :api_key => API_KEY
3. params[:sig] = Digest::MD5.hexdigest(params.inject([]) { |args, pair| args << pair.join('=') }.sort.join + API_SECRET)
4. result,data = Net::HTTP.post_form(URI.parse('http://api.facebook.com/restserver.php'), params)
5. return data
6.end
7.
8. ##--- Method to display top 50 Facebook friend's scores
9. # GET /Facebook/1.xml
10. def Facebook
11. @scores = []
12. if !Customer.find(params[:id]).fb_uid.nil?
13. if !params[:fbsession].nil?
14. data = fcbk("fql.query", {:session_key => params[:fbsession], :query => "select uid2 from friend where uid1 = #{Customer.find(params[:id]).fb_uid}"})
15. doc = REXML::Document.new( data )
16. root = doc.root
17. if root.elements['error_code'].nil?
18. root.elements.each('friend_info/uid2') do |element|
19. data = fcbk("fql.query", :query => "select name,pic_square_with_logo,is_app_user from user where uid = #{element.text}")
20. doc = REXML::Document.new( data )
21. root = doc.root
22. cust = Customer.find(:first, :conditions => { :fb_uid => element.text })
23. @top50 = [{:name => root.elements['user/name'].text, :image => root.elements['user/pic_square_with_logo'].text, :points => totalPoints}]
24. @scores = @scores + @top50
25. end
26. else
27. @scores = [{:error_code => 1, :error => 'No uid, customer has not registered Facebook account'}]
28. end
29. respond_to do |format|
30. format.html # index.html.erb
31. format.xml { render :xml => @scores.to_xml(:root=>'customers', :child => 'customer') }
32. end
33. end
The tricky part is incorporating the Facebook photos of the users friends in the top scores list displayed on the iPhone rather than just listing names. The call to the Facebook API on line #32 only returns the URL of the Facebook photo, so we actually need to make an asynchronous call to load the image on the iPhone. We could have used the Rails server to send back a uuencoded image but it was actually easier to use the iPhone SDK [NSData dataWithContentsOfURL:] method!
We also need to ensure each user registers their Facebook ID (fb_uid) with Play-N-Give so that we can do the necessary lookup. We enforce this loosely by suggesting that if the user wants to use their Facebook photo they need to register with our server. Again, our Privacy Policy is clearly posted.
So that’s how we enabled Facebook integration within our iPhone app using Rails.
--yarri