Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building a Twitter client for Windows Phone - Getting The Public Timeline

DZone's Guide to

Building a Twitter client for Windows Phone - Getting The Public Timeline

· Mobile Zone
Free Resource

Discover how to focus on operators for Reactive Programming and how they are essential to react to data in your application.  Brought to you in partnership with Wakanda

In case you missed them, the previous articles on how to build MangoTree, a Windows Phone Twitter client, are available here:

MangoTree can tweet, but it currently cannot see what other people are tweeting. There is an API method available, though, that can get the current local feed for the authenticated user. It is called statuses/home_timeline. I already have the OAuth core working pretty well, so all I have to do is execute the method against the URL Twitter gives the developer, providing the correct credentials. Here is the snippet:

public static void GetHomeTimeline()
{
    Dictionary<string, string> parameters = new Dictionary<string, string>();
    parameters.Add("oauth_consumer_key", AuthConstants.ConsumerKey);
    parameters.Add("oauth_signature_method", "HMAC-SHA1");
    parameters.Add("oauth_timestamp", StringHelper.UNIXTimestamp);
    parameters.Add("oauth_nonce", Guid.NewGuid().ToString().Replace("-", ""));
    parameters.Add("oauth_version", "1.0");
    parameters.Add("oauth_token", App.Token);

    OAuthClient client = new OAuthClient();
    client.PerformRequest(parameters,
        "http://api.twitter.com/1/statuses/home_timeline.json", "GET", AuthConstants.ConsumerSecret, App.TokenSecret, string.Empty, data =>
        {
            string returned = (string)data[0];
            Debug.WriteLine(returned);
        }, RequestType.Read);
}

This method is a part of a static Methods class, that is designed to access various Twitter capabilities. When this method is successfully executed, I am getting raw JSON data - for testing purposes, I am putting it in the Output Console. The amount of data connected to a single tweet is quite bit - take a look at what it is:

{
      "in_reply_to_user_id_str":"14307150",
      "truncated":false,
      "created_at":"Mon Apr 09 04:44:12 +0000 2012",
      "coordinates":null,
      "retweeted":false,
      "place":null,
      "in_reply_to_screen_name":"tylerjameslee",
      "possibly_sensitive":false,
      "contributors":null,
      "user":{
         "id":1312381,
         "listed_count":57,
         "contributors_enabled":false,
         "profile_sidebar_border_color":"8f0000",
         "geo_enabled":true,
         "profile_background_image_url_https":"https:\/\/si0.twimg.com\/profile_background_images\/75507540\/250px-Porkins.jpg",
         "friends_count":921,
         "profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/2048185458\/IMG_20120405_155554_normal.jpg",
         "profile_background_tile":true,
         "followers_count":746,
         "is_translator":false,
         "show_all_inline_media":true,
         "follow_request_sent":false,
         "statuses_count":47764,
         "utc_offset":-25200,
         "profile_sidebar_fill_color":"cccccc",
         "name":"Joshua Rivera",
         "default_profile_image":false,
         "protected":false,
         "profile_background_color":"961313",
         "favourites_count":275,
         "lang":"en",
         "url":"http:\/\/www.thejoshuarivera.com\/",
         "verified":false,
         "created_at":"Fri Mar 16 20:54:27 +0000 2007",
         "profile_background_image_url":"http:\/\/a0.twimg.com\/profile_background_images\/75507540\/250px-Porkins.jpg",
         "description":"Older white women say I'm very articulate. I'm also a huge dick.",
         "profile_link_color":"424240",
         "profile_image_url_https":"https:\/\/si0.twimg.com\/profile_images\/2048185458\/IMG_20120405_155554_normal.jpg",
         "default_profile":false,
         "following":true,
         "profile_use_background_image":true,
         "location":"burque",
         "notifications":false,
         "id_str":"1312381",
         "profile_text_color":"7a7a7a",
         "time_zone":"Mountain Time (US & Canada)",
         "screen_name":"supersloth"
      },
      "retweet_count":0,
      "favorited":false,
      "in_reply_to_user_id":14307150,
      "source":"\u003Ca href=\"http:\/\/www.tweetdeck.com\" rel=\"nofollow\"\u003ETweetDeck\u003C\/a\u003E",
      "in_reply_to_status_id_str":"189211447128104960",
      "geo":null,
      "in_reply_to_status_id":189211447128104960,
      "id":189212091205419008,
      "id_str":"189212091205419008",
      "text":"@tylerjameslee http:\/\/t.co\/sDmfpuXq and of course http:\/\/t.co\/7W24zRMc"
   }

