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 17 (Final) – Showing Profile Info

10.24.2010
| 6038 views |
  • submit to reddit

Wow, this was a big series of articles on Flickr API on Windows Phone 7. With the core engine that implements most of the API calls it is quite simple to put all this in place now since all you have to do is process the data obtained from the web service. Since the UI design is not the point of this set of articles (or, if you really want to, call them guides) I am not really going to guide the reader through the process of creating lists and loading pictures because that would require another 10 articles. The project itself will be published on CodePlex, so once I update some parts of it, those will appear there and you are more than welcome to pick it up, see how it’s done, modify it and maybe improve it.

For now, though, I am going to show you how to load profile information in the main application. As you already know, once the user goes through the authentication process, we get the user ID and the full authentication token. These are the only two elements needed to continue to work. But before looking at code, I placed a grid and a control set inside a PivotItem element on my main page:

<controls:PanoramaItem Header="profile">
<Grid>
<TextBlock x:Name="txtUser" Text="Hey!" Height="40" Margin="16,0,30,426" Width="374"></TextBlock>
<Image x:Name="profileImage" Stretch="Uniform" Height="96" Width="96" Margin="19,47,305,323"></Image>
<TextBlock Style="{StaticResource PhoneTextTitle2Style}" Height="50" HorizontalAlignment="Left" Margin="124,29,0,0" Name="textBlock1" Text="real name:" VerticalAlignment="Top" Width="150" />
<TextBlock x:Name="txtRealName" Height="30" HorizontalAlignment="Left" Margin="126,84,0,0" FontWeight="Black" Text="REAL NAME" VerticalAlignment="Top" />
<TextBlock Height="50" HorizontalAlignment="Left" Margin="124,123,0,0" Name="textBlock2" Style="{StaticResource PhoneTextTitle2Style}" Text="uploads:" VerticalAlignment="Top" Width="150" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="126,178,0,0" Name="txtUploads" FontWeight="Black" Text="UPLOADS" VerticalAlignment="Top" />
<TextBlock Height="50" Margin="124,217,113,0" Name="textBlock3" Style="{StaticResource PhoneTextTitle2Style}" Text="pro account:" VerticalAlignment="Top" Width="183" />
<TextBlock Height="30" HorizontalAlignment="Left" Margin="126,272,0,0" Name="txtIsPro" FontWeight="Black" Text="YES/NO" VerticalAlignment="Top" />
</Grid>
</controls:PanoramaItem>

It does look confusing, but here is what the actual layout looks like:

The names for the three important text blocks that are used are pretty much self-explanatory – txtUser will display the username, txtRealName will display the real name, txtUploads will display the number of uploads and txtIsPro will show you whether the linked Flickr user is a Pro member or not.

The code-behind requires a bit more work than the UI markup itself. In the page constructor, I create the welcome message that is displayed in txtUser. The username is obtained directly from the application settings and it is set on authentication.

txtUser.Text = "Hey, " + System.IO.IsolatedStorage.IsolatedStorageSettings.ApplicationSettings["user"].ToString() + "!";

if (IsolatedStorageFile.GetUserStoreForApplication().FileExists("profile"))
{
LoadLocalProfile();
}
else
{
LoadWebProfile();
}

Now here is the part where you might ask – what are LoadLocalProfile and LoadWebProfile? And why am I checking whether a file named profile exists?

To put it simple, profile is the file that stores the profile information. It is a good practice to cache some basic info rather than fetch it every time the user opens the app. And that’s exactly what I am doing with profile information.

If the file containing the needed fields is in the isolated storage, then I am loading it locally – LoadLocalProfile. If the file is not there, then I need to get the data from the web – LoadWebProfile.

Let’s start with LoadWebProfile, since it is the method that actually creates the profile file.

void LoadWebProfile()
{
Core.People people = new Core.People();
people.GetInfo(App.API_KEY, IsolatedStorageSettings.ApplicationSettings["id"].ToString(), () =>
{
JsonObject json = JsonObject.Parse(Core.Json.MakeValidJson(people.PeopleQueryResult)) as JsonObject;

SaveProfileData(json);
SaveImage(json);
});
}

I create an instance of class People, that operates with data linked to user accounts on Flickr. The GetInfo method retrieves the basic user info and it doesn’t require authentication, therefore I am not passing a signature here. The only parameter that really matters here is the NSID – the Flickr user ID and it is obtained from the id setting that is created on authentication. When the method returns the needed data, I am parsing the JSON, making sure that it is valid (Flickr adds invalid Json elements at the beginning and the end of the full string) and then calling the SaveProfileData method, that will store the JSON contents locally, in the isolated storage:

void SaveProfileData(JsonObject json)
{
buffer = Encoding.UTF8.GetBytes(json.ToString());

IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("profile", System.IO.FileMode.OpenOrCreate, file))
{
stream.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(EndProfileWrite), stream);
}
}

void EndProfileWrite(IAsyncResult result)
{
IsolatedStorageFileStream stream = (IsolatedStorageFileStream)result.AsyncState;
stream.EndWrite(result);
stream.Close();
}

The nature of the file system on Windows Phone 7 allows me to save a file without providing an extension for it – it is basically a simple identifier, unless you implement custom viewers that are tied to specific extensions.

The byte array that is the Json string is stored via an IsolatedStorageFileStream instance. The writing process itself is executed asynchronously – that’s exactly the reason why there is an AsyncCallback provided that points to EndProfileWrite – that will be method that will finalize the writing and close the stream. Not closing the stream can cause subsequent problems when you will try reading a stream that is tied to the same resource.

