O'Reilly logo

Mobile Development with C# by Greg Shackles

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Accessing the Network

Now that you have built applications for all three platforms and have some code sharing techniques in your tool belt, it’s time to start applying them. Every good application needs to be able to connect to external resources, whether they are a complex set of web services or just a simple leaderboard. The .NET Framework provides a rich networking stack that makes it easy to interact with network resources. Since all three platforms are able to leverage the power of this framework, these built-in networking libraries provide a great common base to build upon, allowing for a large amount of code reuse between them.

Reaching into the Cloud

In this chapter, you will build a very simple Twitter client that can read in a list of tweets from a particular account and display them to the user. The user interface implementations will have to remain native to each platform, but it’s possible to share all of the code needed to actually interact with Twitter’s API, which will live entirely in the shared library. Even though the scope of this application is limited, it’s easy to see that in a full application with an expanded set of features, the ability to reuse your code will go a long way to save time both up front and down the line.

Shared Layer

Open up the SharedLibrary project and create a new folder named Chapter4, where all new shared classes from this chapter will be added. Add a new class to this folder named Tweet. This will be a simple data object to hold information about a single tweet, including its ID, the time it was created, and its text content (see Example 4-1).

Example 4-1. Tweet.cs
using System;

namespace SharedLibrary.Chapter4
{
    public class Tweet
    {
        public long Id { get; set; }
        public DateTime CreatedAt { get; set; }
        public string Text { get; set; }
    }
}

Now that the data class is created, it’s time to design the code to actually consume the data from Twitter’s API. Twitter exposes its data through a simple REST interface, which allows you to request data by using different URLs that it defined, just as you would with a web site. To get a list of tweets from the O’Reilly Media account, you can use the following URL:

https://mobiledevelopmentwithcs.blob.core.windows.net/twitter/

Example 4-2 shows what the resulting XML will look like. The actual response from Twitter will include a lot more information that can be ignored for the purposes of this application. The XML in Example 4-2 only includes the fields relevant to this chapter.

Example 4-2. Sample XML response from Twitter (simplified)
<?xml version="1.0" encoding="UTF-8"?>
<statuses type="array">
  <status>
    <created_at>Mon Dec 12 11:28:54 +0000 2011</created_at>
    <id>111111111111111111</id>
    <text>...</text>
  </status>
  <status>
    <created_at>Sun Dec 11 16:20:10 +0000 2011</created_at>
    <id>222222222222222222</id>
    <text>...</text>
  </status>
</statuses>

Add a new class named TwitterClient to the Chapter4 folder which will handle all communication with the Twitter API (see Example 4-3). The client needs to provide one method named, GetTweetsForUser, which takes in a Twitter username and returns a list of tweets for that user.

Example 4-3. TwitterClient.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Xml.Linq;

namespace SharedLibrary.Chapter4
{
    public class TwitterClient
    {
        private const string _baseUrl = "https://api.twitter.com/1/statuses/";

        public void GetTweetsForUser(string user, Action<IList<Tweet>> callback) 1
        {
            string url = _baseUrl + "user_timeline.xml?screen_name=" + Uri.EscapeUriString(user);
            var client = new WebClient(); 2

            client.DownloadStringCompleted +=
                (sender, args) =>
                {
                    var tweets =
                        XDocument
                            .Parse(args.Result) 3
                            .Root
                            .Elements("status")
                            .Select(status => new Tweet 4
                            {
                                Id = long.Parse(status.Element("id").Value),
                                CreatedAt = DateTime.ParseExact(status.Element("created_at").Value,
                                                                "ddd MMM dd HH:mm:ss zz00 yyyy", null),
                                Text = status.Element("text").Value
                            })
                            .ToList();

                    callback(tweets); 5
                };

            client.DownloadStringAsync(new Uri(url)); 6
        }
    }
}
1

Define the GetTweetsForUser method, which takes in the username and also a callback method for processing the results.

2

Since the System.Net namespace is available to all three platforms, you can leverage the standard WebClient class to handle communicating with the API.

3

Use the built-in .NET XML libraries for parsing the XML response.

4

LINQ-to-XML is available for all platforms as well, so use that to transform each XML status element into a Tweet object.

