Monday, September 20, 2010

Building a ‘real’ Windows Phone 7 Twitter App Part 3 – Sending a new Tweet

In parts one and two I showed you how to create a new Windows Phone 7 Panorama Package.  In part two we added oAuth to authenticate the user with Twitter. This is where the fun stuff happens. I’ll show you how to allow a new user to send a tweet.
We’ll be adding a new xaml page with a textBox for the tweet and an application bar with a save and cancel button.
Right click on the “Views” folder in the solution. Select “Add” then “New Item”.  Pick the “Windows Phone Portrait Page” template and give the page a name, I’ll name the page “TweetPage.xaml”.  Hit the “Add” button when done.
image
In the TweetPage.xaml file, change the “ApplicationTitle” to “Twitt” and the “PageTitle to “new tweet”.  Also remove the applicatoin bar code that is at the bottom.  I”m going to show you how to add it via code.  Your file should look like this:
<phone:PhoneApplicationPage
    x:Class="Twitt.Views.TweetPage"
    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"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
    shell:SystemTray.IsVisible="True">     <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>         <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Twitt" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="new tweet" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>         <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"></Grid>
    </Grid>
</phone:PhoneApplicationPage>



Let’s now add a button on our MainPage.xaml to open up this new page.  Open “MainPage.xaml” and find the third panorama item that we added in part 2.  It should look like this:
<!--Panorama item three-->
<controls:PanoramaItem Header="settings">
    <StackPanel>
        <Button Content="Account Settings" Click="Button_Click"/>
    </StackPanel>
</controls:PanoramaItem>
Add a new button and I’ll add names to each button and change the Click EventHandler names to match:
<!--Panorama item three-->
<controls:PanoramaItem Header="settings">
    <StackPanel>
        <Button x:Name="SettingsButton" Content="Account Settings" Click="SettingsButtonClick"/>
        <Button x:Name="TweetButton" Content="New Tweet" Click="TweetButtonClick"/>
    </StackPanel>
</controls:PanoramaItem>
We’ll need to create the new event handler for our new button in the MainPage.xaml.cs file:
private void SettingsButtonClick(object sender, RoutedEventArgs e)
{
    NavigationService.Navigate(new Uri("/Views/TwitterAuthPage.xaml", UriKind.Relative));
} private void TweetButtonClick(object sender, RoutedEventArgs e)
{
    NavigationService.Navigate(new Uri("/Views/TweetPage.xaml", UriKind.Relative));
}

Compile and run the app to ensure that pressing the new button opens our “new tweet” page:. image  image Let’s now add the TextBox and a couple TextBlock labels to our new “TweetPage.xaml” where the user will enter their tweet.  We’ll add the new code inside the “ContentPanel” “Grid” node.
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <StackPanel>
        <TextBlock x:Name="TitleTextBlock" HorizontalAlignment="Center" FontSize="{StaticResource PhoneFontSizeLarge}" Text="Twitter Message (tap to edit)" TextWrapping="Wrap"/>
        <TextBlock x:Name="CharactersCountTextBlock" HorizontalAlignment="Center" Text="140 characters remaining"/>
        <TextBox x:Name="TweetTextBox" Height="250" HorizontalAlignment="Stretch" TextWrapping="Wrap" TextChanged="TweetTextBoxTextChanged" KeyUp="MessageTextBoxKeyUp" IsTabStop="true" InputScope="Text"/>
    </StackPanel>
</Grid>
I’ve added one TextBlock for a title above the control to tell the user to “tap to edit”.  I’ve also added a seond TextBlock to update the number of characters remaining, since tweets can only hold a maximum of 140 characters.
I’ve set the “InputScope” property to “Text” on the TextBox control. Setting to “Text” will show the user suggested words as they type them on top of the keypad.  I’ve also added the “MessageTextboxKeyUp” event handler.  I want to dismiss the keypad if the user hit the “Enter” key.  For this to work you also need to set “IsTabStop” property to “true”.
I’ve also hooked up the “TextChanged” Event Handler so that as users type I can update the characters left TextBlock that I added.  Here’s what the code behind looks like:
namespace Twitt.Views
{
    public partial class TweetPage : PhoneApplicationPage
    {
        public TweetPage()
        {
            InitializeComponent();             UpdateRemainingCharacters();
        }         private void UpdateRemainingCharacters()
        {
            CharactersCountTextBlock.Text = String.Format("{0} characters remaining", 140 - TweetTextBox.Text.Length);
        }         private void TweetTextBoxTextChanged(object sender, TextChangedEventArgs e)
        {
            UpdateRemainingCharacters();
        }         private void MessageTextBoxKeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                Focus();
            }
        }
    }
}