From LoadWebProfile, SaveImage is the method that is called in order to obtain and save locally the buddy icon – the Flickr user profile image. The implementation is once again based on JSON parsing:

void SaveImage(JsonObject json)
{
JsonValue value = null;
json.TryGetValue("person", out value);
json = JsonObject.Parse(value.ToString()) as JsonObject;
json.TryGetValue("iconserver", out value);

string iconServer = value.ToString().Remove(0, 1);
iconServer = iconServer.Remove(iconServer.Length - 1, 1);

json.TryGetValue("iconfarm", out value);

string iconFarm = value.ToString();

string buddyIconUrl = string.Format("http://farm{0}.static.flickr.com/{1}/buddyicons/{2}.jpg",
iconFarm, iconServer, IsolatedStorageSettings.ApplicationSettings["id"].ToString());

WebClient client = new WebClient();
client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted);
client.OpenReadAsync(new Uri(buddyIconUrl));
}

The URL is built directly based on the parameters obtained from the JSON profile data. In particular, there are two parameters that we need – iconfarm and iconserver. These are the indicators that are included in the request URL , that is formatted the following way:

http://farm{iconfarm}.static.flickr.com/{iconserver}/buddyicons/{nsid}.jpg

All I have to do is plug in the existing values in this string and use a WebClient instance to download the image data.

void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
byte[] image = Core.ByteConverter.ConvertToByte(e.Result);

IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("icon.jpg", System.IO.FileMode.Create, file))
{
stream.BeginWrite(image, 0, image.Length, new AsyncCallback(EndImageWrite), stream);
}
}

void EndImageWrite(IAsyncResult result)
{
IsolatedStorageFileStream stream = (IsolatedStorageFileStream)result.AsyncState;
stream.EndWrite(result);
stream.Close();

LoadLocalProfile();
}

With the help of Core.BitConverter.ConvertToByte I am able to convert the obtained stream to a byte array and then pass it as file contents, once again via IsolatedStorageFileStream. Notice, however, that when I am done getting the image, I am calling LoadLocalProfile – so that the latest profile is actually displayed when the image is there.

void LoadLocalProfile()
{
IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();
using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream("profile", System.IO.FileMode.Open, file))
{
buffer = new byte[stream.Length];
stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ReadCompleted), stream);
}
}

void ReadCompleted(IAsyncResult result)
{
IsolatedStorageFileStream stream = (IsolatedStorageFileStream)result.AsyncState;
stream.EndRead(result);

SetProfileData();
}

I am reading the file containing profile information to a byte array and then I am calling SetProfileData, which basically will display the stored data in the UI:

private void SetProfileData()
{
string contents = Encoding.UTF8.GetString(buffer, 0, buffer.Length);

JsonObject json = JsonObject.Parse(contents) as JsonObject;
JsonValue value = null;

json.TryGetValue("person", out value);
json = JsonObject.Parse(value.ToString()) as JsonObject;

JsonObject count = json;
JsonValue counter = null;
count.TryGetValue("photos", out counter);
count = JsonObject.Parse(counter.ToString()) as JsonObject;
count.TryGetValue("count", out counter);
count = JsonObject.Parse(counter.ToString()) as JsonObject;
count.TryGetValue("_content", out counter);
txtUploads.Text = counter.ToString();

json.TryGetValue("ispro", out value);
if (value.ToString() == "0")
{
txtIsPro.Text = "NO";
txtIsPro.Foreground = new SolidColorBrush(Colors.Red);
}
else
{
txtIsPro.Text = "YES";
txtIsPro.Foreground = new SolidColorBrush(Colors.Green);
}

json.TryGetValue("realname", out value);
json = JsonObject.Parse(value.ToString()) as JsonObject;
json.TryGetValue("_content", out value);

string realName = value.ToString().Remove(0, 1);
realName = realName.Remove(realName.Length - 1, 1);
txtRealName.Text = realName;

SetImage();
}

I parse the convert the byte array to string and then to JSON. Although it might seem like I have too many intermediary steps, this is the only way to do it on Windows Phone 7 since I cannot directly read file contents based on the data type those might hold. Once parsing is complete, I am assigning the proper values to their respective fields and calling SetImage, that will display the image in the Image control I placed on my page:

private void SetImage()
{
IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication();

using (IsolatedStorageFileStream stream = file.OpenFile("icon.jpg",FileMode.Open))
{
buffer = new byte[stream.Length];
stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(ImageReadCompleted), stream);
}
}

void ImageReadCompleted(IAsyncResult result)
{
IsolatedStorageFileStream stream = (IsolatedStorageFileStream)result.AsyncState;
stream.EndRead(result);

MemoryStream mStream = new MemoryStream(buffer);
BitmapImage image = new BitmapImage();
image.SetSource(mStream);

profileImage.Source = image;
}

First of all, the byte array is converted to a stream via MemoryStream and then I am creating a BitmapImage based on the stream. Once this is done, I am directly assigning the BitmapImage as the source for the Image control.

Once done, try launching the application. The end-result should look like this:

Congratulations! Now your app can display profile information for the user that is authenticated for the current session. Thank you for your patience of going through this entire project and stay tuned for updates at http://flickrwp.codeplex.com – I will publish the sourcecode over there soon.