This obviously requires the implementation of two custom models - Tweet and User. I created a new folder called Models and added two cs source files for each of the models mentioned. 

Tweet.cs:

namespace MangoTree.Models
{
    public class Tweet
    {
        public string InReplyToUserIdStr { get; set; }
        public bool Truncated { get; set; }
        public string CreatedAt { get; set; }
        public string Coordinates { get; set; }
        public bool Retweeted { get; set; }
        public string Place { get; set; }
        public string InReplyToScreenName { get; set; }
        public string Contributors { get; set; }
        public User Author { get; set; }
        public int RetweetCount { get; set; }
        public bool Favorited { get; set; }
        public int InReplyToUserId { get; set; }
        public string Source { get; set; }
        public string InReplyToStatusIdStr { get; set; }
        public ulong InReplyToStatusId { get; set; }
        public ulong Id { get; set; }
        public string IdStr { get; set; }
        public string Text { get; set; }
    }
}

User.cs:

namespace MangoTree.Models
{
    public class User
    {
        public int ID { get; set; }
        public int ListedCount { get; set; }
        public bool ContributorsEnabled { get; set; }
        public string ProfileBackgroundImageUrlHTTPS { get; set; }
        public int FriendsCount { get; set; }
        public string ProfileSidebarColor { get; set; } // better to keep the raw color code
        public bool GeoEnabled { get; set; }
        public string ProfileImageUrl { get; set; }
        public bool ProfileBackgroundTile { get; set; }
        public int FollowersCount { get; set; }
        public bool IsTranslator { get; set; }
        public bool ShowAllInlineMedia { get; set; }
        public bool FollowRequestSent { get; set; }
        public int StatusesCount { get; set; }
        public int UTCOffset { get; set; }
        public string ProfileSidebarFillColor { get; set; } // again, better to keep the raw color
        public string Name { get; set; }
        public bool DefaultProfileImage { get; set; }
        public bool Protected { get; set; }
        public string ProfileBackgroundColor { get; set; }
        public int FavoritesCount { get; set; }
        public string Language { get; set; }
        public string Url { get; set; }
        public bool Verified { get; set; }
        public string CreatedAt { get; set; }
        public string ProfileBackgroundImageUrl { get; set; }
        public string Description { get; set; }
        public string ProfileLinkColor { get; set; }
        public string ProfileImageUrlHTTPS { get; set; }
        public bool DefaultProfile { get; set; }
        public bool Following { get; set; }
        public bool ProfileUseBackgroundImage { get; set; }
        public string Location { get; set; }
        public bool Notifications { get; set; }
        public string IdString { get; set; }
        public string ProfileTextColor { get; set; }
        public string TimeZone { get; set; }
        public string ScreenName { get; set; }
    }
}

Json parsing can be done with the stock Json DLL - System.Json.dll (usually located in C:\Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Libraries\Client). In the Utility folder, I created a new class called TweetParser. It will be used to parse individual tweets from the raw response. Here is what the overall "raw-to-model" converter looks like:

using System;
using MangoTree.Models;
using System.Collections.ObjectModel;
using System.Json;
using System.Diagnostics;
namespace MangoTree.Utility
{
    public static class TweetParser
    {
        public static ObservableCollection<Tweet> Parse(string raw)
        {
            ObservableCollection<Tweet> returnable = new ObservableCollection<Tweet>();
            try
            {
                JsonArray array = JsonObject.Parse(raw) as JsonArray;
                foreach (JsonObject tweet in array)
                {
                    Tweet timelineItem = new Tweet()
                    {
                        InReplyToUserIdStr = (tweet["in_reply_to_user_id_str"] ?? string.Empty),
                        Truncated = (bool)tweet["truncated"],
                        CreatedAt = tweet["created_at"],
                        Retweeted = tweet["retweeted"],
                        Place = (tweet["place"] ?? string.Empty),
                        InReplyToScreenName = (tweet["in_reply_to_screen_name"] ?? string.Empty),
                        Contributors = (tweet["contributors"] ?? string.Empty),
                        RetweetCount = tweet["retweet_count"],
                        Favorited = tweet["favorited"],
                        InReplyToUserId = (tweet["in_reply_to_user_id"] ?? 0),
                        Source = (tweet["source"] ?? string.Empty),
                        InReplyToStatusIdStr = (tweet["in_reply_to_status_id_str"] ?? string.Empty),
                        InReplyToStatusId = (tweet["in_reply_to_status_id"] ?? 0),
                        Id = tweet["id"],
                        IdStr = tweet["id_str"],
                        Text = tweet["text"],
                        Author = GetUser(tweet["user"])
                    };

                    returnable.Add(timelineItem);
                }
            }
            catch (Exception ex)
            {
                // The collection failed parsing.
                Debug.WriteLine("Collection failed parsing.\n" + ex.InnerException + Environment.NewLine + ex.Data);
            }
            return returnable;
        }

        private static User GetUser(JsonValue rawUser)
        {
            User returnable = new User()
            {
                ID = rawUser["id"],
                ListedCount = rawUser["listed_count"],
                ContributorsEnabled = rawUser["contributors_enabled"],
                ProfileSidebarColor = rawUser["profile_sidebar_border_color"],
                GeoEnabled = rawUser["geo_enabled"],
                ProfileBackgroundImageUrlHTTPS = (rawUser["profile_background_image_url_https"] ?? string.Empty),
                FriendsCount = rawUser["friends_count"],
                ProfileImageUrl = rawUser["profile_image_url"],
                ProfileBackgroundTile = rawUser["profile_background_tile"],
                FollowersCount = rawUser["followers_count"],
                IsTranslator = rawUser["is_translator"],
                ShowAllInlineMedia = rawUser["show_all_inline_media"],
                FollowRequestSent = rawUser["follow_request_sent"],
                StatusesCount = rawUser["statuses_count"],
                UTCOffset = rawUser["utc_offset"],
                ProfileSidebarFillColor = rawUser["profile_sidebar_fill_color"],
                Name = rawUser["name"],
                DefaultProfileImage = rawUser["default_profile_image"],
                Protected = rawUser["protected"],
                ProfileBackgroundColor = rawUser["profile_background_color"],
                FavoritesCount = rawUser["favourites_count"],
                Language = rawUser["lang"],
                Url = (rawUser["url"] != null ? rawUser["url"].ToString() : string.Empty),
                Verified = rawUser["verified"],
                CreatedAt = rawUser["created_at"],
                ProfileBackgroundImageUrl = (rawUser["profile_background_image_url"] ?? string.Empty),
                Description = (rawUser["description"] ?? string.Empty),
                ProfileLinkColor = rawUser["profile_link_color"],
                ProfileImageUrlHTTPS = rawUser["profile_image_url_https"],
                DefaultProfile = rawUser["default_profile"],
                Following = rawUser["following"],
                ProfileUseBackgroundImage = rawUser["profile_use_background_image"],
                Location = (rawUser["location"] ?? string.Empty),
                Notifications = rawUser["notifications"],
                IdString = rawUser["id_str"],
                ProfileTextColor = rawUser["profile_text_color"],
                TimeZone = rawUser["time_zone"],
                ScreenName = rawUser["screen_name"]
            };
            return returnable;
        }
    }
}

I am making heavy use of the null-coalescing operator to verify whether I am attempting to read a null value. Overall, the parsing process could be done with all the plumbing done for you - just use JSON.NET to deserialize the model, given that you have the correct attribute linking. I decided against this since it will add an extra assembly reference that is not going to be heavily used. System.Json is pretty lightweight for this specific situation and works good enough.

Going back to the GetHomeTimeline method, I am now getting an ObservableCollection<Tweet> - I can easily bind it to anything now, including the ListBox that will be placed on the MainPage.xaml.

You can download the current progress on the project here (full source - without API keys).

Learn how divergent branches can appear in your repository and how to better understand why they are called “branches".  Brought to you in partnership with Wakanda

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}