SilverTweet – Building a Silverlight Twitter client part 4

And once again I’m back on my computer with Live Writer and Visual Studio open. I thought it was time to give you the 4th part of the SilverTweet series. If you have missed part 1, part 2 and/or part 3, I recommend heading back and looking through them.

If someone would have told me a year ago that I would be so obsessed with my work, that I would be sitting on a plane at 07:20 in the morning writing a blog entry, I would probably have said that they must have me confused with someone else. Software development is just a job, it isn’t my life… Well, I guess I care more about it than I would like to admit…

Anyhow, in this part of the blog series, I’m going to go on and show you the rest of the models/viewmodels used in the SilverTweet application.

In the previous part, I talked about the MainModel class. In this class, the first things I would do was to check for user credentials. If none were found, would create a new instance if the LoginModel class and set that as the CurrentView. So I guess the next logical step would be to have a look at that class. The LoginModel class is responsible for handling the logging in to the application.

The logging in is done by having the user visit the Twitter site, using a specific URL, get a PIN code and then pass this PIN code to the application. All of this was covered in step 1. So if you don’t remember (I understand if you don’t. I have the memory of a goldfish), I would recommend going back and having a look at it. So the first thing that the LoginModel has to do is run of and get hold of that specific URL. The actual work here is done by the services created previously, so the LoginModel only has to call the service and wait for the response.

public class LoginModel : ModelBase
{
AuthToken _authToken;

public LoginModel()
{
GetAuthorizationUrl();

...
}

private void GetAuthorizationUrl()
{
IsGettingAuthorizationURL = true;
ITwitterService svc = ServiceLocator.GetService<ITwitterService>();
svc.GetAuthorizationUrlCompleted += GetAuthorizationUrlCompleted;
svc.GetAuthorizationUrlAsync();
}

void GetAuthorizationUrlCompleted(object sender, GetAuthorizationUrlCompletedEventArgs e)
{
((ITwitterService)sender).GetAuthorizationUrlCompleted -= GetAuthorizationUrlCompleted;
IsGettingAuthorizationURL = false;
if (e.Error != null || e.Result == null)
{
CommunicationError = "Error communicating with Twitter. Please try reloading the application...";
return;
}
AuthorizationURL = e.Result.Url ;
_authToken = e.Result.Token;
}
}

 

As you can see, the functionality isn’t that complicated. But there are a few little bits and pieces that might need some explaining. First off, there is some property called IsGettingAuthorizationURL. This is just a “status property” that tells any potential view that the model is busy fetching the URL. Since all webservice calls in Silverlight are asynchronous, this is a pretty helpful thing to have. An asynchronous call over the internet can tale a while to process, and by having this flag, it makes it possible for the view to handle this by showing some form of loading symbol or something.

The other two properties being used here are CommunicationError and AuthorizationURL. The functionality of the AuthorizationURL is pretty obvious. It will provide the view with the URL to the authorization page. The CommunicationError property is also pretty easy to grasp. It is just a crude and simple way to handle errors. The LoginModel has two potential errors that needs handling by the client. The first one is an error while trying to communicate with the Twitter server. The other one is an error while trying to exchange the AuthToken for an AccessToken. Due to this, the LoginModel class has to make it possible for the view to get hold of these, which is done by exposing the error messages through the CommunicationError property and a property called VerificationError. 

string _communicationError;
string _verificationError;
bool _isGettingAuthorizationURL;

public string AuthorizationURL
{
get { return _authorizationURL; }
private set
{
_authorizationURL = value;
OnPropertyChanged("AuthorizationURL");
}
}
public bool IsGettingAuthorizationURL
{
get { return _isGettingAuthorizationURL; }
private set
{
_isGettingAuthorizationURL = value;
OnPropertyChanged("IsGettingAuthorizationURL");
}
}
public string CommunicationError
{
get { return _communicationError; }
private set
{
_communicationError = value;
OnPropertyChanged("CommunicationError");
}
}
public string VerificationError
{
get { return _verificationError; }
private set
{
_verificationError = value;
OnPropertyChanged("VerificationError");
}
}

Once the user has used the AuthorizationURL to go to the Twitter server and gotten a PIN code, this PIN code, together with the AuthToken, has to be exchanged for an AccessToken. And as soon as we are saying that some code execution in the viewmodel has to be initiated by the user, we know that we need an command. Since Silverlight has an ICommand interface, but no implementations of it, I have to create one. I’m actually create one that I use in most of my projects. I should probably include it in my CommandManager assembly considering how often I write this code. It has evolved slightly over time, but not much. It looks like this