I’ve made a helper method called “UpdateRemainingCharacters” which I also call from the constructor to set the text as well as being called from “TweetTextBoxTextChanged”.
Running the app, the “new tweet” page should now look like this:
image
If you select the TextBox and start typing you should see the keypad and suggested words I previously mentioned:
image
Next, we’ll add the application bar to the bottom for our “tweet” and cancel buttons.  In the TweetPage.xaml.cs, add the following new “CreateApplicationBar” method.  I enclosed it in “#region” to keep my code cleaner.
       public TweetPage()
       {
           InitializeComponent();
           CreateApplicationBar();            UpdateRemainingCharacters();
       }        #region ApplicationBar        private void CreateApplicationBar()
       {
           ApplicationBar = new ApplicationBar { IsMenuEnabled = true, IsVisible = true, Opacity = .9 };            var save = new ApplicationBarIconButton(new Uri("/Resources/Images/check.png", UriKind.Relative));
           save.Text = "tweet";
           save.Click += SaveClick;
           save.IsEnabled = false;            var cancel = new ApplicationBarIconButton(new Uri("/Resources/Images/cancel.png", UriKind.Relative));
           cancel.Text = "cancel";
           cancel.Click += CancelClick;            ApplicationBar.Buttons.Add(save);
           ApplicationBar.Buttons.Add(cancel);
       }        private void CancelClick(object sender, EventArgs e)
       {
           if (NavigationService.CanGoBack)
               NavigationService.GoBack();
       }
       private void SaveClick(object sender, EventArgs e)
       {
       }               #endregion In “CreateApplicaitonBar” I add two buttons, the first one is for sending the tweet to Twitter.  I set the “IsEnabled” property to “false.”  We need to check if the user has validated their account before we enable this button.  Notice I’m using two images “Resources/Images/check.png” and also “Resources/Images/cancel.png”.  We’re going to need to go get these two images.
Application icons are installed with the development tools and located here: C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.0\Icons The two icons we need are located at \Icons\dark\appbar.cancel.rest.png and \Icons\dark\appbar.check.rest.png.  We need to add these two icons to our “Resources/Images” folder.  Right click on “Images”, select “Add” and then “Existing Items” and add the two files.  Let’s rename them to “check.png” and “cancel.png”.  Right click on each one and select “Rename”. Try running the app now, and openeing the “new tweet” page, you should see this: image The icons are not showing up.  You need to set each icon as a “content” type instead of the default “resource”.  To do this, right click on each image file and select “Properties” and set the “Build Action” to “Content”. image
Now run the app and the page should now look like this:
image
Try the cancel button.  It should navigate back.
We now need to check if the user has been authenticated with Twitter and if so, enable the “Tweet” icon on the application bar.  I’m going to load the saved settings and check if the “AccessToken” and “AccessTokenSecret” exist, if so enable the first button:
public partial class TweetPage : PhoneApplicationPage
{
    private TwitterAccess _twitterSettings;     public TweetPage()
    {
        InitializeComponent();
        CreateApplicationBar();         UpdateRemainingCharacters();
    }     protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        _twitterSettings = Helper.LoadSetting<TwitterAccess>(Constants.TwitterAccess);
        if (_twitterSettings == null) return;         ((ApplicationBarIconButton)ApplicationBar.Buttons[0]).IsEnabled = !String.IsNullOrEmpty(_twitterSettings.AccessToken) && !String.IsNullOrEmpty(_twitterSettings.AccessTokenSecret);
    }