5

Once the list of tweets has been processed, send that through to the provided callback method.

6

Finally, tell the WebClient object to begin the request asynchronously.

Looking at this code, one thing you might be wondering is the reason for making the request asynchronously and passing the result back through the use of a callback, rather than using the return value of the method. Either style would work perfectly fine, but each comes with its own set of advantages and difficulties. Synchronous code is generally much easier to read and test but will be executed on the same thread it is called from, which in most cases will be the UI thread. If the UI thread of an application is locked up for some period of time, the entire application will appear unresponsive to the user during that time. This means that each calling platform would need to remember to move this method call into a background thread to avoid locking up the application for the duration of the request. By implementing the method asynchronously, as is done in this example, the request will automatically happen in a background thread. This helps to simplify the application code on each platform since it won’t need to worry about spawning and managing new threads.

That’s all the code needed to interact with Twitter’s API for the purposes of this application. As you can see, the code is focused entirely on retrieving a list of tweets across the network without caring how they are being used or displayed. Make sure to add links to these files in the SharedLibrary.MonoTouch, SharedLibrary.MonoAndroid, and SharedLibrary.WindowsPhone projects so that all platforms can access them. You can refer back to Chapter 3 if you need to review how to link files between projects. You’ll also need to add references to System.Xml and System.Xml.Linq to each project in order for them to compile correctly.

iOS

Now that the shared layer is built, it’s time to write some apps on top of it, starting with iOS. Create a new empty iPhone project named Chapter4.MonoTouchApp. Add the SharedLibrary.MonoTouch project to the solution and add a reference to it from Chapter4.MonoTouchApp so that the application can access the Twitter client.

The user interface for this application will be a list of tweets. In iOS applications, lists are typically implemented using a UITableView, which is essentially a table with just one column. It is roughly analogous to list controls you may be familiar with on other platforms. Each row in the table is represented by UITableViewCell objects, which can be customized according to what the application needs to display. One way to add a UITableView to an interface is to use Interface Builder, as in Chapter 2. However, for cases like this where the entire interface is a list, iOS includes a specialized view controller type, UITableViewController, to help simplify the process.

Add a new class to the project named TwitterViewController, which should extend UITableViewController. Once the view loads, the application should show a loading indicator to the user while it downloads the list of tweets, and then show the list once the results come back (see Example 4-4).

Example 4-4. TwitterViewController.cs
using MonoTouch.UIKit;
using SharedLibrary.Chapter4;

namespace Chapter4.MonoTouchApp
{
    public class TwitterViewController : UITableViewController 1
    {
        private TwitterClient _client;

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            _client = new TwitterClient(); 2

            var loading = new UIAlertView("Downloading Tweets", 3
                                          "Please wait...",
                                          null, null, null);
            loading.Show();

            var indicator = new UIActivityIndicatorView(UIActivityIndicatorViewStyle.WhiteLarge);
            indicator.Center = new System.Drawing.PointF(loading.Bounds.Width / 2,
                                                         loading.Bounds.Size.Height - 40);
            indicator.StartAnimating();
            loading.AddSubview(indicator); 4

            _client.GetTweetsForUser("OReillyMedia", tweets => 5
            {
                InvokeOnMainThread(() => 6
                {
                    TableView.Source = new TwitterTableViewSource(tweets);
                    TableView.ReloadData();
                    loading.DismissWithClickedButtonIndex(0, true);
                });
            });
        }
    }
}
1

Extend the UITableViewController class.

2

Create a new instance of TwitterClient.

3

Show a dialog box that displays a message to the user while downloading the tweets.

4

Add an animated loading indicator to the dialog.

5

Start downloading tweets for the OReillyMedia account and assign a callback for the results.

6

On the UI thread, display the results in the table and hide the loading indicator.

The most interesting code here is inside the callback for processing the list of tweets. As discussed earlier, you want to keep all long-running operations happening in background threads in order to keep the application as responsive as possible for the user. However, once the result comes back and you want to make updates to the interface, those updates should happen back on the UI thread. In iOS, you can explicitly say that a block of code should run on the UI thread by wrapping it in a call to InvokeOnMainThread().