public class DelegateCommand<T> : ICommand
{
CommandDelegate<T> _delegate;

public DelegateCommand(CommandDelegate<T> d)
{
_delegate = d;
}

public bool CanExecute(object parameter)
{
return true;
}

public event EventHandler CanExecuteChanged;

public void Execute(object parameter)
{
_delegate.Invoke((T)parameter);
}
}

public delegate void CommandDelegate<T>(T parameter);

 

So it is, as you can see, basically just an ICommand implementation that forwards the actual execution to a delegate. This makes it very simple for me to create an ICommand that executes code in my viewmodel instead of having to write new, specialized commands for each function that needs to be exposed. Once I’ve got the DelegateCommand<T> class up and running I will start using it in my LoginModel. The LoginModel is going to expose a single ICommand property called VerifyPin tat will be declared and set in the constructor. It looks like this

public LoginModel()
{
GetAuthorizationUrl();

VerifyPin = new DelegateCommand<string>(delegate(string pin)
{
if (pin.Length != 7)
{
VerificationError = "The PIN does not seem to be the right length." + Environment.NewLine + "Please try again!";
return;
}
VerificationError = null;
ITwitterService svc = ServiceLocator.GetService<ITwitterService>();
IsGettingAccessToken = true;
svc.GetAccessTokenCompleted += GetAccessTokenCompleted;
svc.GetAccessTokenAsync(pin, _authToken);
});
}

public ICommand VerifyPin
{
get;
private set;
}

As you can see, I use an anonymous method as the delegate and take in the PIN code as a string parameter. How this is passed to the model is up to the view, I’m only opening up the possibility to pass it to the viewmodel. Next I just verify that the PIN is 7 characters. It is really unnecessary to run of on an asynchronous call if the PIN is obviously wrong. I could of course also check that the PIN is only numbers. But for now, the 7 characters check is good enough. If it doesn’t fulfill this requirement, I set the VerificationError property and return. Otherwise, I set the VerificationError to null, new up a ITwitterService and call the GetAccessTokenAsync method. The GetAccessTokenCompleted event is also wired up to a method called GetAccessTokenCompleted. And yeah…the IsGettingAccessToken is set to true, making it possible for the view to inform the user that it is busy fetching the AccessToken. It should probably say something less confusing to the client though…very few clients will know what an AccessToken is…

The final thing to do in the LoginModel is to handle the GetAccessTokenCompleted event. The handler for this event looks like this

void GetAccessTokenCompleted(object sender, GetAccessTokenCompletedEventArgs e)
{
((ITwitterService)sender).GetAccessTokenCompleted -= GetAccessTokenCompleted;
IsGettingAccessToken = false;
if (e.Error != null)
{
AuthorizationURL = null;
CommunicationError = "Error communicating with Twitter. Please try reloading the application...";
return;
}
else if (e.Result == null)
{
VerificationError = "Could not verify PIN. Please try again...";
return;
}
OnLogInCompleted(e.Result);
}

 

It starts off by detaching the handler from the service object and then goes on to set the IsGettingAccessToken to false and then verify the response. If there is a communication error, the AuthorizationURL is set to null and the CommunicationError property is set to a generic error. In a real world application, this should probably be handled in a better way, but this will suffice for now. If the Result property is null, then the service didn’t manage to get an AccessToken, which points towards an invalid PIN. And finally, if everything seems ok, I call a method called OnLogInCompleted which will raise an event called LogInCompleted.

protected virtual void OnLogInCompleted(AccessToken token)
{
if (LogInCompleted != null)
LogInCompleted(this, new PayloadEventArgs<AccessToken>(token));
}
public event EventHandler<PayloadEventArgs<AccessToken>> LogInCompleted;

So now you are probably wondering, or should probably be wondering why the ITwitterService won’t throw and exception if it can’t verify the PIN code. Well…I agree that it seems logical, even though throwing exceptions for “expected error” is debatable. But in this case, leaving the debate about “expected errors” on the sideline, there is another reason. When getting an exception from a service call, Silverlight only gets a generic http error (I think it is a 404). This makes it impossible for my code to figure out if there was a communication error or if the PIN code verification failed. So returning null if the verification failed, will make it possible for me to figure out what went wrong. Another solution would obviously have been to pass a “larger” object as a return from my service calls. That object could have a payload property with the response, but also properties for exceptions and so on. This is generally a good way to get around Silverlight’s somewhat limited exception handling in service calls. But I want to mention that it isn’t really Silverlight’s fault. It has to do with what the browser actually hands to the plug-in…I think…

So…that’s it for the LoginModel. Not that complicated. Once again, having all of the actual Twitter communication logic in the TwitterConnection project and wrapping it in services makes both the services very thin and simple, as well as the code in the Silverlight client.

The next model used in the MainModel is called CredentialVerificationModel. This model will be used when the client starts the application and already has an AccessToken stored in IsolatedStorage. This AccessToken must be verified before being used, since the user might have revoked the token, causing the application to have to get the user to login again.

