-2

I'm trying to get this sample working with Xamarin (Xamarin Forms)

https://developers.google.com/google-apps/calendar/quickstart/dotnet

I modified the code so that the Xamarin Forms app would load the credentials json from a resource file. I checked that the resource file is getting loaded by stepping to the code. I also modified the DataStore class because Xamarin Forms can't access the path that is specified in the sample.

using Google.Apis.Auth.OAuth2;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace TestXamarinFormsLibrary.Pages
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class CalendarPage : ContentPage
    {
        public CalendarPage()
        {
            InitializeComponent();
            Go();
        }

        static string[] Scopes = { CalendarService.Scope.CalendarReadonly };
        static string ApplicationName = "Google Calendar API .NET Quickstart";

        private async void Go()
        {
            UserCredential credential;

            var assembly = GetType().Assembly;
            using (var stream = assembly.GetManifestResourceStream("TestXamarinFormsLibrary.client_secret.json"))
            {
                credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                        GoogleClientSecrets.Load(stream).Secrets,
                        Scopes,
                        "user",
                        CancellationToken.None,
                        new DataStore()).Result;
            }

            // Create Google Calendar API service.
            var service = new CalendarService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = ApplicationName,
            });

            // Define parameters of request.
            EventsResource.ListRequest request = service.Events.List("primary");
            request.TimeMin = DateTime.Now;
            request.ShowDeleted = false;
            request.SingleEvents = true;
            request.MaxResults = 10;
            request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;

            // List events.
            Events events = request.Execute();
            if (events.Items != null && events.Items.Count > 0)
            {
                foreach (var eventItem in events.Items)
                {
                    string when = eventItem.Start.DateTime.ToString();
                    if (String.IsNullOrEmpty(when))
                    {
                        when = eventItem.Start.Date;
                    }
                    await DisplayAlert("ok", $"{eventItem.Summary} ({when})", "ok");
                }
            }
        }
    }

    public class DataStore : IDataStore
    {
        private Dictionary<string, object> Data = new Dictionary<string, object>();

        public Task ClearAsync()
        {
            throw new NotImplementedException();
        }

        public Task DeleteAsync<T>(string key)
        {
            throw new NotImplementedException();
        }

        public async Task<T> GetAsync<T>(string key)
        {
            if (Data.ContainsKey(key))
            {
                return (T)Data[key];
            }

            return default(T);
        }

        public async Task StoreAsync<T>(string key, T value)
        {
            Data.Add(key, value);
        }
    }
}

This is the error I get when I run it:

enter image description here

No exception. I get a similar error on Android. My guess is that it is relying on an oAuth library to open up a web browser, to attempt to allow authentication, but Xamarin Forms does not implement this behaviour. I've also tried turning on all the UWP capabilities, but this didn't help.

How can I get the sample running for Xamarin Forms?

Christian Findlay
  • 6,770
  • 5
  • 51
  • 103
  • Assuming you are creating a console app, the `calendar-dotnet-quickstart.json` is an credential output file created by `GoogleWebAuthorizationBroker.AuthorizeAsync` method after a successful login. Its location really does not matter as its location is maintained in the `UserCredential` object that is used, but it should be private to the user/app as it does contain the authorization scopes that where granted during login. – SushiHangover Mar 05 '18 at 03:25
  • This isn't a console app. I'm trying to get this working in Xamarin Forms. – Christian Findlay Mar 05 '18 at 03:47
  • I've used the native Google Cal APIs in Xamarin.iOS and Xamarin.Android and the Rest APIs in a Xamarin.Forms|iOS|Android|UWP. The last time I looked the Dotnet library did not support UWP (there are a bunch of SO question/answers related to this), but I have not check in quite a while as in the end, all the high-level Google Cal. SDKs use the Rest APIs, so that is what I use now (also like a lot of Google high-level SDKs, they do not expose 100% of the Rest APIs feature-set (Calendar / OneDrive SDKs are prime examples and if you need those missing features, you have to go to the REST API level – SushiHangover Mar 05 '18 at 03:56
  • The link you posted describes the steps for a console app, if you scroll down to the bottom there is a webapp link maybe that is what you need? – sramalingam24 Mar 05 '18 at 03:56
  • The sample is pretty silly because it is storing stuff in files, and hiding stuff from me. I'd rather use this library than REST. It's annoying that they've designed this library so badly. – Christian Findlay Mar 05 '18 at 04:04
  • @SushiHangover, have you got any sample code for using this with Android? It should theoretically be the same. I'd be happy with an Android sample working. – Christian Findlay Mar 05 '18 at 04:06
  • @MelbourneDeveloper I'll take a look to see if I can GitHub some of my prototypes and RestSharp-based wrapper I did (they were for various clients so I need to check based upon the NDA timelines...). It really comes down to doing an OAuth2 login (you can use Xamarin,Auth if you want) and then just calling the various Rest APIs... – SushiHangover Mar 05 '18 at 04:13
  • Thanks. I'm sure I can figure out the REST apis. I'd just rather get this library working. – Christian Findlay Mar 05 '18 at 04:24
  • @SushiHangover I've modified the question and included the best stab I can to get this working on UWP/Android. – Christian Findlay Mar 05 '18 at 05:05

2 Answers2

0

The repo for the .NET Client is here: https://github.com/google/google-api-dotnet-client

I think I will be able to figure this out from the source code, but I havenot yet attempted this.

Christian Findlay
  • 6,770
  • 5
  • 51
  • 103
0

I have the Google .NET Client working in my UWP app. The trick is that you have to put it in a .NET Standard 2.0 Class Library, expose the API services you need, and then reference that library from your UWP app.

Also, you have to handle the getting the auth token yourself. It's not that much work and the Drive APIs and Calendar APIs work just fine (the only ones I've tried). You can see that I pass in a simple class that contains the auth token and other auth details to a method called Initialize.

Here is the single class I used in the .NET Standard 2.0 class library:

namespace GoogleProxy
{
public class GoogleService
{
    public CalendarService calendarService { get; private set; }

    public DriveService driveService { get; private set; }


    public GoogleService()
    {

    }

    public void Initialize(AuthResult authResult)
    {
        var credential = GetCredentialForApi(authResult);
        var baseInitializer = new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = "{your app name here}" };

        calendarService = new Google.Apis.Calendar.v3.CalendarService(baseInitializer);
        driveService = new Google.Apis.Drive.v3.DriveService(baseInitializer);
    }

    private UserCredential GetCredentialForApi(AuthResult authResult)
    {
        var initializer = new GoogleAuthorizationCodeFlow.Initializer
        {
            ClientSecrets = new ClientSecrets
            {
                ClientId = "{your app client id here}",
                ClientSecret = "",
            },
            Scopes = new string[] { "openid", "email", "profile",  "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar.events.readonly", "https://www.googleapis.com/auth/drive.readonly" },
        };

        var flow = new GoogleAuthorizationCodeFlow(initializer);

        var token = new TokenResponse()
        {
            AccessToken = authResult.AccessToken,
            RefreshToken = authResult.RefreshToken,
            ExpiresInSeconds = authResult.ExpirationInSeconds,
            IdToken = authResult.IdToken,
            IssuedUtc = authResult.IssueDateTime,
            Scope = "openid email profile https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events.readonly https://www.googleapis.com/auth/drive.readonly",
            TokenType = "bearer" };

        return new UserCredential(flow, authResult.Id, token);
    }

}
}

Full answer for UWP here: https://stackoverflow.com/a/53971436/1938624

Lee McPherson
  • 931
  • 6
  • 20