The other part of this callback that won’t look familiar is the use of the TwitterTableViewSource class, which hasn’t been defined yet. Add a new class to the project named TwitterTableViewSource that extends the UITableViewSource class. iOS provides two classes for specifying how a UITableView should render: UITableViewDelegate and UITableViewDataSource. Both classes are available in MonoTouch, but since they are very similar, MonoTouch also provides the UITableViewSource class that combines them to simplify the process. By overriding different methods in this class, you can provide the height of a row, the contents of a cell, what to do when the user selects one of the rows, and a number of other options (see Example 4-5).

Example 4-5. TwitterTableViewSource.cs
using System.Collections.Generic;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
using SharedLibrary.Chapter4;

namespace Chapter4.MonoTouchApp
{
    public class TwitterTableViewSource : UITableViewSource
    {
        private readonly IList<Tweet> _tweets;
        private const string TweetCell = "TweetCell";

        public TwitterTableViewSource(IList<Tweet> tweets) 1
        {
            _tweets = tweets;
        }

        public override int RowsInSection(UITableView tableView, int section) 2
        {
            return _tweets.Count;
        }

        public override float GetHeightForRow(UITableView tableView, NSIndexPath indexPath) 3
        {
            return 60;
        }

        public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) 4
        {
            var cell = tableView.DequeueReusableCell(TweetCell) 5
                        ?? new UITableViewCell(UITableViewCellStyle.Subtitle, TweetCell);
            var tweet = _tweets[indexPath.Row];

            cell.TextLabel.Text = tweet.Text; 6
            cell.DetailTextLabel.Text = tweet.CreatedAt.ToLocalTime().ToString(); 7

            return cell;
        }

        public override void RowSelected(UITableView tableView, NSIndexPath indexPath) 8
        {
            var selectedTweet = _tweets[indexPath.Row];
            new UIAlertView("Full Tweet", selectedTweet.Text,
                            null, "Ok", null).Show();
        }
    }
}
1

Require that a list of tweets be supplied via the constructor.

2

The RowsInTable() method declares how many rows a table section has. Since this table has only one section, it is equal to the number of tweets received.

3

For this application all rows should have the same height, so GetHeightForRow() returns a constant value.

4

The GetCell() method is called once per row in the table, and tells the table how to render that row.

5

Create a table cell using the built-in Subtitle style, reusing an existing cell if possible to be more efficient.

6

Set the primary text of the cell to be the text of the tweet.

7

Set the secondary text of the cell to be the date and time the tweet was created, converted to the user’s local time.

8

When the user selects a row in the table, the RowSelected() method specifies how to process it. This application shows an alert to the user containing the full text of the tweet.

One important thing to note here is the use of DequeueReusableCell() inside of the GetCell() method. It’s possible for a list to contain a very large number of elements depending on what you’re showing the user, but only a small fraction of them will be visible at any given time due to the size of the screen. Using DequeueReusableCell() allows the OS to reuse cells it has already allocated if they are no longer visible to the user in order to avoid having to allocate memory for every cell in the table. Even if your table contains multiple cell layouts you can simply assign different keys to each of them and still allow for cell reusability when possible. When there is no cell available to be reused DequeueReusableCell() will return null and a new cell should be constructed.

All that’s left to do is tell the application to load a new TwitterViewController when it starts. Open up AppDelegate.cs and modify the FinishedLaunching() method to show the view (see Example 4-6). Now you can start the application, which should look similar to Figure 4-1.

Example 4-6. AppDelegate.cs
using MonoTouch.Foundation;
using MonoTouch.UIKit;

namespace Chapter4.MonoTouchApp
{
    [Register ("AppDelegate")]
    public partial class AppDelegate : UIApplicationDelegate
    {
        private UIWindow _window;
        private TwitterViewController _twitterViewController;

        public override bool FinishedLaunching(UIApplication app, NSDictionary options)
        {
            _window = new UIWindow (UIScreen.MainScreen.Bounds);

            _twitterViewController = new TwitterViewController();
            _window.RootViewController = _twitterViewController;

            _window.MakeKeyAndVisible ();

            return true;
        }
    }
}
Twitter app for iOS
Figure 4-1. Twitter app for iOS

Android

