Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Building a Flickr Viewer App with BuildIt.Lifecycle

Last week at the Mobile .NET User Group I presented on the BuildIt.Lifecycle for the first time. The main motivation wasn’t to try and get people to start using it (although I’d definitely welcome feedback from anyone who does), it was really to try to get developers to start thinking differently about the way that they write applications. Instead of thinking of their application in terms of the apis that any one platform offer (and I treat XForms as just another platform, even though it sort of in-part aggregates iOS/Windows and Android) or in terms of raw navigation constructs (eg Activities/Intents, ViewControllers or Pages), but more in terms of states within their application.

Applications are littered with states, whether they be the states of a button (Normal, Focussed, Pressed, ToggledOn etc) or visual states of a Page (a Windows concept but you can think of visual states as being the different layouts of any given page/view of your application). In fact if you think of your application as a whole you realise that you can map the entire navigation within your application as just a series of state transitions, with your pages/views being the states that the application can be in. This is indeed the foundation of BuildIt.Lifecycle.

One other point I’ll make before jumping into building the Flickr Viewer application, which should demonstrate the core of the BuildIt.Lifecycle framework, is that most application frameworks, at least for mobile platforms, have been built around the notion of a single window or frame that houses the content that the user is currently looking at. As such most of the frameworks have some metaphor that maps to the application as a whole (often called the App or Application class), and is assumed to be a singleton. In exploring the use of states to model applications, it became evident that there are some platforms, such as Windows and Mac, where this assumption breaks down, and that in fact the framework needs an additional metaphor that maps to each window within an application. I’ve settled on Region for the timebeing, and there’s no surprises that there is a mapping between an instance of a Region in the framework to a Window presented at runtime. This should make more sense once we explore creating the application; alternatively there’s a post on working with Additional Windows using Regions with BuildIt.Lifecycle.

Let me start by giving an example of visual states, just to warm up and get us all thinking about using states to represent an application, or parts of the application. I’m going to start by creating a new Universal Windows Platform project, based on the Blank App (Universal Windows) project template, called FlickrViewer.

image

Next I’ll create another project, this time called FlickrViewer.Core, based on the Class Library (Portable) template, keeping the defaults in the Add Portable Class Library targets dialog.

image

To complete the setup of the projects, I just need to add a reference from the FlickrViewer UWP project to the FlickrViewer.Core PCL project.

image

The last reference I’m going to add initially is to the Microsoft.Net.Http and Newtonsoft.Json NuGet packages (Note that under the Install and Update Options the Dependency behaviour is set to Highest, and both packages are install into both projects).

image 

I’m going to add the FlickrService class (and corresponding IFlickrService interface) that’s listed at the end of this article, which I’ll use to download content from the public flickr feed, to the PCL. This application is going to consist of two pages, the first will display a list of images retrieved from the Flickr public feed; the second page will display the details of the image selected from the first page. I’ll add another page at this point called DetailsPage.xaml, based on the Blank Page item template.
Next, I’m going to switch across to Blend and create a couple of visual states: a Loading state, for when data is being loaded; and a Loaded state, for when data is available to be displayed on the screen. The following XAML for the MainPage includes a ListView, which will be visible once data has loaded (ie Loaded state), and a ProgressRing, which will be visible and active while data is being loaded (ie Loading state).

<Page x:Class="FlickrViewer.MainPage"
      xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:FlickrViewer"
      xmlns:d="
http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="MainStates">
                <VisualState x:Name="Loading">
                    <VisualState.Setters>
                        <Setter Target="progressRing.(UIElement.Visibility)" Value="Visible" />
                        <Setter Target="progressRing.(ProgressRing.IsActive)" Value="True" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Loaded">
                    <VisualState.Setters>
                        <Setter Target="listView.(UIElement.Visibility)" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <ListView x:Name="FlickrListView" Visibility="Collapsed">
          