This viewmodel looks a lot like the LoginModel. It has the same CommunicationError and VerificationError, but it also has a string property called Status. The Status property will basically expose the current status of the model. On top of these properties, it exposes 2 more properties of ICommand type. One called ReAuthenticate and one called VerifyCredentials. The VerifyCredentials command makes it possible to retry the verification if the communication fails, while the ReAuthenticate will cause the saved AccessToken to be deleted and the login process to be re-run. So the main part of it looks like this

public class CredentialVerificationModel : ModelBase
{
string _status;
string _verificationError;
string _communicationError;

public override string Name
{
get { return "CredentialVerification"; }
}
public string Status
{
get { return _status; }
set
{
_status = value;
OnPropertyChanged("Status");
}
}
public string CommunicationError
{
get { return _communicationError; }
set
{
_communicationError = value;
OnPropertyChanged("CommunicationError");
}
}
public string VerificationError
{
get { return _verificationError; }
set
{
_verificationError = value;
OnPropertyChanged("VerificationError");
}
}

public ICommand VerifyCredentials
{
get;
private set;
}
public ICommand ReAuthenticate
{
get;
private set;
}
}

So what is actually going to be happening in this model. Well, the main thing is that it should verify the credentials that have been pulled out from IsolatedStorage. But before I can do this, I need to wire up the ICommands. They are pretty simple and defined as DelegateCommand<T> in the constructor

public CredentialVerificationModel()
{
VerifyCredentials = new DelegateCommand<object>(delegate {
CommunicationError = null;
DoVerifyCredentials();
});

ReAuthenticate = new DelegateCommand<object>(delegate {
VerificationError = null;
OnVerificationFailed();
});
}

So when some view calls the VerifyCredentials command, the CommunicationError is set to null and then the DoVerifyCredentials method is called. If the a view calls the ReAuthenticate, the VerificationError is set to null and a method called OnVerificationFailed is called. The OnVerificationFailed method is responsible for raising one of two events in this model, the VerificationFailed event. The other event in the model is called VerificationComplete and has a corresponding OnVerificationComplete method. It looks like this

protected virtual void OnVerificationFailed()
{
if (VerificationFailed != null)
VerificationFailed(this, new EventArgs());
}
public event EventHandler VerificationFailed;

protected virtual void OnVerificationComplete()
{
if (VerificationComplete != null)
VerificationComplete(this, new EventArgs());
}
public event EventHandler VerificationComplete;

 

 

 

 

 

 

These events are then handled by the MainModel, enabling switches between views and things…

So, the final piece of the puzzle is obviously the DoVerifyCredentials method. It is not at all complicated. It sets the Status to a generic message saying that Twitter is being contacted. It then gets a reference to the ITwitterService and calls its VerifyCredentialsAsync method (after hooking up a handler for the VerifyCredentialsCompleted event).

private void DoVerifyCredentials()
{
Status = "Contacting Twitter to verify credentials!" + Environment.NewLine + "Please wait...";

ITwitterService svc = ServiceLocator.GetService<ITwitterService>();
svc.VerifyCredentialsCompleted += VerifyCredentialsCompleted;
svc.VerifyCredentialsAsync();
}

 

In the handler for the VerifyCredentialsCompleted event, I do the same thing as always. I detach the handler, check for errors and set the correct messages if needed. If there are no errors, I raise the VerificationComplete event by calling the OnVerificationComplete method

void VerifyCredentialsCompleted(object sender, VerifyCredentialsCompletedEventArgs e)
{
((ITwitterService)sender).VerifyCredentialsCompleted -= VerifyCredentialsCompleted;
if (e.Error != null)
{
Status = null;
CommunicationError = "Communication error!";
return;
}
else if (e.Result == null)
{
Status = null;
VerificationError = "Could not verify credentials!" + Environment.NewLine + "Please try re-authenticating...";
return;
}
OnVerificationComplete();
}

That’s it! That’s all there is to the CredentialVerificationModel. So now there is only one final viewmodel left, the TimelineModel. The TimelineModel is responsible for getting and handling any information that has to do with the users timeline. A timeline on Twitter is basically Twitter’s version of a feed.

The first thing to do in this model, is to wire up the “status properties”. This model will have a couple of things going on asynchronously, and these things need corresponding booleans indicating what is going on. So it needs the following properties IsLoadingTimeline, IsUpdatingTimeline and IsSendingTweet. I’m not going to show these, but they are basically public boolean properties with backingfields and property changed notification.

The model also exposes 3 other properties, Tweets, TweetText and Tweet. Tweets is a collection of Tweet objects in a TweetCollection. TwetText is a property that is used for sending a tweet. This will be bound TwoWay making it possible to read what the user wants to tweet as well as being able to empty the TextBox when the tweet is sent. And the Tweet property is an ICommand responsible for sending a tweet. Once again, these methods are boilerplate, so I won’t show them…