Now let’s move on to the Android app. Create a new Mono for Android application project named Chapter4.MonoAndroid. Just as with the iOS application, add the SharedLibrary.MonoAndroid project to the solution and reference it from Chapter4.MonoAndroid. You can remove any activities and layout resources added by the project template to start from a fresh slate.

Since the application needs to access the Internet, Android requires that you explicitly request that permission in the application’s AndroidManifest.xml file. Any permissions you request will be shown to the user when they install the app, so be careful to only include what you need. Since Mono for Android tries to generate as much of this file for you as possible, you can request permissions through the project’s properties dialog.

Open up the project properties and select Android Manifest if using Visual Studio, or Mono for Android Application if you’re using MonoDevelop. If there isn’t already a manifest for the project, which is likely the case since this is a fresh project, it will give you the option to create one. Go ahead and create the file, since this application will need to make changes to it. Once the file is created, you should see different options, including a list of permissions required for the application. From this list, check off the box for the INTERNET permission (see Figure 4-2).

Requesting permission to access the internet
Figure 4-2. Requesting permission to access the internet

In the Resources/Layout folder, add a new Android layout file named Twitter.axml. Add a ListView element to the layout, which will serve as the list of tweets to be shown to the user (see Example 4-7). In the same folder, add a second layout file named Tweet.axml that will be used for each tweet in the list. In this layout, add two TextView elements, one for the tweet’s text and the other for the time it was created (see Example 4-8). For both of these layouts, each element should be assigned an ID so that it is accessible from the application code.

Example 4-7. Twitter.axml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <ListView
        android:id="@+id/Tweets"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
Example 4-8. Tweet.axml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
        android:id="@+id/Text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:singleLine="true"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:padding="5dip" />
    <TextView
        android:id="@+id/CreatedAt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dip" />
</LinearLayout>

Next, add a new activity to the application named TwitterActivity. As before, once the activity is created, it should display a progress dialog to the user while downloading the list of tweets. This activity display the list to the user when the data is ready (see Example 4-9).

Example 4-9. TwitterActivity.cs
using Android.App;
using Android.OS;
using Android.Widget;
using SharedLibrary.Chapter4;

namespace Chapter4.MonoAndroidApp
{
    [Activity (Label = "\\@OReillyMedia", MainLauncher = true)] 1
    public class TwitterActivity : Activity
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate (bundle);

            SetContentView (Resource.Layout.Twitter); 2

            var client = new TwitterClient(); 3

            var loading = ProgressDialog.Show(this, 4
                                              "Downloading Tweets",
                                              "Please wait...", true);

            client.GetTweetsForUser("OReillyMedia", tweets => 5
            {
                RunOnUiThread (() => 6
                {
                    var tweetList = FindViewById<ListView>(Resource.Id.Tweets); 7
                    tweetList.Adapter = new TweetListAdapter(this, tweets); 8
                    tweetList.ItemClick += (object sender, ItemEventArgs e) => 9
                    {
                        var selectedTweet = tweets[e.Position];

                        new AlertDialog.Builder(this)
                            .SetTitle("Full Tweet")
                            .SetMessage(selectedTweet.Text)
                            .SetPositiveButton("Ok", delegate { })
                            .Show();
                    };

                    loading.Hide();
                });
            });
        }
    }
}
1

Decorate the activity with ActivityAttribute to generate the proper entry in AndroidManifest.xml.

2

Set the activity’s layout to Twitter.axml.

3

Create a new instance of TwitterClient.

4

Show a progress dialog to the user while downloading the list of tweets.

5

Start downloading tweets for the OReillyMedia account and assign a callback for the results.

6

Make sure to update the UI from the UI thread.

7

Get a reference to the list in the layout.

8

Set the list’s adapter so that it displays the data.

9

When any list item is clicked on, display an alert dialog containing the full text of the tweet.

Just like in iOS, Android provides its own method to allow a background thread to update the UI: RunOnUiThread(). This is important because if your application does not respond to user input for five seconds, it will show an alert to the user saying it has stopped responding, and offers them the option of forcing the app to close. Even though the limit here is five seconds, in reality you never want to come anywhere close to that threshold. You should always strive to keep your application as responsive as possible.

