MVVM Gone Reactive: Creating a WPF Twitter client with ReactiveUI

In the last article you have seen an introduction to reactive extensions and how it can simplify programming, especially when you want to observe data that comes to you. As an example, you can be observing events or notifications, periodic information and so on. You have seen that you can combine different sources of information using LINQ operators and obtain new information from it.

And how do this relate to the UI of an application? There is a lot of relation, for example – the UI sends asynchronous signals to the back end (mouse events, keystroke events, change of data, commands activated, etc), that can be observed in a detached way, so you can separate the UI and the processing of the signals, thus getting a more testable program and (this is very important nowadays) a backend that is not tied to a specific UI, thus making easier to develop cross-platform applications.

The MVVM pattern

Working with XAML and C#, one thing that comes to mind is the Model-View-ViewModel (MVVM) pattern, developed by Microsoft architects and based on the Model-View-Presenter (MVP) pattern, that takes advantages of the features of the XAML platform, introduced with WPF. With it, you take advantage of Data Binding and commands to facilitate separating the UI from the business logic, using three layers:

Model – This is the data layer, where the data comes. It can be any kind of data, like POCO (Plain Old CLR Object) classes, database data, REST objects and so on. These classes usually don’t have any special treatment and can be shared by many applications

View – This is the presentation layer, where the user interacts with data: data entry, buttons, lists are part of the view layer

ViewModel – This layer makes the mediation between the view and the data. ViewModels change the data in a way that it can be shown in the View and receive the updates from the UI and pass them to the model. All this is done using the XAML infrastructure, mainly Data Binding and commands. The ViewModel is not tied to the View, so it can be completely testable and can be used in multi-platform programs (a single ViewModel can serve Views for different platforms). On the other side, the View usually isn’t tied to the ViewModel, so you can have many Views for the same ViewModel or many ViewModels for the same View. The Model also isn’t tied to the ViewModel, so it can be used independently of it (in fact, many developers create their models in an independent assembly, to emphasize decoupling).

Reactive UI

Then comes Reactive UI. It brings Reactive Programming to the MVVM pattern with a “Reactive ViewModel”, where you have observable properties and commands and things work in a reactive way. This seems complicated, but an example will clarify things. Let’s create a small login that allows the user to login to an application:

In Visual Studio, create a new WPF program and add the ReactiveUI NuGet package to it. Then, we can create our model. Create a new folder and name it “Models”. In it, create a new class and name it “Login”. In it, add this code:

public class Login
{
    public string UserName { get; set; }
    public string Password { get; set; }

    public async Task<bool> DoLogin()
    {
        var validData = new Dictionary<string, string>()
        {
            {"john", "wayne"},
            {"robert", "deniro"},
            {"meryl", "streep"},
            {"julia", "roberts"},
            {"richard", "gere"},
            {"drew", "barrymore"}
        };
        if (string.IsNullOrWhiteSpace(UserName) || string.IsNullOrWhiteSpace(Password))
            return false;
        var userName = UserName.ToLowerInvariant();
        await Task.Delay(5000);
        return validData.ContainsKey(userName) && 
            validData[userName] == Password.ToLowerInvariant();
    }
}

This class has nothing too special: just two properties and a method to do the login. Notice that I’m adding a 5s delay to give the impression that there is a very hard processing underneath. The next step is to add the controls to the main window for the View. In MainWindow.xaml, add this code:


<Window x:Class="_1___Introduction.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="350">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="3*"/>
        </Grid.ColumnDefinitions>
        <TextBlock VerticalAlignment="Center" Margin="5" Grid.Column="0" Grid.Row="0" Text="User Name"/>
        <TextBox VerticalAlignment="Center" Margin="5" Grid.Column="1" Grid.Row="0" 
                 Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}" Height="30" VerticalContentAlignment="Center"/>
        <TextBlock VerticalAlignment="Center" Margin="5" Grid.Column="0" Grid.Row="1" Text="Password"/>
        <TextBox VerticalAlignment="Center" Margin="5" Grid.Column="1" Grid.Row="1" VerticalContentAlignment="Center"
                 Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Height="30"/>
        <Button VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="5" 
                Content="Sign In" Grid.Row="2" Grid.Column="1" Width="65" Height="30"
                Command="{Binding LoginCommand}" />
        <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#40000000" Grid.ColumnSpan="2" Grid.RowSpan="3"
                Visibility="{Binding IsBusy, Converter={StaticResource BooleanToVisibilityConverter}}"/>
    </Grid>
