Hi all,
Jesse authored Translate.WP.TranslateText - a Windows Phone sample for the Google Translate API.
A week ago, I submitted a Blogger.Sample for Universal Store apps using the Google Blogger API.
These are the first samples in our repository that target Windows Phone 8.1 and Windows 8.1 apps using the .NET client library for Google APIs.
In this blog post I'll focus on the Universal Windows sample I created. The sample shares code between the Windows Store and Windows Phone versions, and uses MVVM best practices.
I must admit, the last time I wrote MVVM was back in 2011, back then I worked on a huge WPF project and while working on MVVM again I saw that nothing really has changed since then.
For those of you who don’t know, Universal Windows apps help you develop and share code between all Windows devices. You can read more about it in building-universal-windows-apps.
It's important to mention that I already had a hacky sample for Windows Phone 8.0 that I used to test a new release of the client library with it. I just thought that it is going to be easy to upgrade it to Windows Phone 8.1.
I WAS WRONG
I didn't find a right meme... any suggestion will be appreciated :)
Microsoft introduced significant breaking API changes when upgrading from 8.0 to 8.1. The WebAuthenticationBroker.AuthenticateAsync which I used in Windows Phone 8.0 isn't supported anymore. Starting with Windows 8.1, Microsoft supports several AndContinue methods (one of them is WebAuthenticationBroker.AuthenticateAndContinue). When you call an AndContinue method, the app is deactivated until the operation completes. You can read more in How to continue your Windows Phone Store app after calling an AndContinue method.
So, in order to support the OAuth 2.0 protocol for Windows Phone Store apps, I had to do the following:
Step 1
Create core classes that I’m going to move shortly to Google.Apis and Google.Apis.Auth new projects that will target Windows Phone 8.1. Those classes include the following:A Windows Phone implementation of IDataStore. It uses the platform PasswordVault. PasswordVault represents a credential locker and its content is private for each app and can’t be used by other apps or services in the phone.
An implementation of ICodeReceiver. The important method is ReceiveCodeAsync which should be called twice.
In the 1st time, the method calls WebAuthenticationBroker.AuthenticateAndContinue so that users would be able to authorize this app to access their personal resources.
In the 2nd time, it should be called after the app had already received the authorization code. The implementation counts on the developer to store the code response in the PasswordVaultDataStore using WebAuthResult.Name. (see changes in MainPage.xaml.cs later on…)
An implementation of IAuthorizationCodeInstalledApp, that uses the standard AuthorizationCodeInstalledApp with the AuthorizationCodeBroker mentioned above.
A helper class that puts everything together - the installed app and the password vault data store.
* All those classes are very similar to classes that we already have for supporting Windows Store applications or Windows Phone 8.0.
Step 2
In addition, each Windows Phone app that wants to support continuation should implement the following:ContinuationManager
A custom and a very short implementation that targets authentication ONLY. The full implementation is available here: http://msdn.microsoft.com/en-us/library/dn631755.aspx. Take a look in this short version:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// ContinuationManager is used to detect if the most recent activation was due to a | |
/// authentication continuation. | |
/// | |
/// Note: To keep this sample as simple as possible, the content of the file was changed | |
/// to support WebAuthenticationBrokerContinuation ONLY. Take a look in | |
/// http://msdn.microsoft.com/en-us/library/dn631755.aspx for a full documentation on how | |
/// to support continuation in other cases. | |
/// </summary> | |
public class ContinuationManager | |
{ | |
internal void Continue(IContinuationActivatedEventArgs args) | |
{ | |
switch (args.Kind) | |
{ | |
case ActivationKind.WebAuthenticationBrokerContinuation: | |
var page = MainPage.Current as IWebAuthenticationContinuable; | |
if (page != null) | |
{ | |
page.ContinueWebAuthentication( | |
args as WebAuthenticationBrokerContinuationEventArgs); | |
} | |
break; | |
} | |
} | |
} |
The application starting point. The important method is OnActivated, which might be called after the authorization code was received. The implementation looks like the following:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// On activated callback. It is used in order to continue the application | |
/// after the user authenticated. | |
/// </summary> | |
protected override void OnActivated(IActivatedEventArgs e) | |
{ | |
base.OnActivated(e); | |
ContinuationManager = new Blogger.Common.ContinuationManager(); | |
var continuationEventArgs = e as IContinuationActivatedEventArgs; | |
if (continuationEventArgs != null) | |
{ | |
// Call ContinuationManager to handle continuation activation. | |
// The default ContinuationManager (which is documented in | |
// http://msdn.microsoft.com/en-us/library/dn631755.aspx) was changed to | |
/// handle ONLY authentication. | |
// It supports now current window and NOT current active frame. | |
ContinuationManager.Continue(continuationEventArgs); | |
} | |
Window.Current.Activate(); | |
} |
The first time the users log in to the app, the app should be suspended and the users must authorizes the app in order to access their private resources. When the app is activated again, it calls the continuation manager which in turn calls the ContinueWebAuthentication method. I think that the method is self documented, so without any words, take a look:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Continues the app after retrieving the authorization code. | |
/// | |
/// First it stores the authorization code result in the data store. | |
/// Then it calls the view model get blogs method, in order to retrieve all blogs. | |
/// The view model is responsible to check for if the authorization code exists in | |
/// the data store, and then, continue with the regular flow. | |
/// After retrieving all blogs, this method deletes the authorization code so it | |
/// won't be used in a next run. | |
/// </summary> | |
public async void ContinueWebAuthentication( | |
WebAuthenticationBrokerContinuationEventArgs args) | |
{ | |
await PasswordVaultDataStore.Default.StoreAsync<WebAuthResult>( | |
WebAuthResult.Name, new WebAuthResult(args.WebAuthenticationResult)); | |
await ((BlogsViewModel)this.DataContext).GetBlogsAsync(); | |
await PasswordVaultDataStore.Default.DeleteAsync<WebAuthResult>(WebAuthResult.Name); | |
} |
The only piece that is still missing to you now, is the BlogsViewModel and the BloggerRepository. Those two classes (and several others) are shared between the Windows and Windows Phone projects.
BlogsViewModel is just the ViewModel of the MainPage. When the users request to get all their blogs, the GetBlogsCommand is called. Then GetBlogsAsync will be executed and in turn it will call the repository to retrieve all blogs.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary>The blogs view model which contains all blog data.</summary> | |
public class BlogsViewModel : ViewModelBase | |
{ | |
/// <summary>Gets the blog view models..</summary> | |
public ObservableCollection<BlogViewModel> Blogs { get; private set; } | |
private BlogViewModel selectedBlog; | |
/// <summary>Gets or sets the selected blog.</summary> | |
public BlogViewModel SelectedBlog | |
{ | |
get { return selectedBlog; } | |
set | |
{ | |
Set(() => SelectedBlog, ref selectedBlog, value); | |
if (selectedBlog != null) | |
selectedBlog.RefreshPosts(); | |
} | |
} | |
private readonly IBloggerRepository repository; | |
/// <summary>Gets the get blogs command.</summary> | |
public RelayCommand GetBlogsCommand { get; private set; } | |
public BlogsViewModel(IBloggerRepository repository) | |
{ | |
Blogs = new ObservableCollection<BlogViewModel>(); | |
this.repository = repository; | |
this.GetBlogsCommand = new RelayCommand( | |
async () => await GetBlogsAsync()); | |
} | |
/// <summary>Asynchronously gets all the blogs.</summary> | |
public async Task GetBlogsAsync() | |
{ | |
var blogs = await repository.GetBlogsAsync(); | |
// Fill the blogs collection should be from the | |
// thread that created the collection. | |
await UIUtils.InvokeFromUIThread(() => | |
{ | |
Blogs.Clear(); | |
foreach (var b in blogs) | |
{ | |
Blogs.Add(new BlogViewModel(repository) | |
{ | |
Name = b.Name, | |
Id = b.Id | |
}); | |
} | |
}); | |
} | |
} |
* Note that the implementation of GoogleWebAuthorizationBroker is different for Windows and Windows Phone apps.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// The blogger repository implementation which works the same for Windows | |
/// and Windows Phone. | |
/// </summary> | |
public class BloggerRepository : IBloggerRepository | |
{ | |
private UserCredential credential; | |
private BloggerService service; | |
private async Task AuthenticateAsync() | |
{ | |
if (service != null) | |
{ | |
// The user had already authenticated. | |
return; | |
} | |
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync( | |
new Uri("ms-appx:///Assets/client_secrets.json"), | |
new[] { BloggerService.Scope.BloggerReadonly }, | |
"user", | |
CancellationToken.None); | |
var initializer = new BaseClientService.Initializer() | |
{ | |
HttpClientInitializer = credential, | |
ApplicationName = "BloggerApp", | |
}; | |
service = new BloggerService(initializer); | |
} | |
public async Task<IEnumerable<Blog>> GetBlogsAsync() | |
{ | |
await AuthenticateAsync(); | |
var list = await service.Blogs.ListByUser("self").ExecuteAsync(); | |
return from blog in list.Items | |
select new Blog | |
{ | |
Id = blog.Id, | |
Name = blog.Name | |
}; | |
} | |
public async Task<IEnumerable<Post>> GetPostsAsync(string blogId) | |
{ | |
await AuthenticateAsync(); | |
var list = await service.Posts.List(blogId).ExecuteAsync(); | |
return from post in list.Items | |
select new Post | |
{ | |
Title = post.Title, | |
Content = post.Content | |
}; | |
} | |
} |
![]() |
Upstate NY, Oct 2014 |
Stay tuned for further documentation - the Windows Phone 8.1 section is going to be refreshed soon.
We hope you find our new sample useful when writing Windows Phone 8.1 apps with Google Apis. Let us know if you have any questions in the comments of this post, on the client issue tracker or in Stack Overflow using the google-api-dotnet-client tag (Preferred).
Enjoy!
Eyal (upside down)