<ListView.ItemTemplate>
                <DataTemplate>
                    <Grid Height="100" Margin="0,10">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="100" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <Image Source="{Binding media.m}"
                               Stretch="UniformToFill"
                               HorizontalAlignment="Center"
                               VerticalAlignment="Center" />
                        <TextBlock Text="{Binding title}"
                                   Grid.Column="1"
                                   TextWrapping="WrapWholeWords"
                                   FontSize="20"
                                   Margin="10,0"/>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <ProgressRing x:Name="LoadingProgress"
                      HorizontalAlignment="Center"
                      VerticalAlignment="Center"
                      Visibility="Collapsed"
                      Width="100"
                      Background="Red"
                      Height="100" />

    </Grid>
</Page>

When the MainPage loads, I’ll create an instance of the FlickrService and invoke the LoadPhotos method in order to retrieve data to be displayed on the MainPage.

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    VisualStateManager.GoToState(this, "Loading", true);
    var flickr = new FlickrService();
    var photos = await flickr.LoadPhotos();
    FlickrListView.ItemsSource = photos;
    VisualStateManager.GoToState(this, "Loaded", true);
}

Running the application at this point will display the progress ring while the data is being downloaded (Loading state), followed by the list of photos once the data has been retrieved (Loaded state).

image

So now that you’ve got the idea of visual states, let’s move on to talking about the application in terms of states. I’m going to use the BuildIt.Lifecycle framework, so I’ll start by adding references to the NuGet packages. Into the FlickrViewer project, I’m going to add the BuildIt.Lifecycle.UWP package; into the FlickrViewer.Core project, I’m going to add the BuildIt.Lifecycle package (there will be some refactoring coming which will consolidate these into a single package soon but for now there are platform specific packages to be added).

As with most frameworks I need to add a class to the PCL that will correlate to the application as a whole. There are a couple of different options available as base classes; in this case I’m going to pick the one that is already setup to be able to handle multiple regions, despite not actually requiring it for this application. Currently this base class does require the DefineApplicaitonRegions method to be defined, but in this case since we are only going to define a single region, PrimaryRegion, there is no need to do anything within this method. The type, PrimaryRegion, is provided as a generic argument when inheriting from the RegionAwareBaseApplication class, which registers and identifies the PrimaryRegion class as the first region of the application.

public class RootApplication : RegionAwareBaseApplication<PrimaryRegion>
{
    protected override void DefineApplicationRegions()
    {
    }
}

Next, I need to define the PrimaryRegion class, and for this I’m going have it inherit from the StateAwareApplicationRegion class. It’s possible to define regions of the application that aren’t state aware, meaning that the developer has to provide some other mechanism for navigating between pages/views. In this case, we want to think of our application in terms of states. Rather than thinking about the navigation through our application as going from MainPage to the DetailsPage, I’ll define two states, Main and Details, between the application will transition. These states are easily defined as enum values, and used within the PrimaryRegion to define what makes up each state:

public class PrimaryRegion : StateAwareApplicationRegion
{
    public enum RegionStates
    {
        Base,
        Main,
        Details
    }

    public PrimaryRegion()
    {
        StateManager.Group<RegionStates>()
            .DefineState(RegionStates.Main)
            .DefineState(RegionStates.Details);
    }
}

On of the interesting things about the pages of the PrimaryRegion is that they correspond to pages within the application, which in turn need a view model to data bind to. This is how most developers think of this relationship. Actually it’s almost the other way around. Each of the states within the PrimaryRegion will have a view model (think of this as the data that is related to that state). The platform specific implementation of moving to a particular state will display a page that correlates to the state, and will data bind the corresponding view model to the page. Hopefully this helps set the stage for the next step – we’re going to go back to the StateManager within the PrimaryRegion, and this time change to defining view models for each state:

public PrimaryRegion()
{
    StateManager.GroupWithViewModels<RegionStates>()
        .StateWithViewModel<RegionStates, MainViewModel>(RegionStates.Main)
        .EndState()
        .StateWithViewModel<RegionStates, DetailsViewModel>(RegionStates.Details)
        .EndState();
}