</Window>

This window has two textboxes to enter the username and password (I should use a PasswordBox for the password, but I’m making it a textbox, so I don’t have to work with SecureStrings), a button that will be used to proceed with the login, and a border that will be shown when the login is processing.

The next step is to a ViewModel that will interface between the Model and the View. Create a new folder and name it “ViewModels”. Add a new class to it and name it “LoginViewModel”. Add the following code to the class:

public class LoginViewModel : ReactiveObject
{
    private readonly Login _login;
    private bool _isBusy;
    private string _password;

    private string _userName;

    public LoginViewModel(Login login)
    {
        _login = login;

        var canLogin = this.WhenAnyValue(x => x.UserName, x => x.Password, x => x.IsBusy,
            (u, p, b) => !b && !string.IsNullOrEmpty(u) && !string.IsNullOrEmpty(p));
        
        LoginCommand = ReactiveCommand.CreateFromTask<string, bool>(_ => DoLogin(), canLogin);
        LoginCommand.Subscribe(CheckLogin);
        this.WhenAnyValue(x => x.UserName).Subscribe(n => _login.UserName = n);
        this.WhenAnyValue(x => x.Password).Subscribe(p => _login.Password = p);
    }

    public string UserName
    {
        get { return _userName; }
        set { this.RaiseAndSetIfChanged(ref _userName, value); }
    }


    public string Password
    {
        get { return _password; }
        set { this.RaiseAndSetIfChanged(ref _password, value); }
    }

    public bool IsBusy
    {
        get { return _isBusy; }
        set { this.RaiseAndSetIfChanged(ref _isBusy, value); }
    }

    public ReactiveCommand<string, bool> LoginCommand { get;  }

    private async Task<bool> DoLogin()
    {
        IsBusy = true;
        return await _login.DoLogin();
    }

    private void CheckLogin(bool b)
    {
        IsBusy = false;
    }
}

The ViewModel is derived from ReactiveObject, which will implement the INotifyPropertyChanged interface to notify changes. The core of the ViewModel is in the constructor, where you set the commands and the subscriptions. In this ViewModel we have created a canLogin Observable, that will emit a value every time the UserName, Password or IsBusy has changed. It will emit a true value when IsBusy is false and both the UserName and Password have values. Then we create a LoginCommand, that will execute DoLogin when triggered and will only be active when canLogin emits True.

The last part of the constructor will update the model every time UserName or Password changes. In Mainpage.xaml.cs, we link the view to the ViewModel:

public MainWindow()
{
    InitializeComponent();
    DataContext = new LoginViewModel(new Login());
}

Now, if you run the program, you will see that the SignIn button is only available when you fill something on both boxes. If you click the SignIn button, the border covers the window until the login is finished. We will create a subscription for canLogin, so we can see the results every time something happens:

public LoginViewModel(Login login)
{
    _login = login;

    var canLogin = this.WhenAnyValue(x => x.UserName, x => x.Password, x => x.IsBusy,
        (u, p, b) => !b && !string.IsNullOrEmpty(u) && !string.IsNullOrEmpty(p));
    canLogin.Subscribe(r =>
    {

    });
    LoginCommand = ReactiveCommand.CreateFromTask<string, bool>(_ => DoLogin(), canLogin);
    LoginCommand.Subscribe(CheckLogin);
    this.WhenAnyValue(x => x.UserName).Subscribe(n => _login.UserName = n);
    this.WhenAnyValue(x => x.Password).Subscribe(p => _login.Password = p);
}