In the callback method, you may have noticed the use of a class named TweetListAdapter, which has not been defined yet. Go ahead and add a new class named TweetListAdapter to the project. As the name implies, this is a class that will be used to take the list of tweets returned and expose them in a way the ListView understands. In Android, each item in a list can be any view object, so it can be anything from a single TextView to a layout containing any number of objects. Items in the list of tweets will be inflated using the layout defined in Tweet.axml (see Example 4-11).

Example 4-10. TweetListAdapter.cs
using System.Collections.Generic;
using Android.App;
using Android.Views;
using Android.Widget;
using SharedLibrary.Chapter4;

namespace Chapter4.MonoAndroidApp
{
    public class TweetListAdapter : BaseAdapter<Tweet> 1
    {
        private readonly Activity _context;
        private readonly IList<Tweet> _tweets;

        public TweetListAdapter(Activity context, IList<Tweet> tweets) 2
        {
            _context = context;
            _tweets = tweets;
        }

        public override View GetView(int position, View convertView, ViewGroup parent) 3
        {
            var view = convertView 4
                        ?? _context.LayoutInflater.Inflate(Resource.Layout.Tweet, null);
            var tweet = _tweets[position];

            view.FindViewById<TextView>(Resource.Id.Text).Text = tweet.Text; 5
            view.FindViewById<TextView>(Resource.Id.CreatedAt).Text = 6
                tweet.CreatedAt.ToLocalTime().ToString();

            return view;
        }

        public override int Count 7
        {
            get { return _tweets.Count; }
        }

        public override long GetItemId(int position) 8
        {
            return position;
        }

        public override Tweet this[int position] 9
        {
            get { return _tweets[position]; }
        }
    }
}
1

Extend the BaseAdapter class, strongly typing it with the type Tweet.

2

In the constructor, require an activity context and a list of tweets.

3

The GetView() is what provides the list with a view to display, and is called every time a list item is being created.

4

If there is a value for convertView, use that instead of inflating a brand new View.

5

Update the first TextView to contain the tweet’s text.

6

Update the second TextView to contain the date and time the tweet was created, converted to the user’s local time.

7

Specify how many items are in this list.

8

GetItemId() specifies an ID for a given row. For this application, simply return the position in the list.

9

Override the array index operator for this class so that it returns the item at a specified position in the list.

Inside the implementation of GetView(), make note of the use of the convertView parameter. As in the iOS application, you don’t want to have to allocate list items for every element in a very long list when only a few will be displayed at any given time. If a view has already been inflated that can be reused, it will be passed in as convertView. If the parameter is null, use the provided activity to inflate a new View object from the layout. In Java, this class would normally be implemented as an anonymous implementation, which automatically has access to the outer activity. Since C# doesn’t support this feature, you have to pass in the activity to the adapter as seen here.

That’s everything required for the Android application. Start it up in the emulator and it should look similar to Figure 4-3.

Twitter app for Android
Figure 4-3. Twitter app for Android

Windows Phone

Finally, let’s create the same application for Windows Phone. Create a new Windows Phone application project named Chapter4.WindowsPhoneApp. Add the SharedLibrary.WindowsPhone project to the solution and reference it from Chapter4.WindowsPhoneApp.

Open up MainPage.xaml, and modify it to look like Example 4-11:

Example 4-11. MainPage.xaml
<phone:PhoneApplicationPage
    x:Class="Chapter4.WindowsPhoneApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Twitter" Style="{StaticResource PhoneTextNormalStyle}"/> 1
            <TextBlock x:Name="PageTitle" Text="@OReillyMedia" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/> 2
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel x:Name="Loading"> 3
                <ProgressBar IsIndeterminate="True" />
                <TextBlock TextAlignment="Center" Text="Downloading tweets, please wait..." />
            </StackPanel>
            <ListBox x:Name="Items" Margin="0,0,-12,0" ItemsSource="{Binding}" SelectionChanged="TweetSelected"> 4
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Margin="0,15,0,15">
                            <TextBlock Text="{Binding Text}" TextTrimming="WordEllipsis" Style="{StaticResource PhoneTextNormalStyle}"/> 5
                            <TextBlock Text="{Binding CreatedAt}" Style="{StaticResource PhoneTextSmallStyle}"/> 6
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>
1