You’ll notice that this makes use of two view models, MainViewModel and DetailsViewModel, which I’ll need to create in the PCL. The code associates each of the view model types with the corresponding state enum value. For the timebeing the view models will be relatively basic, only inheriting from NotifyBase which is a simple implementation of the INotifyPropertyChanged interface.

public class MainViewModel:NotifyBase
{ }
public class DetailsViewModel:NotifyBase
{ }

So far I’ve added two states, with view models, to the PrimaryRegion but it doesn’t know which state to start at. By overriding the CompleteStartup method, it’s possible to direct the StateManager to go directly to the Main state.

protected override async Task CompleteStartup()
{
    await base.CompleteStartup();

    await StateManager.GoToState(RegionStates.Main);
}

The navigation to actual pages in the UWP application is handled at the UI layer, and relies on there being a known association between the state that the application is in, and the corresponding page that should be displayed. Some frameworks rely on convention to do this; we take the approach that we prefer this to be explicit, which has the secondary benefit of avoiding reflection. When the application launches, I need to register the MainPage and DetailsPage to the corresponding state:

public App()
{
    this.InitializeComponent();
    this.Suspending += OnSuspending;

    LifecycleHelper.RegisterView<MainPage>().ForState(PrimaryRegion.RegionStates.Main);
    LifecycleHelper.RegisterView<DetailsPage>().ForState(PrimaryRegion.RegionStates.Details);
}

I’m also going to replace all the startup logic that would normally reside in the OnLaunched method in the App.xaml.cs file, with the following code:

protected async override void OnLaunched(LaunchActivatedEventArgs e)
{
    var core = new RootApplication();
    var wm = new WindowManager(core);
    await core.Startup();
}

From this code you can I’m creating an instance of the RootApplication, which knows about the PrimaryRegion, which in turn manages the state transitions between Main (ie MainPage) and Details (DetailsPage). The WindowManager introduces all the glue necessary to navigate to the appropriate pages with the application, and to ensure the window completely loads by calling the Activate method.

For the timebeing I’m going to replace the contents of the MainPage with the following TextBlock and Button, contained within a vertical StackPanel.

<StackPanel>
    <TextBlock Text="{Binding Data}"
                FontSize="40" />
    <Button Click="GoToSecond">Go</Button>
</StackPanel>

Into the MainViewModel I’ll add a simple property that returns a static value:

public string Data { get; } = "Hello World!";

Running the application displays Hello World! on the screen, indicating that the MainViewModel has been setup as the DataContext for the MainPage that’s being displayed.

image

Next, is to wire up the Go button to transition the application to the Details state, in order to display the DetailsPage. The change in state needs to be controlled by the StateManager that belongs to the PrimaryRegion. This means that in order to change state, the Main state needs to raise an event, or send a message, to signal to the PrimaryRegion. In actual fact, it’s the MainViewModel that will raise the event but rather than simply define a custom event I will instead inherit from the BaseViewModelWithCompletion class, using the DefaultCompletion enum which defines a single usable value of Complete (if the state had multiple states it could transition to, I could define a new enum that has a value for each state to transition to). The MainViewModel class now looks like:

public class MainViewModel : BaseViewModelWithCompletion<DefaultCompletion>
{
    public string Data { get; } = "Hello World!";

    public void Complete()
    {
        OnComplete(DefaultCompletion.Complete);

    }
}

The Complete method simply gets invoked by the event handler for the Button

private void GoToSecond(object sender, RoutedEventArgs e)
{
    (DataContext as MainViewModel).Complete();
}

The last thing is to handle the completion of the MainViewModel/Main state in the PrimaryRegion. This can be done by extending the definition of the Main state.

StateManager.GroupWithViewModels<RegionStates>()
    .StateWithViewModel<RegionStates, MainViewModel>(RegionStates.Main)
        .OnComplete(DefaultCompletion.Complete)
        .ChangeState(RegionStates.Details)
    .EndState()
    .StateWithViewModel<RegionStates, DetailsViewModel>(RegionStates.Details)
    .EndState();