Add a breakpoint in the closing brace for the subscription and run the program. When the program breaks, you can use OzCode’s magic wand to create a tracepoint with this value: Result = {r}:

 

You can add tracepoints at the setters for UserName, Password and IsBusy with these values:

  • UserName new value = {value}
  • Password new value = {value}
  • IsBusy new value = {value}

Then, run the program again and fill the values until the button is enabled and click the button, so the login is triggered. After that, you can end the program and open the tracepoint window by clicking the number of trace messages in the bottom of Visual Studio window.

 

You will see something like the figure below.

 

In the first line, I am still entering the UserName and canLogin emits False. As soon as I enter the first letter in the password (fourth line), canLogin starts emitting True and the button becomes active. In the fourth line counting from the bottom, I click on the SignIn button and IsBusy becomes true, thus making canLogin to emit False. When the login is completed, IsBusy is set to False and canLogin emits True again. As you can see, everything was set up in the constructor and the observables emit the correct values while things are happening in the UI. Very cool, no?

Now we can create the program that works for us (ops, out fictitious character) when procrastination mode is on: the automatic Twitter updater.

Automatic Twitter updater

The first step to create a Twitter client is to get an application key in the Twitter dev site: http://apps.twitter.com. There, you can register your application and get two keys: the consumer key and consumer secret:

 

