REST APIs in App Studio Part 2- Changing the Source Code
In Part 1 of this series, we used App Studio to build the shell of a project that will (eventually) display data from a REST API call, specifically the Rotten Tomatoes API. In App Studio, we were able to create a dynamic collection, select the type of layout for its two pages (list and details), and then set the bindings for each of those pages without setting any actual data. Here in part 2, we are going to edit the source code generated from App Studio to then get our data from the Rotten Tomatoes API. If you would rather follow along by video, you can find the Youtube video here.
The Source Code
You should have finished part 1 by downloading the source code. If you have done so and unzipped the file you should see a Visual Studio solution file. Keep in mind, you will need Visual Studio 2013 update 2. Go ahead and open the .sln file highlighted below.
Once Visual Studio has loaded up you can take a look at the Solution Explorer for this list of projects and files. Notice that we have four different projects here; AppStudio.Windows, AppStudio.WindowsPhone, AppStudio.Shared, and AppStudio.Data. This is the general structure for a Windows Universal App, which supports Windows 8.1 and Windows Phone 8.1. That said, the first two projects contain files specific to those two respective platforms. Then there is the Shared project which holds files shared between the two platforms, and the Data project (a portable class library) which is where we will be focused today.
Below you can se the three highlighted files from within the Data project that we are going to be looking at today
InTheaterSchema.cs
First let’s take a look at the InTheatersSchema file. This is basically the object model for the data that we defined in our collection. You can see the four fields that we defined in App Studio; Title, Rating, ImageUrl, and Synopsis. Also, keep in mind, that the section that contains this collection in Part 1 was called “In Theaters”. This is why this file is called InTheatersSchema. It is the data schema for the “In Theaters” section. We won’t be editing this file, but it is worth mentioning where it comes from and making the connection.
ServiceDataProvider.cs
From here, let’s move over to the ServiceDataProvider file. This file by default is used to access databases of a Dynamic Collection that we create in App Studio. However, since we wont be getting our data that way (we want data from Rotten Tomatoes), we could either change this file or create our own. Either way is fine, but to make things easier, I have added an updated ServiceDataProvider.cs file to my OneDrive. You can simply replace the file in your solution explorer with my updated one. Since all App Studio projects by default have the same solution name, the namespaces should work out okay here.
So, go ahead and replace the file. The best way to do this is to just drag the new file in and choose to replace the older file.
Now, let’s take a second to talk about what is happening here. In general, we need to…
- Call the Rotten Tomatoes API
- Deserialize that data
- Return the appropriate data type.
In looking at the code, the first things you will notice are your API Key string (which you will need to fill in) and several strings that represent different calls that we can make to Rotten Tomatoes. Again, make sure that you fill in your API Key here or none of the below calls will work.
private static string ROTTEN_TOMATOES_API_KEY = "";
private static string ROTTEN_TOMATOES_API_MOVIES_BOXOFFICE = @"https://api.rottentomatoes.com/api/public/v1.0/lists/movies/box_office.json?limit=20&country=us&apikey=" + ROTTEN_TOMATOES_API_KEY;
private static string ROTTEN_TOMATOES_API_MOVIES_INTHEATERS = @"https://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json?page_limit=20&page=1&country=us&apikey=" + ROTTEN_TOMATOES_API_KEY;
private static string ROTTEN_TOMATOES_API_MOVIES_OPENING = @"https://api.rottentomatoes.com/api/public/v1.0/lists/movies/opening.json?limit=20&country=us&apikey=" + ROTTEN_TOMATOES_API_KEY;
private static string ROTTEN_TOMATOES_API_MOVIES_UPCOMING = @"https://api.rottentomatoes.com/api/public/v1.0/lists/movies/upcoming.json?page_limit=20&page=1&country=us&apikey=" + ROTTEN_TOMATOES_API_KEY;
private static string ROTTEN_TOMATOES_API_DVD_TOPRENTALS = @"https://api.rottentomatoes.com/api/public/v1.0/lists/dvds/top_rentals.json?limit=20&country=us&apikey=" + ROTTEN_TOMATOES_API_KEY;
private static string ROTTEN_TOMATOES_API_DVD_CURRENTRELEASES = @"https://api.rottentomatoes.com/api/public/v1.0/lists/dvds/current_releases.json?page_limit=20&page=1&country=us&apikey=" + ROTTEN_TOMATOES_API_KEY;
private static string ROTTEN_TOMATOES_API_DVD_NEWRELEASES = @"https://api.rottentomatoes.com/api/public/v1.0/lists/dvds/new_releases.json?page_limit=20&page=1&country=us&apikey=" + ROTTEN_TOMATOES_API_KEY;
private static string ROTTEN_TOMATOES_API_DVD_UPCOMING = @"https://api.rottentomatoes.com/api/public/v1.0/lists/dvds/upcoming.json?page_limit=20&page=1&country=us&apikey=" + ROTTEN_TOMATOES_API_KEY;
For more information on how we formed this strings, you can check out my post on the Rotten Tomatoes API.
Now, let’s take a look at the three methods in this class, DownloadAsync, Load, and Copy.
DownloadAsync
DownloadAsync method which accepts a Uri as a parameter, and then uses an HttpClient to get and return the JSON string from our call. Pretty straight forward.
public async Task<string> DownloadAsync(Uri url)
{
HttpClient client = new HttpClient();
return await client.GetStringAsync(url);
}
Load
The Load method that will get the data returned from DownloadAsync, use the Copy method (see below) to convert it. This load function will look pretty similar to the one from the original ServiceDataProvider.cs file (if you took a look before we replaced it). It makes a call to the Copy method (see below) and returns the list of data. One thing you can explore with here is the Rotten Tomatoes call in bold below. Currently, I am using the Box Office call which returns a list of movies in Box Office, but you can choose any of the ones that we added above.
public async Task<IEnumerable<T>> Load<T>()
{
try
{
string data = await DownloadAsync(new Uri(ROTTEN_TOMATOES_API_MOVIES_BOXOFFICE));
IEnumerable<T> records = (IEnumerable<T>)Copy(data);
return records;
}
catch (Exception ex)
{
AppLogs.WriteError("ServiceDataProvider.Load", ex);
return null;
}
}
Copy
The Copy method takes the JSON string response, deserializes it to a Dynamic object, and then copies that data to an ObservableCollection of InTheatersSchema objects. Remember us walking through the InTheaterSchema file earlier? Just to reiterate, that schema file will have a property for each column of our dynamic collection that we defined in App Studio.
public ObservableCollection<InTheatersSchema> Copy(string data)
{
dynamic foo = JObject.Parse(data);
ObservableCollection<InTheatersSchema> collection = new ObservableCollection<InTheatersSchema>();
foreach (dynamic movie in foo.movies)
{
InTheatersSchema temp = new InTheatersSchema();
temp.Title = movie.title;
temp.Rating = movie.year.ToString();
string image = movie.posters.original;
temp.ImageUrl = image.Replace("tmb", "ori");
temp.Synopsis = movie.synopsis;
collection.Add(temp);
}
return collection;
}
The goal here was to make this method as generic as possible, so that with minimal changes, you could reference different APIs. HINT HINT. Stay tuned for future post on that
InTheatersDataSource.cs
Now, let’s open up the InTheatersDataSource.cs. Two lines are highlighted below that create an instance of a ServiceDataProvider class and calls its load method. We only need to make one small change here; strip out the parameters when creating the new instance of ServiceDataProvider. We don’t have an explicit constructor in our updated version, so we can’t accept any parameters here.
Should now look like this.
**Additionally, we might need to add the following using statement to resolve the error on ServiceDataProvider.
using AppStudio.Data.DataProviders;
Deploy to Windows 8.1
Go ahead and deploy to Windows 8.1, and you should see the following.
Additionally, if you click on one o the movies, you will see its details page.
Deploy to Windows Phone
You can also run it as a phone app in the phone emulator.
You might notice that the images don’t fit perfectly. You can go in and edit the layout accordingly. Additionally, check out Part 3, where I will show you how to incorporate any API that you want!
As always comment below with any questions, comments, or concerns and follow me on Twitter @jquickwit.
Comments
Anonymous
July 14, 2014
Wow very in depth. This is great!Anonymous
July 24, 2014
Nice post James, thanks for showing how use an API with an app studio project. Looking forward to your next posts.Anonymous
July 29, 2014
The comment has been removedAnonymous
August 06, 2014
Hi James, I have tried this 3 times now and each time I can only get the movie titles to show up nothing else (no image, synopsis etc)... I am lost as to what I am doing wrong because surely if it can get the title then it can get the rest of the data...HelpAnonymous
August 06, 2014
Ok I think I have worked it out I haven't bound the data to the template....oops sorry...looking forward to more!!!Anonymous
August 06, 2014
@Aaron glad it worked out! Let me know if you have any more questions and keep an eye out for more posts! You can follow me on twitter @jquickwit and I will announce there as well!Anonymous
March 23, 2015
When I add another Web API for a second menu item, I keep getting an error in the MainPage.xaml "object reference not set to an instant of an object. Can I add two or more different Web API sources for App Studio or I'm limited to just one? Below is the details error in the MainPage.xaml Please help. Thanks. at AppStudio.Data.HighlightRootSchema.GetHashCode() at System.Collections.Generic.GenericEqualityComparer1.GetHashCode(T obj) at System.Linq.Set
1.InternalGetHashCode(TElement value) at System.Linq.Set1.Find(TElement value, Boolean add) at System.Linq.Enumerable.<IntersectIterator>d__92
1.MoveNext() at System.Linq.Enumerable.<OfTypeIterator>d__aa1.MoveNext() at AppStudio.Data.ObservableCollectionExtensions.AddRangeUnique[T](ObservableCollection
1 oldCollection, IEnumerable1 newItems) at AppStudio.Data.DataSourceBase
1.<LoadDataAsync>d__1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at AppStudio.ViewModels.ViewModelBase
1.<LoadItemsAsync>d__0.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at AppStudio.ViewModels.MainViewModel.<LoadDataAsync>d__0.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at AppStudio.Views.MainPage.<OnNavigatedTo>d__0.MoveNext()Anonymous
March 30, 2015
@Henry my example above really works well with just 1 API. However, you can use what i have as a template to use as many API as you want. For each API you want to leverage, you will need to create its own Service Data Provider. It would look almost exactly the same as the one I have above except customized for your additional APIs.Anonymous
April 11, 2015
James, I did not create another service data provider. Probably that was the issue. I'm going to use your app example and add another service provider to see if I can get it to work. I'll let you know. Thanks for your reply and the tutorials!Anonymous
April 19, 2015
The comment has been removedAnonymous
April 25, 2015
@Hey henry, in general to use two different APIs, you will need two different *DataSource.Cs files. They will look almost exactly the same except the actual call to the API and the conversion of the JSON data to the Object defined in App Studio. You would also need to update the service data provider to reference the correct DataSource fileAnonymous
May 04, 2015
@James, Woo hoo, I've got it to work. I created two different datasource files and two different service provider files. Do you know if there is an article explaining the code modules of App Studio in details? I love to learn more about the code generated from App Studio. Thank you very much for explaining! Cheers :)