Now run the app.  If the “Tweet” button is still disabled  on the “new tweet” page, go back to the main page and ensure you hit “Account Settings” to authenticate your Twitter account.  Note:  every time you close the emulator down the Isolated Storage is wiped out.  You’ll have to re-authenticate every time you reload the emulator.  However, if you just exit the app and don’t close the emulator you won’t have to do this. 
Once you’re authenticated you should get this screen.  The “Tweet” button however still won’t do anything, we’ll tackle that next.
image
When we hit the “Tweet” button, we’re going to want a progress bar to show something is going on. In the TweetPage.xaml above the “TitlePanel” I’ve added a ProgressBar.  I’ve set the “Visibility” property to “Collapsed” so it is off by default.  For performance reasons you should also set “IsIndeterminate” to “False” when it’s not visible,
<!--TitlePanel contains the name of the application and page title-->
<ProgressBar x:Name="ProgressBar" VerticalAlignment="Top" IsIndeterminate="False" Visibility="Collapsed"/>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
    <TextBlock x:Name="ApplicationTitle" Text="Twitt" Style="{StaticResource PhoneTextNormalStyle}"/>
    <TextBlock x:Name="PageTitle" Text="new tweet" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
I’m now going to create a “TwitterHelper” class to wrap all of our calls to Twitter.  Under the “Common” folder create a new class (Right click folder, “Add” –> “Class”:
image
Here’s the code for our “TwitterHelper” class:
using System;
using System.Net;
using System.Windows;
using Hammock;
using Hammock.Authentication.OAuth;
using Hammock.Web; namespace Twitt.Common
{
    public class TwitterHelper
    {
        private readonly TwitterAccess _twitterSettings;
        private readonly bool _authorized;
        private readonly OAuthCredentials _credentials;
        private readonly RestClient _client;
        public event EventHandler TweetCompletedEvent;
        public event EventHandler ErrorEvent;         public TwitterHelper(TwitterAccess settings)
        {
            _twitterSettings = settings;             if (_twitterSettings == null || String.IsNullOrEmpty(_twitterSettings.AccessToken) ||
               String.IsNullOrEmpty(_twitterSettings.AccessTokenSecret))
            {
                return;
            }             _authorized = true;             _credentials = new OAuthCredentials
            {
                Type = OAuthType.ProtectedResource,
                SignatureMethod = OAuthSignatureMethod.HmacSha1,
                ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader,
                ConsumerKey = TwitterSettings.ConsumerKey,
                ConsumerSecret = TwitterSettings.ConsumerKeySecret,
                Token = _twitterSettings.AccessToken,
                TokenSecret = _twitterSettings.AccessTokenSecret,
                Version = TwitterSettings.OAuthVersion,
            };             _client = new RestClient
            {
                Authority = "http://api.twitter.com",
                HasElevatedPermissions = true
            };
        }         public void NewTweet(string tweetText)
        {
            if (!_authorized)
            {
                if (ErrorEvent != null)
                    ErrorEvent(this, EventArgs.Empty);
                return;
            }             var request = new RestRequest
            {
                Credentials = _credentials,
                Path = "/statuses/update.json",
                Method = WebMethod.Post
            };             request.AddParameter("status", tweetText);             _client.BeginRequest(request, new RestCallback(NewTweetCompleted));
        }         private void NewTweetCompleted(RestRequest request, RestResponse response, object userstate)
        {
            // We want to ensure we are running on our thread UI
            Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    if (response.StatusCode == HttpStatusCode.OK)
                    {
                        if (TweetCompletedEvent != null)
                            TweetCompletedEvent(this, EventArgs.Empty);
                    }
                    else
                    {
                        if (ErrorEvent != null)
                            ErrorEvent(this, EventArgs.Empty);
                    }
                });
        }
    }
} I’ll walk through this code now: In the constructor, we take in the TwitterAccess object.  This was already loading in our “TweetPage” so we will pass it through.  This object contains the User Tokens.  If the tokens were passed in successfully we set an “_authorized” member variable to true.  This will be used to ensure we’ll  have stored credentials when we make calls to the class methods.  The constructor also sets up the “_credentials” and “_client” objects as they are needed for all authorized calls to Twitter. Next is the “NewTweet” method.  This is the method we’ll call when the user presses the “Tweet” button.  It will first check that we are authorized and then make the call.  Last in this file, is the “NewTweetCompleted” Callback method, which is called when the Twitter call is completed.  We need to check if the call was successful or not.  I set up two different Events to fire back, one for a successful call and one for a failed call. All we have left is the “TweetPage.xaml.cs” code that makes this call and hooks up the two event handlers.  Here is the code:
  private void SaveClick(object sender, EventArgs e)
{
    PostTweet();
} … private void PostTweet()
{
    if (String.IsNullOrEmpty(TweetTextBox.Text))
        return;     ProgressBar.Visibility = Visibility.Visible;
    ProgressBar.IsIndeterminate = true;     var twitter = new TwitterHelper(_twitterSettings);     // Successful event handler, navigate back if successful
    twitter.TweetCompletedEvent += (sender, e) =>
        {
            ProgressBar.Visibility = Visibility.Collapsed;
            ProgressBar.IsIndeterminate = false;
            if (NavigationService.CanGoBack)
                NavigationService.GoBack();
        };     // Failed event handler, show error
    twitter.ErrorEvent += (sender, e) =>
        {
            ProgressBar.Visibility = Visibility.Collapsed;
            ProgressBar.IsIndeterminate = false;
            MessageBox.Show("There was an error connecting to Twitter");
        };     twitter.NewTweet(TweetTextBox.Text);
}