In the title panel, set the application’s title to “Twitter.”

2

Also in the title panel, change the page’s title to “@OReillyMedia.”

3

Add a StackPanel that contains a progress bar and text informing the user that the download is happening.

4

Add a ListBox to display the list of tweets returned from Twitter, attaching an event handler to when a tweet is selected.

5

For each tweet, display the tweet’s text first.

6

Below that, in a smaller font display the time and date which it was created, converted to the user’s local time.

Next, open up the page’s code-behind file, MainPage.xaml.cs. When the page is loaded, it should display the progress indicator to the user while downloading the list of tweets, and then show the list when the data is ready (see Example 4-12).

Example 4-12. MainPage.xaml.cs
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
using SharedLibrary.Chapter4;

namespace Chapter4.WindowsPhoneApp
{
    public partial class MainPage : PhoneApplicationPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            var client = new TwitterClient(); 1

            client.GetTweetsForUser("OReillyMedia", tweets => 2
            {
                Deployment.Current.Dispatcher.BeginInvoke(() => 3
                {
                    DataContext = tweets; 4
                    Loading.Visibility = Visibility.Collapsed; 5
                });
            });
        }

        private void TweetSelected(object sender, SelectionChangedEventArgs e)
        {
            var tweet = (Tweet)e.AddedItems[0];

            MessageBox.Show(tweet.Text, "Full Tweet", MessageBoxButton.OK); 6
        }
    }
}
1

Create a new instance of TwitterClient.

2

Start downloading tweets for the OReillyMedia account and assign a callback for the results.

3

Make sure to update the UI from the UI thread.

4

Set the page’s DataContext to the list of tweets to trigger data binding.

5

Hide the progress indicator.

6

When a tweet is selected, display a message box containing its full text.

Windows Phone, just like iOS and Android, provides a mechanism to invoke a block of code on the UI thread when running on a background thread by using Deployment.Current.Dispatcher.BeginInvoke(). This allows the application to remain responsive and display the progress indicator instead of freezing for the duration of the request. Thanks to XAML’s powerful data binding capabilities, that’s all that needs to be done to display the results in the user interface. If you run the application in the emulator, it should look like Figure 4-4.

Twitter app for Windows Phone
Figure 4-4. Twitter app for Windows Phone

Notifying the User Interface

Suppose that you wanted to extend the application to show the user an alert whenever somebody mentions her on Twitter. The logic to check with Twitter for mentions would ideally remain in the shared layer so that it can be reused across all platforms. This means that the check needs to be platform-independent, but each platform still needs to be able to know when it occurs so that it can notify the user. This is a great example of a time when the observer pattern discussed in Chapter 3 can come in handy. TwitterClient can expose an event that it publishes when a mention is detected, and each application can subscribe to the event and alert the user.

Shared Layer

First, add a new class to the Chapter4 folder named MentionEventArgs, which will represent the data sent back to the application when the event is fired. It should include a Tweet object (the same type created in the first section) containing information about the mention (see Example 4-13).

Example 4-13. MentionEventArgs.cs
using System;

namespace SharedLibrary.Chapter4
{
    public class MentionEventArgs : EventArgs
    {
        public Tweet Tweet { get; private set; }

        public MentionEventArgs(Tweet tweet)
        {
            Tweet = tweet;
        }
    }
}

Now open up TwitterClient.cs and modify it to look like Example 4-14. The example only includes new code being added to the class, so append this to what is already in that class.

Example 4-14. TwitterClient.cs (updates only)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Xml.Linq;

namespace SharedLibrary.Chapter4
{
    public class TwitterClient
    {
        public event EventHandler<MentionEventArgs> MentionReceived; 1

        public TwitterClient()
        {
            new Timer(delegate 2
            {
                var mention = new Tweet 3
                {
                    Id = 42,
                    CreatedAt = DateTime.Now,
                    Text = "This is a fake mention"
                };

                if (MentionReceived != null) 4
                {
                    MentionReceived(this, new MentionEventArgs(mention));
                }
            }, null, 15 * 1000, Timeout.Infinite); 5
        }