Clicking the Button will transition the application from the Main to Details state, and in doing so navigate from the MainPage to the DetailsPage. You’ll also see that a back button appears in the top navigation bar, allowing the transition back to the Main state – this works without having to manually define the back button behaviour.

image

I’ll return now to the MainPage and look at how to populate it with data. There are three main steps to this:

- Register the FlickrService
- Associated the FlickrService with the MainViewModel
- Use the FlickrService to populate data within the MainViewModel

The registration of services happen at an application level but can be done either within the RootApplication, if the implementation of the services is accessible within the PCL (like our FlickrService), of it can be done on App startup as part of the call to Startup using a delegate callback to register platform specific services. For example either the following:

RootApplication

protected override void RegisterDependencies(ContainerBuilder builder)
{
    base.RegisterDependencies(builder);

    builder.RegisterType<FlickrService>().As<IFlickrService>();
}

Or, App.Xaml.cs

await core.Startup(builder => {
       builder.RegisterType<FlickrService>().As<IFlickrService>();
});

In this case, since the FlickrService doesn’t rely on any platform specific implementation I’ll register it in the RootApplication.

Next, we want the instance of the FlickrService to be passed into the MainViewModel so that we can use it to retrieve feed data. To do this we rely on constructor injection by specifying an IFlickrService parameter to the MainViewModel constructor.

public IFlickrService Flickr { get; }

 

public MainViewModel(IFlickrService flickr)
{
    Flickr = flickr;
}

And then I need to expose a collection of photos and provide a method that will invoke the LoadPhotos method on the IFlickrService.

public ObservableCollection<Photo> Photos { get; } = new ObservableCollection<Photo>();
public async Task Load()
{
    var photos = await Flickr.LoadPhotos();
    photos.DoForEach(Photos.Add);
}

A lot of other MVVM style frameworks rely on the view models inheriting from a base view model which has init/dispose style methods defined for when the view model comes into focus or leaves focus (ie the user arrives or leaves at a page). In this case, because we’re defining our application behaviour in terms of states, we want to invoke code when we arrive at the Main state. Again we do this within the PrimaryRegion by adding a call to WhenChangedTo to the Main state definitiion.

.StateWithViewModel<RegionStates, MainViewModel>(RegionStates.Main)
    .WhenChangedTo(vm => vm.Load())
               
    .OnComplete(DefaultCompletion.Complete)
    .ChangeState(RegionStates.Details)
.EndState()

Before running the application at this point we need to return to our MainPage.xaml and return the layout to what we initially created with the ListView, the ProgressRing and the visual states. I needed to make two minor adjustments:

- Make sure the ListView is visible initially – since we don’t have anything to drive the Loaded/Loading visual state changes, yet
- Add the ItemsSource attribute to be data bound to the Photos property ie ItemsSource=”{Binding Photos}”

Running the application now should display a list of photos, similar to what I had at the beginning of this post. To add back the visual states, all I need to do is adjust the MainViewModel to include its own state manager:

public class MainViewModel : BaseStateManagerViewModelWithCompletion<DefaultCompletion>
{
    public enum MainStates
    {
        Base,
        Loading,
        Loaded
    }