The constructor for this class sets up a few little bits and pieces. First off, it gets hold of an ITwitterService reference and Goes off to get the initial feed. Next, it creates a new DispatcherTimer that is set to have an interval of one minute. In the timers Tick event, it stops the timer and updates the timeline. And finally, the Tweet command is created.

public TimelineModel()
{
_svc = ServiceLocator.GetService<ITwitterService>();
GetTimeline();
_tmr = new DispatcherTimer() { Interval=TimeSpan.FromSeconds(60) };
_tmr.Tick += delegate { _tmr.Stop(); UpdateTimeline(); };

Tweet = new DelegateCommand<object>(delegate { TweetIt(); });
}

Let’s keep going and look at the GetTimeline method. It is once again not that complicated. It just sets the IsLoadingTimeline to true and then asks the ITwitterService for the 20 latest tweets in the feed. The completed handler is a little bit different though. It detaches the handler, checks for errors, creates a new TweetCollection from the result and starts the timer to initiate updates every minute. The thing that is different here compared to the previous service calls is the handling of any error. If any error occurs, it has a counter that is incremented. Not until 5 has failed more than 5 times is an actual error reported. Instead, it just re-runs the GetTimeline method for the first couple of failed attempts.

private void GetTimeline()
{
IsLoadingTimeline = true;
_svc.GetTimelineCompleted += GetTimelineCompleted;
_svc.GetTimelineAsync(null, 20);
}
void GetTimelineCompleted(object sender, GetTimelineCompletedEventArgs e)
{
_svc.GetTimelineCompleted -= GetTimelineCompleted;
IsLoadingTimeline = false;

if (e.Error != null)
{
_retries++;
if (_retries > 5)
{
throw new Exception("Problems communicating with Twitter");
}
GetTimeline();
return;
}
_retries = 0;

Tweets = new TweetCollection(e.Result,30);
_tmr.Start();
}

 

The sibling method, UpdateTimeline, is basically doing the same thing. These methods could probably be refactored into a united thing quite easily, but now they are like this… The only real difference in the code is that the UpdateTimelineCompleted adds any Tweet objects returned to the Tweets collection instead of creating a new collection

private void UpdateTimeline()
{
IsUpdatingTimeline = true;
_svc.GetTimelineCompleted += UpdateTimelineCompleted;
long? sinceId = null;
int? count = 20;
if (Tweets != null && Tweets.Count > 0)
{
sinceId = Tweets[0].ID;
count = null;
}
_svc.GetTimelineAsync(sinceId, count);
}
void UpdateTimelineCompleted(object sender, GetTimelineCompletedEventArgs e)
{
_svc.GetTimelineCompleted -= UpdateTimelineCompleted;
IsUpdatingTimeline = false;
if (e.Error != null)
{
_retries++;
if (_retries > 5)
{
throw new Exception("Problems communicating with Twitter");
}
UpdateTimeline();
return;
}
_retries = 0;
_tmr.Start();
Tweets.AddRange(e.Result);
}

Oh yeah…it also checks if there are any tweets in the collection already. If there is, it only requests tweets tweeted since the last fetch.

The final thing in this model is the TweetIt method. This method is going to take any text in the TweetText property and pass it to Twitter as a new Tweet. It starts out by verifying that there is text in the TweetText property. If there isn’t, there is no need to call on Twitter. If there is, it goes on to stop the update timer, sets the IsSendingTweet to true and then does a call to the ITwitterService instance. In the completed handler, the IsSendingTweet is set to false, and then the result is checked for errors. In this case, I actually ignore any error (this should be handled in a nice way and not ignored!). If there is no error, the TweetText property is emptied out and the UpdateTimeline method is called to update the timeline

private void TweetIt()
{
if (string.IsNullOrEmpty(TweetText))
return;
_tmr.Stop();
IsSendingTweet = true;
_svc.TweetCompleted += TweetItCompleted;
_svc.TweetAsync(TweetText);
}
void TweetItCompleted(object sender, TweetCompletedEventArgs e)
{
_svc.TweetCompleted -= TweetItCompleted;
IsSendingTweet = false;
if (e.Error != null)
{
return;
}
TweetText = "";
UpdateTimeline();
}

 

That’s actually it for that model as well. The models are not that complicated, and all of that service plumbing really pays off at this point (as I have mentioned over and over again). The time put into these models will pay off in the same way when building the UI…so bear with me…

This is it for now. The next, and hopefully last, part will be about the Xaml part of this application. Hope you return to read this part…

Cheers!

[UPDATE]
Part 5 is now available as well

Comments are closed