Cross Platform C#
Using OAuth, Twitter and Async To Display Data
Connect to Twitter from a mobile application using OAuth with the Xamarin.Auth library, along with Joe Mayo's LINQ To Twitter library.
Authentication has been a part of .NET for a long time. Unfortunately, there are a couple of problems:
- Users must remember their user ids and passwords. For technologists, this is not a problem. For my parents, this is a huge challenge. Many people are able to remember their Twitter or other social network passwords. Unfortunately, remembering their user IDs and password for your service is most likely a problem.
- When an application is responsible for the user IDs and passwords, it must store that information securely in some way. If there's one thing we should all understand, no application is 100 percent secure. There is always the chance for some type of data loss.
Depending on the application's needs, it may make sense to let another service store user IDs and passwords. The next question is, "Is there a standard way to interact with other services that can handle authentication?" Thankfully, there is a standard for cross-application authentication: OAuth. OAuth has been implemented by a number of social networks and other services, including Twitter, Facebook, Google and others.
OAuth is an open standard for authorization. It provides the following:
- A standard method for client authentication.
- A standard process for end users to authorize third-party access to server resources.
- The ability to authorize these third parties without the sharing of credentials (username and password pair).
This article will not get into OAuth specifics; that information is available on the OAuth Web site.
For these set of examples, we'll use OAuth as a mechanism to authenticate users. We'll authenticate users, get a response and display information to the user (specifically, a profile image). From there, the example will integrate with Twitter via OAuth and then make calls to Twitter via a third-party library. This will demonstrate the strength of the growing Xamarin ecosystem.
Xamarin.Auth
Xamarin.Auth is a component library provided by Xamarin. It helps an application authenticate users via the OAuth standard authentication mechanism. In addition, Xamarin.Auth provides a secure storage mechanism. As of this writing (late February 2014), Xamarin.Auth is available for iOS and Android; there is a Windows Phone implementation that hasn't undergone extensive testing as of yet .
Xamarin.Auth has a cross-platform API, with only minor platform-specific differences between the iOS and Android implementations. Listing 1 makes a call to Twitter for authentication. The key steps:
- An instance of the OAuth Version 1 authenticator is instantiated. The constructor takes the application's token and secret values. (Note: these values are for an application created for this example. You'll need to go to the Twitter site, register a new value for this to work in the download code, and insert that value into the supplied code.)
- When the auth process is completed, the .Completed event is called. In this example, some setup is performed. One thing to notice is that the user's connection information is saved into the Xamarin.Aith's AccountStore. This is a secure storage closet for the connection information.
- A reference to the authentication UI must be obtained. The call to auth.GetUI(this) will return an activity that will be used.
- Finally, an activity is presented to the user.
Listing 1: Connecting to Twitter in Android.
var auth = new OAuth1Authenticator(
"DynVhdIjJDZSdQMb8KSLTA",
"REvU5dCUQI4MvjV6aWwUWVUqwObu3tvePIdQVBhNo",
new Uri("https://api.twitter.com/oauth/request_token"),
new Uri("https://api.twitter.com/oauth/authorize"),
new Uri("https://api.twitter.com/oauth/access_token"),
new Uri("http://twitter.com"));
//save the account data in the authorization completed even handler
auth.Completed += (sender, eventArgs) =>
{
if (eventArgs.IsAuthenticated)
{
loggedInAccount = eventArgs.Account;
AccountStore.Create(this).Save(loggedInAccount, "Twitter");
GetTwitterData();
btnSearch.Enabled = true;
btnTimeline.Enabled = true;
btnPost.Enabled = true;
}
};
var ui = auth.GetUI(this);
StartActivityForResult(ui, -1);
The result is shown in Figure 1.
The iPhone code, shown in Listing 2, is very similar.
Listing 2: Connecting to Twitter in iOS. Note the similarities to the Android version that was shown in Listing 1.
var auth = new OAuth1Authenticator(
"DynVhdIjJDZSdQMb8KSLTA",
"REvU5dCUQI4MvjV6aWwUWVUqwObu3tvePIdQVBhNo",
new Uri("https://api.twitter.com/oauth/request_token"),
new Uri("https://api.twitter.com/oauth/authorize"),
new Uri("https://api.twitter.com/oauth/access_token"),
new Uri("http://twitter.com"));
//save the account data in the authorization completed even handler
auth.Completed += async (s, eventArgs) =>
{
if (eventArgs.IsAuthenticated)
{
btnPost.Enabled = true;
btnSearch.Enabled = true;
btnTimeLine.Enabled = true;
loggedInAccount = eventArgs.Account;
//save the account data for a later session, according to Twitter docs, this doesn't expire
AccountStore.Create().Save(loggedInAccount, "Twitter");
GetTwitterData();
DismissViewController(true, null);
}
};
var ui = auth.GetUI();
PresentViewController(ui, true, null);
The output is shown in Figure 2.
Now that the connection's been made, the application can retrieve information from the user. This is done with the code in Listing 3. The sequence is fairly simple: The application instantiates an instance of the OAuth V1 Request object. Then an async request is made, the data is returned in a JSON format, the JSON.NET library is used to pull the URL for the profile image, and finally the image is displayed to the user.
Listing 3:Getting information about the logged in user in Twitter.
async public void GetTwitterData()
{
//use the account object and make the desired API call
var request = new OAuth1Request(
"GET",
new Uri("https://api.twitter.com/1.1/account/verify_credentials.json "),
null,
loggedInAccount);
await request.GetResponseAsync().ContinueWith(t =>
{
var res = t.Result;
var resString = res.GetResponseText();
Console.WriteLine("Result Text: " + resString);
var jo = Newtonsoft.Json.Linq.JObject.Parse(resString);
var imageUrl = new Java.Net.URL((string)jo["profile_image_url"]);
// get the image which is typically platform specific.
});
}
Figure 3 shows an Android screen with the user's profile image displayed.
Figure 4 shows an iPhone screen with the user's profile image displayed. This is analogous to Figure 3.
Within the returned JSON, the object has the user's ID. This is a primary key that can be used to identify the authenticated user. This can now be used to tie data in a developer's application to the actual Twitter-authenticated user.
More Twitter Options
Twitter supports many complex operations. Unfortunately, they tend to be fairly complex calls via the REST API. This is fine; but it becomes fairly complicated, given all of the potential parameters. Thankfully, there's a solution: Joe Mayo has built an open source library named Linq To Twitter (L2T) that easily allows calls to be made to the Twitter API. There's a portable class library (PCL) version that runs on iOS and Android; the rest of this article will use that version.
(Note: The setup of L2T has a problematic step that must be mentioned. First, you can get L2T from NuGet, as well as download it from CodePlex. Assuming you install from NuGet, there are several additional libraries added in, and some of them conflict. These namespaces at the time of this writing are System.Runtime, System.IO, and System.Threading.Tasks. Removing these references will allow the project to compile properly.)
Searching
One of the first questions that comes up in Twitter is, "What are users saying about a product?" This can be tracked via a Linq query. It's amazingly simple to code, and is shown in Listings 4 and 5, for Android and iPhone respectively. The steps to perform the search:
- Obtain the necessary OAuth information. This is obtained from the OAuth account information previously used. While L2T has an authentication feature, this shows that libraries can work well together, due to their reliance on standards. In this situation, the code queries the account store and retrieves the information.
- The application display class needs to be set up. In Android, this is done by inheriting from the ListActivity and using an Adapter. In iOS, by inheriting from a UITableViewController.
- The data is brought back in a query that is the same in both Android and iOS.
- The data is bound to the ListActivity in Android, and the UITableView in iOS. For each binding, a standard display is used. In Android, this is the Android.Layout.Resource.ActivityLayoutItem. In iOS, this is done via the UITableViewCellStyle.Subtitle Style.
- When going off device, all requests should be done asynchronously. C# 5 and L2T provide the ability to go perform asynchronous operations. Not only is this an issue with retrieving text, but also when retrieving images. When retrieving images, always do your best to perform asynchronous operations to retrieve data.
Listing 4 is the code for an Android search.
Listing 4: Performing a search and display in Android.
[Activity(Label = "Search Twitter")]
public class SearchTwitter : ListActivity
{
private string SearchTerm;
Xamarin.Auth.Account loggedInAccount;
List<LinqToTwitter.Search> _searches;
async protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
//has the user already authenticated from a previous session? see AccountStore.Create().Save() later
IEnumerable<Xamarin.Auth.Account> accounts = Xamarin.Auth.AccountStore.Create(this).FindAccountsForService("Twitter");
SearchTerm = "Xamarin.Android";
//check the account store for a valid account marked as "Twitter" and then hold on to it for future requests
foreach (Xamarin.Auth.Account account in accounts)
{
loggedInAccount = account;
break;
}
var cred = new LinqToTwitter.InMemoryCredentialStore();
cred.ConsumerKey = loggedInAccount.Properties["oauth_consumer_key"];
cred.ConsumerSecret = loggedInAccount.Properties["oauth_consumer_secret"];
cred.OAuthToken = loggedInAccount.Properties["oauth_token"];
cred.OAuthTokenSecret = loggedInAccount.Properties["oauth_token_secret"];
var auth0 = new LinqToTwitter.PinAuthorizer()
{
CredentialStore = cred,
};
var TwitterCtx = new LinqToTwitter.TwitterContext(auth0);
Console.WriteLine(TwitterCtx.User);
_searches = await (from tweet in TwitterCtx.Search
where (tweet.Type == SearchType.Search) &&
(tweet.Query == SearchTerm)
select tweet).ToListAsync();
this.ListAdapter = new SearchAdapter(this, _searches[0].Statuses);
}
}
public class SearchAdapter : BaseAdapter<LinqToTwitter.Status>
{
List<LinqToTwitter.Status> _items;
Activity _context;
public SearchAdapter(Activity context,
List<LinqToTwitter.Status> items)
: base()
{
this._context = context;
this._items = items;
}
public override long GetItemId(int position)
{
return position;
}
public override LinqToTwitter.Status this[int position]
{
get { return _items[position]; }
}
public override Java.Lang.Object GetItem(int position)
{
return _items[position].Text;
}
public override int Count
{
get { return _items.Count; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView; // re-use an existing view, if one is available
if (view == null) // otherwise create a new one
view = _context.LayoutInflater.Inflate(Android.Resource.Layout.ActivityListItem, null);
view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = _items[position].Text;
var imageUrl = new Java.Net.URL(_items[position].User.ProfileImageUrl);
Task.Factory.StartNew(() =>
{
System.IO.Stream stream = imageUrl.OpenStream();
var bitmap = Android.Graphics.BitmapFactory.DecodeStream(stream);
(view.FindViewById<ImageView>(Android.Resource.Id.Icon)).SetImageBitmap(bitmap);
});
return view;
}
}
Code output is shown in Figure 5, using the Android Genymotion Emulator.
Listing 5 shows the code for an iOS search.
Listing 5: The Twitter Search code in iOS. The data is displayed in a UITableView.
public class SearchTwitterController : UITableViewController
{
public string SearchTerm;
List<LinqToTwitter.Search> _searches;
Xamarin.Auth.Account loggedInAccount;
LinqToTwitter.PinAuthorizer auth0;
LinqToTwitter.TwitterContext TwitterCtx;
public SearchTwitterController()
{
_searches = new List<Search>();
_searches.Add(new Search());
_searches[0].Statuses = new List<LinqToTwitter.Status>();
}
public override void DidReceiveMemoryWarning()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
async public override void ViewDidLoad()
{
base.ViewDidLoad();
//has the user already authenticated from a previous session? see AccountStore.Create().Save() later
IEnumerable<Xamarin.Auth.Account> accounts = AccountStore.Create().FindAccountsForService("Twitter");
//check the account store for a valid account marked as "Twitter" and then hold on to it for future requests
foreach (Xamarin.Auth.Account account in accounts)
{
loggedInAccount = account;
break;
}
var twitterAuth = new LinqToTwitter.XAuthAuthorizer();
var cred = new LinqToTwitter.InMemoryCredentialStore();
cred.ConsumerKey = loggedInAccount.Properties["oauth_consumer_key"];
cred.ConsumerSecret = loggedInAccount.Properties["oauth_consumer_secret"];
cred.OAuthToken = loggedInAccount.Properties["oauth_token"];
cred.OAuthTokenSecret = loggedInAccount.Properties["oauth_token_secret"];
auth0 = new LinqToTwitter.PinAuthorizer()
{
CredentialStore = cred,
};
TwitterCtx = new LinqToTwitter.TwitterContext(auth0);
await SearchAndBind();
}
private async System.Threading.Tasks.Task SearchAndBind()
{
BigTed.BTProgressHUD.Show("Searching Twitter");
Console.WriteLine(TwitterCtx.User);
_searches = await (from tweet in TwitterCtx.Search
where (tweet.Type == SearchType.Search) &&
(tweet.Query == SearchTerm)
select tweet).ToListAsync();
this.TableView.ReloadData();
BigTed.BTProgressHUD.Dismiss();
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = TableView.DequeueReusableCell("cell");
if (cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Subtitle, "cell");
}
if ((_searches.Count != 0) && (_searches[0].Statuses.Count > 0))
{
var task = System.Threading.Tasks.Task.Factory.StartNew(() =>
{
InvokeOnMainThread(delegate
{
tableView.BeginUpdates();
cell.ImageView.Image =
LoadImage(_searches[0].Statuses[indexPath.Row].User.ProfileImageUrl);
tableView.EndUpdates();
});
});
cell.TextLabel.Text = _searches[0].Statuses[indexPath.Row].Text;
cell.DetailTextLabel.Text = _searches[0].Statuses[indexPath.Row].User.Name;
}
return cell;
}
public UIImage LoadImage(string imageUrl)
{
UIImage img = new UIImage();
try
{
var nsu = new NSUrl(imageUrl);
var nsd = NSData.FromUrl(nsu);
img = UIImage.LoadFromData(nsd);
}
catch (System.Exception exc)
{
Console.WriteLine(exc.Message);
}
return img;
}
public override int NumberOfSections(UITableView tableView)
{
return 1;
}
public override int RowsInSection(UITableView tableview, int section)
{
var i = 0;
return _searches[0].Statuses.Count;
}
}
The Twitter search code from the iPhone listing is shown in the Figure 6.
Timeline
Presenting a user's Twitter information is similar to performing a search, with two differences:
- The L2T query is different. There are several subtle differences, but the biggest one is the tweet type.
- The data format returned is different. It's a timeline with a list of status objects.
The code in Listing 6 gets the logged-in user's timeline. It's the same in both Android and iOS. All other pieces of code are the same; they're not shown, but are in the code download accompanying this article.
Listing 6: The code for getting a user's timeline.
//has the user already authenticated from a previous session? see AccountStore.Create().Save() later
IEnumerable<Xamarin.Auth.Account> accounts = AccountStore.Create(this).FindAccountsForService("Twitter");
//check the account store for a valid account marked as "Twitter" and then hold on to it for future requests
foreach (Xamarin.Auth.Account account in accounts)
{
loggedInAccount = account;
break;
}
var cred = new LinqToTwitter.InMemoryCredentialStore();
cred.ConsumerKey = loggedInAccount.Properties["oauth_consumer_key"];
cred.ConsumerSecret = loggedInAccount.Properties["oauth_consumer_secret"];
cred.OAuthToken = loggedInAccount.Properties["oauth_token"];
cred.OAuthTokenSecret = loggedInAccount.Properties["oauth_token_secret"];
var auth = new LinqToTwitter.PinAuthorizer()
{
CredentialStore = cred,
};
var TwitterCtx = new LinqToTwitter.TwitterContext(auth);
Console.WriteLine(TwitterCtx.User);
tl = await
(from tweet in TwitterCtx.Status
where tweet.Type == LinqToTwitter.StatusType.Home
select tweet).ToListAsync();
Console.WriteLine("Tweets Returned: " + tl.Count.ToString());
Figure 7 shows the output of the previous code listing in Xamarin.Android in the Genymotion Emulator.
Figure 8 shows the same code output running in iOS, displaying the user's friend's tweets from the timeline.
Posting To Twitter
Finally, how does a developer post to Twitter? L2T makes this amazingly simple by using the TwitterContext's .TweetAsync() method and passing the appropriate parameter(s). In this example, the code will just send some text to Twitter.
Listing 7 shows the code in both Android and the iPhone to send a tweet.
Listing 7: Code to post to Twitter.
var cred = new LinqToTwitter.InMemoryCredentialStore();
cred.ConsumerKey = loggedInAccount.Properties["oauth_consumer_key"];
cred.ConsumerSecret = loggedInAccount.Properties["oauth_consumer_secret"];
cred.OAuthToken = loggedInAccount.Properties["oauth_token"];
cred.OAuthTokenSecret = loggedInAccount.Properties["oauth_token_secret"];
var auth0 = new LinqToTwitter.PinAuthorizer()
{
CredentialStore = cred,
};
var TwitterCtx = new LinqToTwitter.TwitterContext(auth0);
TwitterCtx.TweetAsync("Posting thanks to @joemayo and @wbm");
Figure 9 shows that the tweet was actually posted.
Lock It Down
This article has looked at the Xamarin.Auth library. Specifically, it's demonstrated how to connect to Twitter using OAuth, and the support in the Xamarin.Auth library. Once you have access to the necessary connectivity information, you can take the necessary authentication information and use a second library, showing a third-party community of libraries, to easily query and use Twitter.