    public MainViewModel(IFlickrService flickr)
    {
        StateManager.Group<MainStates>().DefineAllStates();
        Flickr = flickr;
    }

Into the MainViewModel we’ve added a new enumeration, whose values match the names of the visual states. I’ve also adjusted the base class for the MainViewModel to be one that already has a state manager defined. And finally I’ve called DefineAllStates which will create all the states that correlate to the values in the MainStates enumeration. That’s it! Run the application again, and the visual state changes are already wired up.

The final stretch of this application is handling when the user selects an item from the list of photos. The selected photo needs to be passed through to the details page so that the details of the photo can be displayed on the page. A lot of developers prefer to use command binding to pass through the click event to the method in the view model. To keep it simple I’m just going to wire up an event handler for the ItemClick event on the ListView

<ListView x:Name="FlickrListView"
            ItemsSource="{Binding Photos}"
            IsItemClickEnabled="True"
            ItemClick="FlickrListView_OnItemClick"
>
private void FlickrListView_OnItemClick(object sender, ItemClickEventArgs e)
{
    (DataContext as MainViewModel).DisplayPhoto(e.ClickedItem as Photo);
}

Inside the DisplayPhoto method on the MainViewModel I’m going to track the SelectedPhoto before calling the OnComplete method.

public Photo SelectedPhoto { get; private set; }
public void DisplayPhoto(Photo photo)
{
    SelectedPhoto = photo;
    OnComplete(DefaultCompletion.Complete);
}

In order to pass the selected photo into the DetailsViewModel, the state manager in the PrimaryRegion needs to be updated to hand the selected photo into the DetailsViewModel when the Details state is transitioned to.

StateManager.GroupWithViewModels<RegionStates>()
    .StateWithViewModel<RegionStates, MainViewModel>(RegionStates.Main)
        .WhenChangedTo(vm => vm.Load())
       
.OnCompleteWithData(DefaultCompletion.Complete, vm => vm.SelectedPhoto)
        .ChangeState(RegionStates.Details)
    .EndState()

    .StateWithViewModel<RegionStates, DetailsViewModel>(RegionStates.Details)
        .WhenChangedToWithData<RegionStates, DetailsViewModel, Photo>((vm, data) =>
        {
            vm.Photo = data;
        })
    .EndState();

There are two updates to the StateManager:
- The OnComplete in the Main state is replaced with OnCompleteWithData which is used to extract the SelectedPhoto so that it can be passed through to the new state (in this case the Details state)
- A call to WhenChangedToWithData is used to pass through the selected photo into the Photo property on the DetailsViewModel.

Of course, the DetailsViewModel needs to be updated to include the Photo property. As the Photo property may not be set by the time the DetailsViewModel is set as the DataContext on the page, it’s important that the Photo property raises the PropertyChanged event in the setter.

public class DetailsViewModel : NotifyBase
{
    private Photo photo;

    public Photo Photo
    {
        get { return photo; }
        set
        {
            photo = value;
            OnPropertyChanged();
        }
    }
}

The last thing to do is to update the design of the DetailsPage.xaml to render the photo:

<Page x:Class="FlickrViewer.DetailsPage"
      xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:FlickrViewer"
      xmlns:d="
http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Image Source="{Binding Photo.media.m}"
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
               Stretch="UniformToFill" />
        <Grid VerticalAlignment="Bottom">
            <Border Background="Black"
                    Opacity="0.9"/>
            <TextBlock Text="{Binding Photo.title}"
                       TextWrapping="WrapWholeWords"
                       FontSize="30"
                       Foreground="White"
                       MaxLines="3"
                       Margin="20,10" />
        </Grid>

    </Grid>
</Page>

Running the application now gives the following interface for viewing the public Flickr feed.

image


********************************************************************************************************************************************************
                 Flickr Download Service

********************************************************************************************************************************************************

public interface IFlickrService
{
    Task<IEnumerable<Photo>> LoadPhotos();
}

public class FlickrService : IFlickrService
{
    public async Task<IEnumerable<Photo>> LoadPhotos()
    {
        var client = new HttpClient();
        var json = await client.GetStringAsync(
                    "
http://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1");
        var photos = JsonConvert.DeserializeObject<FlickrData>(json);
        return photos.items;
    }
}

public class Media
{
    public string m { get; set; }
}

public class Photo
{
    public string title { get; set; }
    public string link { get; set; }
    public Media media { get; set; }
    public string date_taken { get; set; }
    public string description { get; set; }
    public string published { get; set; }
    public string author { get; set; }
    public string author_id { get; set; }
    public string tags { get; set; }
}

public class FlickrData
{
    public string title { get; set; }
    public string link { get; set; }
    public string description { get; set; }
    public string modified { get; set; }
    public string generator { get; set; }
    public List<Photo> items { get; set; }
}

Pingbacks and trackbacks (1)+

Comments are closed