        // ...code from last section...
    }
}
1

Define the MentionReceived event, supplying MentionEventArgs as its argument type.

2

In the constructor, start a timer to pretend a mention is received.

3

Create a fake mention, assigning values for its properties.

4

If there are any subscribers to the event, publish the event to them.

5

Set the timer to fire once after 15 seconds.

To keep things simple, this example uses a Timer to fake the event of receiving a mention instead of getting bogged down in Twitter specifics. Fifteen seconds after the instance of TwitterClient is created, the timer will elapse and any subscribers to the event will be notified that a mention was received. Each application is then free to notify the user in any way it wants, allowing for fully native behavior. Add links to both of these files in all three platform-shared library projects.

iOS

Open up TwitterViewController.cs and attach a handler to the client’s MentionReceived event in the ViewDidLoad() method. When the event is received, show a new UIAlertView containing the tweet’s text (see Example 4-15). Remember that the callback will happen on a background thread, so be sure to show the alert back on the UI thread. When you run the application and the timer elapses, it should look like Figure 4-5.

Example 4-15. TwitterViewController.cs (updates only)
using MonoTouch.UIKit;
using SharedLibrary.Chapter4;

namespace Chapter4.MonoTouchApp
{
    public class TwitterViewController : UITableViewController
    {
        public override void ViewDidLoad()
        {
            // ...code from last section...

            _client.MentionReceived += (object sender, MentionEventArgs args) =>
            {
                InvokeOnMainThread(() =>
                    new UIAlertView("Mention Received", args.Tweet.Text,
                                    null, "Ok", null).Show());
            };
        }
    }
}
Mention received on iOS
Figure 4-5. Mention received on iOS

Android

Back in the Android application, modify TwitterActivity and attach a handler to the client’s MentionReceived event in OnCreate(). When the event is fired, show an alert dialog with the tweet’s text (see Example 4-16), making sure to do it on the UI thread. Running the application should result in something similar to Figure 4-6.

Example 4-16. TwitterActivity.cs (updates only)
using Android.App;
using Android.OS;
using Android.Widget;
using SharedLibrary.Chapter4;

namespace Chapter4.MonoAndroidApp
{
    [Activity (Label = "\\@OReillyMedia", MainLauncher = true)]
    public class TwitterActivity : Activity
    {
        protected override void OnCreate(Bundle bundle)
        {
            // ...code from last section...

            client.MentionReceived += (object sender, MentionEventArgs args) =>
            {
                RunOnUiThread(() =>
                {
                    new AlertDialog.Builder(this)
                        .SetTitle ("Mention Received")
                        .SetMessage(args.Tweet.Text)
                        .SetPositiveButton("Ok", delegate { })
                        .Show();
                });
            };
        }
    }
}
Mention received on Android
Figure 4-6. Mention received on Android

Windows Phone

Lastly, in the Windows Phone application, open MainPage.xaml.cs. In the OnNavigatedTo() method, show a message box when a mention is received, again being careful to execute the code on the UI thread. Running the application in the emulator should look like Figure 4-7.

Example 4-17. MainPage.xaml.cs (updates only)
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
using SharedLibrary.Chapter4;

namespace Chapter4.WindowsPhoneApp
{
    public partial class MainPage : PhoneApplicationPage
    {
        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            // ...code from last section...

            client.MentionReceived += delegate(object sender, MentionEventArgs args)
            {
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    MessageBox.Show(args.Tweet.Text, "Mention Received",
                                    MessageBoxButton.OK);
                });
            };
        }
    }
}
Mention received on Windows Phone
Figure 4-7. Mention received on Windows Phone

Summary

In this chapter, you created a simple application that communicates with the Twitter API over the Internet to download a list of tweets for a given user. All code involved with accessing the network and API was entirely contained to the shared library, and worked seamlessly across all platforms. Each application implemented a native user interface on top of that shared layer. While the implementations were similar across all of them, there is nothing preventing you from creating entirely different interfaces and user experiences for each of them. The observer pattern introduced in the last chapter was also leveraged to allow the shared layer to publish notifications to the application that a mention was received. In the next chapter, we will explore some options for persisting data in your applications, making use of both the filesystem and local databases.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required