With that, we can start creating our Twitter app. Create a new WPF app in Visual Studio and call it “ReactiveTwitter”. In the Solution Explorer, right-click in the References node and add ReactiveUi and Tweeinvi. Tweetinvi (https://github.com/linvi/tweetinvi) is an open source library to access the Twitter API.

Once you have the Consumer Key and Secret, you must authenticate the user, so he can give access to the application. This can be done in two ways:

  • You call an authentication web page, where the user gives access to the app and then Twitter will redirect to a page with the client’s key and secret. This is more useful for web applications, where the redirect page is in your application
  • For desktop applications, the best way to authenticate is to open a web page where the user gives access to the app and gets a pin code. This pin code is entered in the app and, with that pin code, the app can get the client’s key and secret.

These two keys must be stored in the app, so the user doesn’t need to authenticate again each time he enters the app. All these steps will be done in a new class, named TwitterAuthenticator. In the app, create a new folder, named “Model” and in it, add a new class named “TwitterAuthenticator”. The class is something like this:


public class TwitterAuthenticator
{
    private static IAuthenticationContext _authenticationContext;
    const string CreadentialsFileName = "ReactiveTwitter.xml";

    public static bool AuthenticateUser()
    {
        var consumerKey = ConfigurationManager.AppSettings["ConsumerKey"];
        var consumerSecret = ConfigurationManager.AppSettings["ConsumerSecret"];
        var userCredentials = GetCredentials();
        if (userCredentials != null)
        {
            Auth.SetUserCredentials(consumerKey, consumerSecret,
                userCredentials.AccessToken, userCredentials.AccessTokenSecret);
            return true;
        }
        var appCredentials = new TwitterCredentials(consumerKey, consumerSecret);

        _authenticationContext = AuthFlow.InitAuthentication(appCredentials);

        Process.Start(_authenticationContext.AuthorizationURL);
        return false;
    }

    public static void CreateAndSetCredentials(string pinCode)
    {
        var userCredentials = AuthFlow.CreateCredentialsFromVerifierCode(pinCode, _authenticationContext);

        Auth.SetCredentials(userCredentials);
        SaveCredentials(userCredentials);
    }

    private static ITwitterCredentials GetCredentials()
    {
        string settingsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
        if (!Directory.Exists(settingsDirectory))
            Directory.CreateDirectory(settingsDirectory);
        string credentialsFile = Path.Combine(settingsDirectory, CreadentialsFileName);
        if (!File.Exists(credentialsFile))
            return null;
        var credentialsDoc = XDocument.Load(credentialsFile);
        if (credentialsDoc.Root == null)
            return null;
        return new TwitterCredentials("", "", credentialsDoc.Root.Element("AccessToken")?.Value, 
            credentialsDoc.Root.Element("AccessSecret")?.Value);
    }

    public static void SaveCredentials(ITwitterCredentials credentials)
    {
        string settingsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
        if (!Directory.Exists(settingsDirectory))
            Directory.CreateDirectory(settingsDirectory);
        string credentialsFile = Path.Combine(settingsDirectory, CreadentialsFileName);
        XDocument credentialsDoc = new XDocument(
            new XElement("Credentials",
                new XElement("AccessToken", credentials.AccessToken),
                new XElement("AccessSecret", credentials.AccessTokenSecret)));
        credentialsDoc.Save(credentialsFile);
    }
}

The first method, AuthenticateUser, will do the first step of the authentication: with the Consumer key and secret, it will get the url to get the authorization and open it. Before that, the method checks if there are any saved credentials (in my case, I saved them as plain text in a xml file, but you should encrypt this in real usage). If there are any saved credentials, the app will use them to login and the method will return. If there aren’t any saved credentials, the method will show the authorization page.

The second method, CreateAndSetCredentials, will get the pin code that was given to the user and use it to create new credentials and save them in the xml file.

The main ViewModel will do all the process of authentication and will get all the data. In the project, create a new folder and name it “ViewModels” and add a new class to it and name it “MainViewModel”. The class should be like this:


public class MainViewModel : ReactiveObject
{
    private string _userName;
    private string _userPicture;
    private IEnumerable<ITweet> _tweets;
    private bool _isGettingPin;
    private string _pinValue;

    public MainViewModel()
    {
        var authObs = Observable.Start(TwitterAuthenticator.AuthenticateUser);
        authObs.Subscribe(logged =>
        {
            if (!logged)
                IsGettingPin = true;
            else
                SetAuthenticatedUser(User.GetAuthenticatedUser());
        });
        ConfirmPinCommand = ReactiveCommand.Create(DoConfirmPin);
        CancelPinCommand = ReactiveCommand.Create(DoCancelPin);
    }

    private void DoCancelPin()
    {
        IsGettingPin = false;
    }

    private void DoConfirmPin()
    {
        IsGettingPin = false;
        TwitterAuthenticator.CreateAndSetCredentials(PinValue);
        var user = User.GetAuthenticatedUser();
        SetAuthenticatedUser(user);
    }

    private void SetAuthenticatedUser(IAuthenticatedUser u)
    {
        UserName = u.Name;
        UserPicture = u.ProfileImageUrl400x400;
        Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(15)).Subscribe(_ =>
            Tweets = u.GetHomeTimeline(100));
    }

    public string UserName
    {
        get { return _userName; }
        set { this.RaiseAndSetIfChanged(ref _userName, value); }
    }

    public string UserPicture
    {
        get { return _userPicture; }
        set { this.RaiseAndSetIfChanged(ref _userPicture, value); }
    }

    public string PinValue
    {
        get { return _pinValue; }
        set { this.RaiseAndSetIfChanged(ref _pinValue, value); }
    }
    public bool IsGettingPin
    {
        get { return _isGettingPin; }
        private set { this.RaiseAndSetIfChanged(ref _isGettingPin, value); }
    }

    public IEnumerable<ITweet> Tweets
    {
        get { return _tweets; }
        private set { this.RaiseAndSetIfChanged(ref _tweets, value); }
    }

    public ReactiveCommand ConfirmPinCommand { get; }
    public ReactiveCommand CancelPinCommand { get; }
}

The ViewModel starts creating an observable that will call AuthenticateUser and will return if the user was already logged. If the user is logged, it will initialize the user data and will start getting the user’s timeline. If not, it will set IsGettingPin to true and then it will initialize the two commands needed for getting the pin value.

SetAuthenticatedUser will set the user data and create a new observable that will fire every 15 seconds and will get the user timeline. Presto! No need to refresh anymore!

The main window XAML is like this one:


<Window x:Class="ReactiveTwitter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="800" Width="1000">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>
    <Grid>
      <Grid.RowDefinitions>
          <RowDefinition Height="60"/>
          <RowDefinition Height="*"/>
      </Grid.RowDefinitions> 
        <StackPanel HorizontalAlignment="Right" Margin="10,0" VerticalAlignment="Center" Orientation="Horizontal">
            <TextBlock Text="{Binding UserName}" Margin="10,0" VerticalAlignment="Center" FontWeight="Bold"/>
            <Image Width="50" Height="50" Source="{Binding UserPicture}"/>
        </StackPanel>
        <ListBox Grid.Row="1" ItemsSource="{Binding Tweets}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" />
        <Grid Grid.Row="0" Grid.RowSpan="2" Visibility="{Binding IsGettingPin, Converter={StaticResource BooleanToVisibilityConverter}}">
            <Grid Background="Black" Opacity="0.5"/>
            <Border
            MinWidth="250"
            Background="DarkCyan" 
            BorderBrush="Black" 
            BorderThickness="1" 
            HorizontalAlignment="Center" 
            VerticalAlignment="Center">
                <StackPanel>
                    <TextBlock Margin="5" Text="Pin Value:" FontWeight="Bold"  />
                    <TextBox MinWidth="150" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding PinValue}"/>
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                        <Button Margin="5" Content="Ok" Width="65" Command="{Binding ConfirmPinCommand}"/>
                        <Button Margin="5" Content="Cancel" Width="65" Command="{Binding CancelPinCommand}" />
                    </StackPanel>
                </StackPanel>
            </Border>
        </Grid>
    </Grid>
</Window>

This window has two parts: the second part is a grid used to get the pin number. It has a textbox and two buttons, where you can confirm or cancel the pin number. This grid is only visible when IsGettingPin is true. That why we set IsGettingPin to true when the user is not logged in and reset it to false when the user clicks any of the buttons. The first part is where the data will be shown: the logged user name and image and his timeline. The user’s data is filled at start and the tweet list is filled every 15 seconds.

We only need to link the ViewModel to the view in Mainpage.xaml.cs:

public MainWindow()
{
    InitializeComponent();
    DataContext = new MainViewModel();
}

Now, when we run the program, we get something like this:

And a web page will open, so you can authorize the app:

Once you authorize it, a pin number will appear in the screen:

You must type this number in the main screen of the app and you are all set. The tweets should be shown in the main screen:

This is not the best visualization for the tweets. It is showing the ToString evaluation for the Tweet class. Let’s make it better by adding a data template. Let’s see what members should be added. To do that, set a breakpoint in the line Tweets = u.GetHomeTimeline(100). When the program breaks, we can analyze the tweets with OzCode’s Reveal feature:

There, we can select the data we want:

With this info, we can create a data template for the items:

<ListBox.ItemTemplate>
    <DataTemplate>
        <Grid Margin="5" TextElement.FontSize="14">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="60"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Image Source="{Binding CreatedBy.ProfileImageUrl400x400}" Margin="5" Height="50" Width="50"/>
            <Grid Grid.Column="1">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding CreatedBy.Name}" Margin="0,0,10,0" FontWeight="Bold"/>
                    <TextBlock Text="@"/>
                    <TextBlock Text="{Binding CreatedBy.ScreenName}" Margin="0,0,10,0"/>
                    <TextBlock Text="{Binding CreatedAt}" Margin="0,0,10,0"/>
                </StackPanel>
                <TextBlock Grid.Row="1" Text="{Binding Text}" TextWrapping="Wrap"/>
            </Grid>
        </Grid>
    </DataTemplate>
</ListBox.ItemTemplate>

We will add the image, name and screen name of the sender, the creation date and the text. When we add this data template to the listbox, we get something better:

Conclusions

As you can see, using ReactiveUI makes it very simple to make your ViewModels reactive, with very few code you can create a program that shows your timeline, refreshing it every 15 seconds with no need of manual refresh. Problem solved! Now, back to work!

The full source code for the project is at https://github.com/bsonnino/ReactiveTwitter