The last post acted as a very good foundation for me to make changes that I am planning with the features required for the Twitter client application. Over the last week I made some more changes to the application. It would be difficult to cover all of them in one post. I’ll post them as smaller articles.
Some of the changes I did was to extract the single list box I was using so far for displaying the tweets into a user control. This was needed because I added the functionality for displaying the Mentions section along with the normal TimeLine. Anytime someone mentions the logged in user on Twitter you’ll get to see a tweet in a separate column called Mentions. The look and feel is the same as the Timeline column, only it filters the Mentions into a dedicated column. After this refactoring the application shows Tweets from the timeline as well as the Mentions.
Apart from that I added support for periodically checking for updates. I’ll cover this in the next post as its worth a dedicated blog post. One thing which I found on the iPhone Twitter client was the relative time display for the tweets. So I built a similar functionality in NGTweet as well.
Display Relative Time for Tweets
If you look at all the previous posts, I was displaying the date when a particular Tweet was created. This was just the date and the month. Say for example it was twitted on 1st of July, we would display 1 July. Rather than displaying just the date I wanted to display the relative time when it was twitted. Say for example 10 minutes before or 1 hour before or 1 day before etc. This relative time also needs to be updated periodically so that we get real time difference and not just first time when the data is fetched from the server. If you leave the application open and switch to some other screen come back later after 10 minutes we should be able to see the relative time updated instead of showing the stale time.
I had this thought of displaying the relative time since I started developing NGTweet. In one of the previous posts I had said I’ll discuss in detail the reason for having a view model at the Tweet level. This was one of the reason. Before I go into the details further, I’ll show a screenshot of the application so that it makes much more sense about what I am talking about.
We can see the time when the tweet was sent like the first one was sent 2 mins ago, 2nd one was 11 mins and so forth. There are multiple was of achieving this feature. Since our view is bound to a view model properties, simplest way is to compute this value based on the CreatedDate in the getter of the bound property. This works fine if you have to use it in only once place. But if you have the requirement to reuse this logic then its bit of a pain.
Bit about IValueConverter
Databinding in Silverlight comes to the rescue once again. We can use what is know as ValueConverter. A value converter is basically an adapter that does a two way conversion between one form of the entity to another. We had already used BooleanToVisibilityConverter in earlier posts to control the visibility of stack panel. Value converters implement an interface called IValueConverter which has 2 methods. Convert and ConvertBack. Convert method is used when the value is bound from the view model or source to the view. ConvertBack is used the other way round while binding value from view to the view model or source.
DateTimeToRelativeTimeConverter as an implementation of IValueConverter
In our case the CreatedDate is a read only field. Hence if you remember, I had changed the databinding for most of the properties to OneTime. The data is all read only and needs to be bound only once from the source. In this scenario where we need to display the relative time the one time binding will only work if we bind it once and leave it forever. This is not what I would like to see. I need the ability to show close to real time difference between a tweet was tweeted and the current time. This means not an update every millisecond but every few minutes.
In order to achieve this, I need to make a small change to the binding related to CreatedDate property. I’ll change it from OneTime mode to OneWay mode. This enables me to still keep it as a read only mode but the updates to the source data will be propagated to the UI layer automatically. We’ll see in a short while why this change was needed. Following is the code snippet showing this change
<TextBlock Text="{Binding CreatedDate, Mode=OneWay, Converter={StaticResource dateTimeToRelativeTimeConvertor}}"
TextAlignment="Right"
FontWeight="Thin"
FontStyle="Italic"
FontSize="10"
Grid.Row="0"
Grid.Column="2" />
As you can see above, I have removed the formatting of CreateDate which was being done previously using StringFormat attribute. Instead I have added a dateTimeToRelativeTimeConvertor to the binding expression. This is used as a static resource and needs to be defined in the user controls resources as follows
<UserControl.Resources>
<convertors:DateTimeToRelativeTimeConverter x:Key="dateTimeToRelativeTimeConvertor" />
</UserControl.Resources>
What happens with this change is, when the data is bound from the view model to the TextBlock element, the binding infrastructure figures out there is a converter associated with this binding. Before assigning the value to the text block, it calls the Convert method of the value converter. So lets have a look at the DatTimeToRelativeTimeConverter implementation
public class DateTimeToRelativeTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
DateTime createdDate;
DateTime.TryParse(value.ToString(), out createdDate);
TimeSpan timeDifference = DateTime.Now - createdDate;
if (timeDifference.TotalMinutes < 1)
{
return string.Format("{0} secs ago", Math.Round(timeDifference.TotalSeconds));
}
if (timeDifference.TotalMinutes > 1 && timeDifference.TotalMinutes < 60)
{
return string.Format("{0} mins ago", Math.Round(timeDifference.TotalMinutes));
}
if (timeDifference.TotalHours > 1 && timeDifference.TotalHours <= 24)
{
return string.Format("{0} hours ago", Math.Round(timeDifference.TotalHours));
}
if (timeDifference.TotalDays > 1 && timeDifference.TotalDays <= 7)
{
return string.Format("{0} days ago", Math.Round(timeDifference.TotalDays));
}
return createdDate.ToString("m");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
For the moment I have only implemented the Convert method. We convert the value which is passed by binding infrastructure to a DateTime object. The rest of the code is self explanatory. Based on the TimeSpan between the current time and the created time we display the appropriate value in terms of seconds, minutes, hours or days ago. I am not sure if anything beyond one week would be really interesting to be highlighted. So I just show the date on which it was twitted. This is what you get to see in the tweets under the Mentions heading in the above screen shot of the application.
This is one of the easiest way in Silverlight and WPF to convert values between different types. We can use the ConvertBack to convert a value from UI layer to a property of a different type in view model. I am sure we’ll come across that situation some time in future. So I’ll leave it for then instead of writing about it here.
Automatically update relative time at periodic interval
As discussed above, I didn’t want this relative time to be assigned once and remain as static value. It should be updated in real time. I decided to update it every minute. So I used a DispatcherTimer which comes as part of Threading in Silverlight. I can use normal windows timer as well. This works exactly the same way as normal windows timer. We need to specify the Interval and the callback method when the interval is elapsed.
private readonly DispatcherTimer _refreshTimer;
public TweeterStatusViewModel(NGTweeterStatus tweeterStatus)
{
_tweeterStatus = tweeterStatus;
_refreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMinutes(1) };
_refreshTimer.Tick += RefreshTimerTick;
_refreshTimer.Start();
}
I have created an event handler for the timers Tick event which fires when the timer has elapsed. Here is the event handling code.
private void RefreshTimerTick(object sender, EventArgs e)
{
RaisePropertyChanged("CreatedDate");
}
All it does is to raise the property changed notification. The binding and the converter take care of updating the user interface. This is why I had to change the binding in XAML from OneTime to OneWay. Now every time the property change is fired the value is refreshed in the UI.
Conclusion
In the past I have seen people utilizing data binding capabilities of client side technologies like WPF or Silverlight in not very efficient way. Many people tend to directly bind data from the service layer to UI controls. The disadvantage of this approach is that we need to transfer all the data from Service layer to the client in the specific format which is required by the GUI. Instead we can utilize the computing power of client and build different user interfaces for the same service.
Think of Twitter. There is only one central Twitter service. But there are so many different clients each of which use the same service but present the data in completely different fashion to the end user. Take example of Sesmic Desktop and TweetDeck which are two commonly used clients for Twitter. The look and feel of both of them is very different, yet they depend on the same service.
That is the beauty of Service Oriented Architecture (SOA). Services need to be multi tenanted. The job of a service is to provide business functionality and not worry about the presentation of the data. If the service starts to cater to individual clients then it doesn’t remain a service. It becomes an application itself. Imagine if Twitter had to know how Sesmic and TweetDeck are displaying the data. The moment another client comes up with the slightest of customization twitter would have to modify itself which could have an adverse effect on existing clients.
I remember once designing a service contract which exposed a business entity. Internally the business entity was composed of smaller units which were displayed by splitting a computed text field. Say for e.g. the service was returning “ABCD01234”. In the UI this needed to be displayed as ABCD 12 & 34 in 3 different fields. It was purely from the UI point of view that this split was required. The persistent layer stored it as one single field and also in domain specific terms it was an single entity. The developer working on this functionality requested me to split this into multiple fields so that it could be data bound easily. Finally after a healthy discussion we agreed that the service should not split the fields and it should be handled in the UI layer as it was a view specific concern. This kind of situation is tailor made for a view model based approach that greatly helps in separating different concerns in the right manner.
As always the complete working solution is available for download.
Until next time Happy Programming
No comments:
Post a Comment