Hi, this is Andy Rich from the C++ QA team. Previously, I showed you how you can use the C++ REST API to connect to Live services, but this is just one of many web services that you can use the REST API with. In this blog post, I will walk you through creating a Windows 8 Store App that will log a user into Facebook and download and display their photo albums.
Setting up a Facebook Developer Account
In order to develop apps for Facebook, you will need to have a real Facebook account, and sign up to be a developer on their platform, which you can accomplish by visiting http://developers.facebook.com.
Once you are enrolled as a developer, you will need to create an app identity by clicking “Apps” in the top bar and choosing “Create New App”. Give your app a name, but don’t worry about setting any of the other parameters for now. Click “Save Changes” and your app will be created.
You should also set your app to a “Desktop” app, which will reduce how often your users need to log in. To do this, select “Advanced” under your application settings, change App Type to “Native/Desktop” and set “App Secret in Client” to “No” and click the “Save Changes” button.
Finally, go your basic settings page and make note of your app’s App ID/API key (later referred to as
For the purposes of this post, we will be creating an abstraction to deal with communication with Facebook, a facebook_client class:
// Facebook.h
#pragma once
#include
#include
#include
class facebook_client {
public:
static facebook_client& instance(); // Singleton
pplx::task<void> login(std::wstring scopes);
pplx::task
web::http::uri_builder base_uri(bool absolute = false);
private:
facebook_client():
raw_client(L"https://graph.facebook.com/"),
signed_in(false) {}
pplx::task<void> full_login(std::wstring scopes);
std::wstring token_;
bool signed_in;
web::http::client::http_client raw_client;
};
Since we only need one instance of this class for our app, we are employing the singleton pattern and have made the constructor private. This ensures that the client can be accessed from anywhere, and that multiple instances of the client do not proliferate through our app.
The following sections will walk through implementation of this class. The code in the following sections assumes that your implementation file has these using declarations:
using namespace pplx;
using namespace web;
using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Security::Authentication::Web;
using namespace Windows::Storage;
The implementation of the instance() method uses a Meyer’s Singleton:
facebook_client& facebook_client::instance()
{
static facebook_client c;
return c;
}
Logging into Facebook
Like many popular services, Facebook uses OAuth to perform user logins. For Windows Store apps, developers should use the WebAuthenticationBroker to negotiate OAuth logins. This API presents a trustworthy dialog for your users to consent to connecting your application to the authorizing service which is consistent across all Windows Store apps. This dialog will guide your user through Facebook’s Desktop App Login.
The following code will present a dialog for the user to log into Facebook:
pplx::task<void> facebook_client::full_login(std::wstring scopes)
{
http::uri_builder login_uri(L"https://www.facebook.com/dialog/oauth");
login_uri.append_query(L"client_id", L"
login_uri.append_query(L"redirect_uri", L"https://www.facebook.com/connect/login_success.html");
login_uri.append_query(L"scope", scopes);
login_uri.append_query(L"display", L"popup");
login_uri.append_query(L"response_type", L"token");
return create_task(WebAuthenticationBroker::AuthenticateAsync(
WebAuthenticationOptions::None, ref new Uri(ref new String(login_uri.to_string().c_str())),
ref new Uri("https://www.facebook.com/connect/login_success.html")))
.then([=](WebAuthenticationResult^ result) {
if(result->ResponseStatus == WebAuthenticationStatus::Success)
{
signed_in = true;
std::wstring response(result->ResponseData->Data()); // Save authentication token
auto start = response.find(L"access_token=");
start += 13;
auto end = response.find('&');
token_ = response.substr(start, end-start);
auto ls = ApplicationData::Current->LocalSettings->CreateContainer("LoginDetailsCache",
ApplicationDataCreateDisposition::Always);
ls->Values->Insert("facebookToken", ref new String(token_.c_str()));
}
});
}
After the login is successful, the code extracts the access token from the redirection URI which was captured by the WebAuthenticationBroker and stores it for later use. Note that we are storing this token in a local variable within our facebook_client class, but we are not making an effort here to prevent concurrent access of this value.
Sadly, Facebook does not support the ms-app URI protocol, which is necessary to enable Single Sign On (SSO) using the WebAuthenticationBroker. This is why we are explicitly specifying the OAuth redirection URI as https://www.facebook.com/connect/login_success.html. Since we are unable to use SSO, our application will need to display the login dialog to the user every time they need to be logged in. In order to reduce how often the login dialog needs to be presented to our users, we are saving this login token in our App’s LocalSettings container.
Caching the Login Token
The tokens retrieved by the WebAuthenticationBroker will be valid for 60 days, so we will cache this token in our app’s LocalSettings. (If you find your tokens are only valid for 1-2 hours, you should verify that your registered app type is “Desktop” on Facebook’s developer portal as specified in “Setting up a Facebook Developer Account.”)
Facebook provides a specific API for checking to see if a token is valid. In order to make use of this API, we first need to provision an app token (in future code, “
https://graph.facebook.com/oauth/access_token?client_id=
Armed with this app token, we can implement facebook_client::login. This will first attempt to retrieve the cached login token (stored from a previous invocation), validate it, and if the token is expired, send the user through the full login API.
pplx::task<void> facebook_client::login(std::wstring scopes)
{
// Look in the application container for login credentials
auto ls = ApplicationData::Current->LocalSettings->CreateContainer("LoginDetailsCache",
ApplicationDataCreateDisposition::Always);
if(ls->Values->HasKey("facebookToken")) {
token_ = dynamic_cast<String^>(ls->Values->Lookup("facebookToken"))->Data();
}
if(!token_.empty()) {
// Check if the token is still valid
using namespace http;
uri_builder tokendbg_uri(L"/debug_token");
tokendbg_uri.append_query(L"input_token", token_);
tokendbg_uri.append_query(L"access_token", L"
http_request request(methods::GET);
request.set_request_uri(tokendbg_uri.to_string());
request.headers().add(header_names::accept, L"application/json");
return raw_client.request(request)
.then([](http_response response){
return response.extract_json();
}).then([=](json::value v) -> task<void> {
auto obj = v[L"data"];
if(obj[L"is_valid"].as_bool()) {
// Currently cached access token is valid
signed_in = true;
return create_task([](){}); // Return an empty task to match the function's return value
}
// If the token was invalid, go through full login
return full_login(scopes);
}, task_continuation_context::use_current());
}
// If no token was found, go through full login
return full_login(scopes);
}
The code above is creating the HTTP request manually (instead of using the URI-based overload of http_client::request). This is because we need to manually set the “accept” header to “application/json,” as Facebook’s response encoding is “text/javascript” by default, and the C++ REST API throws an exception when attempting to extract JSON from response encodings other than the standardized MIME type “application/json”.
Calling the Graph API
Every invocation of the Graph API will require us to set the accept header, as well as append the user’s key obtained during login. The method facebook_client::base_uri returns a uri_builder with the necessary user key added to the query (this is factored out for use later). The facebook_client::get method takes a URI fragment for the API being requested and returns the expected JSON.
http::uri_builder facebook_client::base_uri(bool absolute)
{
http::uri_builder ret;
if(absolute)
ret.set_path(L"https://graph.facebook.com");
ret.append_query(L"access_token", token_);
return ret;
}
task
{
using namespace http;
http_request request(methods::GET);
request.set_request_uri(base_uri().append_path(path).to_uri());
request.headers().add(header_names::accept, L"application/json");
return raw_client.request(request)
.then([](http_response response) {
return response.extract_json();
});
}
As elsewhere in this example, we are not handling any cases where the web calls may fail. In a real-world app, the response from the server may not indicate success. In this case, you should validate the HTTP response in the request continuation before attempting to extract or process the JSON. Your app may also experience a failure to connect or timeout (for example, if the user does not have internet connectivity), in which case the request continuation will not be called into. You should use task-based continuations to observe these exceptions and represent them to users appropriately.
Wiring up to your App
We can now exercise the facebook_client functionality by hooking it up to an actual app. The end goal is to display all of the user’s albums in a GridView. The GridView will be the main display portion of the application, and we will include two buttons, one for logging the user in and one for fetching the list of albums. (In a real app, these operations would likely be done automatically and include UI cues and progress indicators for the current state of login and downloading of the album metadata.) The XAML looks like this:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="150" />
Grid.RowDefinitions>
<GridView Grid.Row="0" x:Name="AlbumGrid" />
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Button x:Name="LoginButton" Click="LoginButton_Click_1">LoginButton>
<Button x:Name="AlbumButton" IsEnabled="False" Click="AlbumButton_Click_1">Fetch AlbumsButton>
StackPanel>
Grid>
Note that we are not defining the look of the individual album elements in the GridView yet; we will deal with this later. Also, the default state of the Album button is disabled – we will enable this button (and disable the login button) once the user is successfully logged in.
The code for the login button’s click handler should look like the following:
void MainPage::LoginButton_Click_1(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
LoginButton->IsEnabled = false; // Disable button to prevent double-login
facebook_client::instance().login(L"user_photos")
.then([=](){
AlbumButton->IsEnabled = true;
}, pplx::task_continuation_context::use_current());
}
We are passing the login scope “user_photos” which will allow the user to consent to our app accessing his Facebook photo albums. For a full list of login scopes and the permissions Facebook provides, see http://developers.facebook.com/docs/concepts/login/permissions-login-dialog/.
After login is successful, we enable the albums button. Because the login operation is not being started from a WinRT operation, PPL by default will not marshal it back to the calling thread. However, since we want to modify UI elements, we need to specify that the continuation context should match the current thread (we are in a UI event handler, so the current thread will be the UI thread).
Designing the Album UI
The major pieces of the album UI that we want to display to our users are the name of the album, the count of photos in the album, and the album’s cover photo. The easiest way to wire all this data up to our UI is by creating a view model for the UI to bind against, which will store the values we will retrieve from Facebook:
[Windows::UI::Xaml::Data::Bindable]
[Windows::Foundation::Metadata::WebHostHidden]
public ref class FacebookAlbum sealed
{
internal:
FacebookAlbum(std::wstring title, int count, std::wstring id, std::wstring photo_id):
title_(title), count_(count), id_(id), photo_id_(photo_id) {}
public:
property Platform::String^ Title {
Platform::String^ get();
}
property int Count {
int get();
}
property Windows::UI::Xaml::Media::ImageSource^ Preview {
Windows::UI::Xaml::Media::ImageSource^ get();
}
private:
std::wstring id_;
std::wstring title_;
std::wstring photo_id_;
int count_;
Windows::UI::Xaml::Media::ImageSource^ preview_;
};
The three properties will convert the stored data elements (which are native C++ types) into C++/CX compatible types for databinding:
String^ FacebookAlbum::Title::get()
{
return ref new String(title_.c_str());
}
int FacebookAlbum::Count::get()
{
return count_;
}
ImageSource^ FacebookAlbum::Preview::get()
{
if(preview_ == nullptr) {
auto preview_uri = facebook_client::instance().base_uri(true);
preview_uri.append_path(photo_id_);
preview_uri.append_path(L"/picture");
preview_ = ref new Imaging::BitmapImage(ref new Uri(StringReference(preview_uri.to_string().c_str())));
}
return preview_;
}
For the “preview” property, we are using the XAML UI type BitmapImage. We are creating this preview image just-in-time when the property getter is called – this prevents our app from unnecessarily creating objects and downloading images for albums which are never displayed on screen, providing for better app responsiveness. Note that we are using facebook_client::base_uri, as we need to append the user’s access token to the calculated URI for accessing the album preview photo.
Lastly, we need to set the item template of our GridView to bind to these elements and display them in a user-friendly way:
<GridView Grid.Row="0" x:Name="AlbumGrid">
<GridView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Left" Width="400" Height="250" Margin="5">
<Image Source="{Binding Preview}" Stretch="UniformToFill" />
<StackPanel VerticalAlignment="Bottom">
<TextBlock Text="{Binding Title}" FontSize="30" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Count}" FontSize="12" HorizontalAlignment="Right" VerticalAlignment="Top" />
StackPanel>
Grid>
DataTemplate>
GridView.ItemTemplate>
GridView>
Fetching the Albums
Now that we have the UI set up, we can implement the album button’s callback to read the user’s list of albums from Facebook and populate a collection of FacebookAlbum classes to bind the UI to. To understand the following code, you may find it helpful to look at the data returned by the album API in the Facebook Graph API explorer. The code for the album button callback should look like this:
void MainPage::AlbumButton_Click_1(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
using namespace pplx;
AlbumButton->IsEnabled = false;
facebook_client::instance().get(L"/me/albums")
.then([](web::json::value v){
std::vector<FacebookAlbum^> albums;
for(const auto& it : v[L"data"]){
auto& elem = it.second;
albums.push_back(ref new FacebookAlbum(
elem[L"name"].as_string(),
elem[L"count"].as_integer(),
elem[L"id"].as_string(),
elem[L"cover_photo"].as_string()
));
}
return task_from_result(std::move(albums));
}).then([=](std::vector<FacebookAlbum^> albums){
AlbumGrid->ItemsSource = ref new Vector<FacebookAlbum^>(std::move(albums));
}, task_continuation_context::use_current());
}
This function parses the JSON returned by Facebook and creates a vector of FacebookAlbum classes which we can bind to. In order to modify the UI to present this data to the user, we need to marshal back to the UI thread, but it is unnecessary to do most of the JSON parsing work on the UI thread. We want to run the minimum operations on the UI thread, to keep our UI as responsive as possible.
In order to accomplish this, we take the computed vector and pass it to a future continuation using task_from_result, and specify that the future continuation have the current (UI thread) task continuation context. We use the move constructor to eliminate unnecessary copies of the albums vector, which makes this continuation very inexpensive to call.
Get the Source
The source for the example application in this blog post is available in the Release\Samples directory of the C++ REST SDK source on Codeplex. Note: this example contains placeholders for the application ID and application token, you will need to replace these with ones that you obtain from Facebook.