Mobile Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

Flickr API for Windows Phone 7 – Part 4 – Activity – User Photos

09.30.2010
| 5996 views |
  • submit to reddit

In this article I am going to show how to use the flickr.activity.userPhotos method. Now you might ask – there are a couple of tens of Flickr API methods out there – will I cover each one in a separate article? The answer is no – I will focus on the main methods. Once you get the idea behind the kind of functionality the API offers, you will be able to code the rest of your way through it.

So what’s up with flickr.activity.userActivity? Basically, this is the method that will pull up all the recent activity on your photos. For example, if someone commented on one of your photos, it will let you know that there was a comment posted, will show you the user who posted it, the date and the comment contents. In case there is no activity (therefore, no comments or other kinds of activity), it will return a JSON-formatted document (remember, I am using JSON in this series?) that contains no data that would interest you.

So what I did first here is I added an Activity class to the Core folder – that way, I can concentrate the activity category of the Flickr API in one place.

Now, I decided that the delegate I was using before might be handy in other classes as well, so all I did is take its declaration and put it inside the FlickrWP7.Core namespace:

The Activity class itself is quite simple and resembles the Authentication class if you look at GetUserPhotos and the call structure:

public class Activity
{
    private  HelperDelegate helperDelegateInstance;

    public  string LastActivityResult { get; set; }

    public  void GetUserPhotos(string apiKey, string signature, string authToken, HelperDelegate helperDelegate, string page = "", string timeframe = "", string resultsPerPage = "" )
    {
        helperDelegateInstance = helperDelegate;

        WebClient client = new WebClient();
        client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
        string URL = string.Format("http://www.flickr.com/services/rest/?method=flickr.activity.userPhotos&api_key={0}&api_sig={1}&auth_token={2}&format=json",
                apiKey, signature,authToken);

        if (page != "")
            URL += "&page=" + page;
        if (timeframe != "")
            URL += "&timeframe=" + timeframe;
        if (resultsPerPage != "")
            URL += "&per_page=" + resultsPerPage;

        client.DownloadStringAsync(new Uri(URL));
    }

    void client_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
    {
        LastActivityResult = e.Result;
        helperDelegateInstance();
    }
}

I have the LastActivityResult property - it is the place where the raw JSON data will be stored for the last call from the activity set. You might ask - why is this class not static? It is a tricky situation here – there is also flickr.activity.userComments that is a member of the activity category – therefore, I might want to preserve one’s state and get another activity – so I will declare two separate instances for each one instead of having two separate properties, where one of them will never be used. And since I am pulling the same raw JSON data, it is alright for now to store it in a single property. Eventually, if you decide, for example, to deserialize the data, you might want to introduce separate properties for each.

Now here you might have another question – why pull raw data instead of having a property that will be a custom class representing the item hierarchy? The answer is simple – the item hierarchy might change. Therefore, if I hardcode it here, an exception will be thrown each time the specification changes and the application is not able to retrieve the needed data. Raw JSON can be parsed directly in the application, outside the action method and if Flickr decides to modify the returned result, I will only have to modify the parsing in the app and not the core engine.

You can see that GetUserPhotos accepts three optional parameters – page, timeframe and resultsPerPage. These are documented in the call specification and the user might or might not need them. But in case he decides to specify them, the URL will be built accordingly.

To experiment with this class, I created a test page in my Windows Phone 7 application. Its XAML markup looks like this:

<phone:PhoneApplicationPage
    x:Class="FlickrWP7.MainPage"
    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"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
 
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Button x:Name="btnFullToken" Content="Get Full Token" Height="72" HorizontalAlignment="Left" Margin="12,684,0,0" VerticalAlignment="Top" Width="230" Click="btnFullToken_Click" />
        <Button x:Name="btnUserPhotos" Content="Get User Photos" Height="72" HorizontalAlignment="Left" Margin="229,684,0,0" VerticalAlignment="Top" Width="239" Click="btnUserPhotos_Click" />
        <TextBlock TextWrapping="Wrap" Height="666" HorizontalAlignment="Left" Margin="12,12,0,0" Name="txtLog" VerticalAlignment="Top" Width="456" />
    </Grid>
 
</phone:PhoneApplicationPage>

It is a simple testing “console”:

I am testing the functionality of my methods in the following manner:

private void btnFullToken_Click(object sender, RoutedEventArgs e)
{
    param.Add("api_key", apiKey);
    param.Add("method", "flickr.auth.getFullToken");
    param.Add("mini_token", miniToken);
    param.Add("format", "json");

    Core.Authentication.GetSignature(param, “SECRET", () =>
    {
       txtLog.Text += "Getting full token...";
        Core.Authentication.GetFullToken(
            miniToken, Core.Authentication.Signature, apiKey, () =>
            {
                txtLog.Text += "\nFull token generated\n" + Core.Authentication.FullToken;
            });
    });
}

private void btnUserPhotos_Click(object sender, RoutedEventArgs e)
{
    param["method"] = "flickr.activity.userPhotos";
    param.Remove("mini_token");
    param.Add("auth_token", Core.Authentication.FullToken);
    param.Add("timeframe", "100d");

    Core.Authentication.GetSignature(param, "SECRET", () =>
    {
        txtLog.Text += "\nGetting list of photos...";
        Core.Activity activity = new Core.Activity();

        activity.GetUserPhotos(apiKey, Core.Authentication.Signature,Core.Authentication.FullToken, () =>
        {
            txtLog.Text += "\n" + activity.LastActivityResult;
        },"","100d");
    });
}

What I am doing here is simply getting the signatures for each method (according to the parameters specified) and then executing those methods (some of them are introduced via a delegate - triggered only when the actual method signature is ready).

NOTE: The signature is tied to the parameters used. Therefore, if your signature is generated with a specific parameter present and you miss it in the method call, the call will fail. Same applies if you use a parameter in a call but didn’t use it in the signature. In most cases, when you get an error code 96 – Invalid signature, the problem is somewhere with the parameters used.

apiKey and miniToken are fields that are publicly accessible in the class and should represent your unique authentication identifiers.

As you’ve probably noticed, the GetUserPhotos method should be executed after GetFullToken, since the full authentication token is needed to get the recent activity.

If you want to experiment directly with my existing solution, download the updated version (if you follow the series) here. Don't forget that you need the helper hashing service for it to work.