The first thing this method does is turns on the ProgressBar by setting the “Visibility” property to “Visible” and also turns on the “IsIndeterminate” property to “true”.  We then setup the two callback event handlers (one for success and one for failed calls).  Both will turn the progressbar off.  Failed calls will also display an error message.  Successful calls will navigate back to the previous page. That’s it!  We can now tweet from the app. Next Part of this series I’ll show how to get friend statuses, mentions, direct messages and favorites. You can find the source code for part 3 here: http://twitt.codeplex.com/  Sam






































11 comments:

  1. Great series, complete with all the little details of a professional app. The link for source code takes me to my Live home page after a redirect.

    ReplyDelete
  2. very interesting and well explained. a side note: when I tried to download the code I found myself on my live page, could not reach the url you provided.

    ReplyDelete
  3. Brilliant article. Thanks so much for doing this. And... like others here the download link is not working. Is there any chance you might be able to fix that? Very much appreciated :-)

    ReplyDelete
  4. A little digging around... and the following link works for me (not sure why ?client=wnf works)

    http://cid-229b74769854b9a9.office.live.com/self.aspx/Twitt/Twitt%20Part%203.zip?client=wnf

    ReplyDelete
  5. I'm about to post part 4, I think i'll look at using CodePlex for the code instead of windows live.

    ReplyDelete
  6. The source code can now be found at: http://twitt.codeplex.com/

    ReplyDelete
  7. Very helpful post! Thanks.

    It would also be helpful to know how to post an update with an image. I've been looking at this documentation
    https://dev.twitter.com/docs/api/1/post/statuses/update_with_media
    and your code, but I can't figure it out. I have to say I'm not familiarized with REST. So any guidelinens in this way would be great!

    Thanks for such interesting posts!

    ReplyDelete
  8. hi,
    i have aproblem.I am not able to send the tweets as the button is disabled.I ve followed each step but still couldnt get the check button enabled.Can u pls help me out as soon as possible..
    Thanks in advance

    ReplyDelete
  9. hi,
    I have downloaded your code and executed it on Emulator, Authentication part is ok, but unable to post the tweet and also unable to get the messages. All time their is Error.

    Can you please help me out of this??

    ReplyDelete
  10. Hi mind_freak,
    Twitter retired some of their older 1.0 APIs, it seems the xml versions are no longer working.

    I wrote this 2 years ago with those. It's not a hard change however just use the json equivalent, so for posting a tweet use this:

    var request = new RestRequest
    {
    Credentials = _credentials,
    Path = "/statuses/update.json",
    Method = WebMethod.Post
    };

    Hope that helps.

    Sam

    ReplyDelete
  11. I tried the above and it still getting error, since the json output is different than xml.
    After changing the path to .json, iam getting the error at
    var xmlElement = XElement.Parse(response.Content);
    Can you please help me to solve it, Since i am trying a twitter app for wp7.

    ReplyDelete