Archivo de la etiqueta: tutorial

Soluciones a los ejercicios de Trucos para Xamarin.Forms y .NET MAUI en dispositivos

Pues eso… aquí tienes los ejercicios y las soluciones propuestos en el post Trucos para Xamarin.Forms y .NET MAUI en dispositivos. También los tienes disponibles en GitHub.

Los ejercicios eran:

Si quieres, como ejercicio de práctica, puedes hacer lo siguiente:

Ejercicio 1:

Añade un panel (StackLayout) con la orientación horizontal, de esa forma los controles que metas dentro de ese stack se mostrarán uno al lado del otro, pon un Switch seguido de un botón con el texto «Un Switch» y después otro Switch y otro botón con el texto «Otro Switch» (o lo que te de la gana pero que no sea demasiado largo).
En este caso el texto no pillará todo el ancho y verás que no se ve igual en iOS / iPhone que en Android o UWP/Windows.
Si no puedes probarlos en distintos dispositivos emuladores, te pondré la respuesta con algunas capturas de los tres sistemas que utilizo con Xamarin.Forms.

Nota:
Probando el ejercicio, compruebo que el poner más espacios al final, no tienen el efecto deseado, así que… si es iOS usa el valor 6,0 para la propiedad Padding.

Otra cosa interesante que puedes hacer (ejercicio 2) es que las etiquetas estén alineadas en el centro vertical, es decir, para que no se vean con el texto tan arriba, sino en el centro de la propia etiqueta.

Estas son las soluciones

Al ejercicio 1 y 2:

<StackLayout Orientation="Horizontal" Padding="4" Spacing="3">
    <Label Text="2 Switch con botones: " VerticalOptions="Center"/>
    <Switch />
    <!-- Los espacios detrás no se respetan... habrá que usar padding -->
    <Button Text=" Un Switch "
            Padding="{OnPlatform iOS='6,0', Android='6,0', Default=4}"/>
    <Switch />
    <Button Text=" Otro Switch "
            Padding="{OnPlatform iOS='6,0', Android='6,0', Default=4}"/>
</StackLayout>

Nota:
El valor ColorAzul2 está definido en App.xaml.

En ej ejercicio 2 lo que debes usar es: VerticalOptions="Center"

Capturas de los ejemplos

Aquí te dejo algunas capturas (me gusta que los posts tengan imágenes 😉 )

Ya sabes, pulsa en la imagen para verla en grande.

Figura 1. Página principal del proyecto en Android.

Figura 2. La solución a los ejercicios en Android.

Figura 3. Página principal del proyecto en iPhone.

Figura 4. Las soluciones en iPhone, pero en modo oscuro no se ve la etiqueta…

Figura 5. Solución a los ejercicios en iPhone (usando el tema claro).

Y esto es todo.

Lee el post de estos ejercicios porque hay cambios.

Nos vemos.
Guillermo

Trucos para Xamarin.Forms y .NET MAUI en dispositivos

Pues eso… te voy a relacionar varios trucos sobre cómo usar el código (y el código de marcado XAML) para dispositivos móviles (Android, iOS y UWP -Windows 10-) usando Xamarin.Forms (Mono mobile) y seguramente servirá para .NET MAUI, la que será el sustituto de Xamarin cuando .NET 6.0 esté en fase release (sobre noviembre de este año 2021).

Nota:
El código mostrado a la hora de escribir este post (2 de junio de 2021 a las 11:15) es para Xamarin.Forms y C#.
Cuando tenga ejemplos de .NET MAUI lo indicaré si es diferente al de Xamarin.

Los trucos

El código con los ejemplos, ejercicios y soluciones están publicados en GitHub.

  1. Saber la plataforma (Android, iOS o UWP) y el tipo de dispositivo (Phone, Tablet, TV, Watch, Desktop)
  2. Poner varios valores en el código XAML usando OnPlatform, OnIdiom, etc.
    1. Ejercicio 1
    2. Ejercicio 2
  3. Si al crear un proyecto no te aparecen los emuladores ni dispositivos
  4. Acceder a los recursos definidos en ResourceDictionary o Application.Resources desde código (C#) (en otro post del 14-jun-2021)

Saber la plataforma (Android, iOS o UWP) y el tipo de dispositivo (Phone, Tablet, TV, Watch, Desktop)

Algunas veces es conveniente saber si la aplicación está funcionando en un dispositivo Android, iOS o compatible con UWP (Windows 10 escritorio o móvil, Xbox, etc.), tanbién si es un móvil, escritorio, tablet, etc.

La primera (la plataforma) se averigua con Platform y OnPlatform y los valores aceptados son los indicados en la tabla 1 (de la ayuda de Xamarin.Forms) que corresponden al tipo DevicePlatform:

La segunda (el tipo de dispositivo) se averigua con Idiom y OnIdiom y los valores de DeviceIdiom que son los que te muestro en la tabla 2.

Cuando usamos Platform o Idiom lo haremos por código usando la clase DeviceInfo (definida en Xamarin.Essentials).
OnPlatform y OnIdiom lo usaremos en el código XAML.
Ahora veremos ejemplos de código c# y XAML de estos dos casos.

AndroidGets the Android platform.
iOSGets the iOS platform.
macOSMac OS
TizenGets the Tizen platform
tvOSGets the tvOS platform
UnknownGets the unknown platform.
UWPGets the UWP platform.
watchOSGets the watchOS platform
Tabla 1. Valores de la estructura DevicePlatform.

DesktopGets the desktop idiom.
PhoneGets the phone idiom.
TabletGets the tablet idiom.
TVGets the TV idiom.
UnknownGets the unknown idiom.
WatchGets the watch idiom.
Tabla 2. Valores de la estructura DeviceIdiom.

Código de ejemplo

Por código podemos usar DeviceInfo (habrá que importar el espacio de nombres Xamarin.Essentials) y las estructuras DevicePatform o DeviceIdiom según lo que queramos averiguar.

Nota:
En estos ejemplos estoy usando una página sencilla de tipo ContentPage de Xamarin.
En la que estarán definidos los controles usados en el código.

Por ejemplo, si queremos saber si el código está ejecutándose en un dispositivo UWP (por ejemplo, en Windows 10) o en Andoid o en iOS podemos hacer lo siguiente comprobando la propiedad Platform del tipo DeviceInfo:

// Saber la plataforma
if (DeviceInfo.Platform == DevicePlatform.UWP)
    LabelDevicePlatform.Text = "Estás usando la plataforma UWP";
else if (DeviceInfo.Platform == DevicePlatform.Android)
    LabelDevicePlatform.Text = "Estás usando la plataforma Android";
else if (DeviceInfo.Platform == DevicePlatform.iOS)
    LabelDevicePlatform.Text = "Estás usando la plataforma iOS";

Lo mismo te interesa saber si se está ejecutando en un teléfono móvil una tableta o en el escritorio, para eso usaremos la propiedad Idiom de la clase DeviceInfo:

// Saber el tipo de dispositivo
if (DeviceInfo.Idiom == DeviceIdiom.Desktop)
    LabelDeviceIdiom.Text = "Estás usando el escitorio.";
else if (DeviceInfo.Idiom == DeviceIdiom.Phone)
    LabelDeviceIdiom.Text = "Estás usando un teléfono móvil.";
else if (DeviceInfo.Idiom == DeviceIdiom.Tablet)
    LabelDeviceIdiom.Text = "Estás usando una tableta.";

Pero puede ser que lo que queremos es asignar un color según el código esté funcionando en una de las plataformas. En lugar de hacerlo por código, lo podemos hacer definiendo esa característica en el diseñador XAML.

Vamos a verlo aplicando un color al fondo de la etiqueta LabelDevicePlatform definida en el ejemplo.

<Label x:Name="LabelDevicePlatform" Padding="6"
       TextColor="White"
       BackgroundColor="{OnPlatform Android=#FA3F7E, iOS=#34C759, UWP=#0077DF}" />

En el código XAML es donde usaremos OnPlatform.

De igual forma podemos usar OnIdiom para saber el tipo de dispositivo.

<Label x:Name="LabelDeviceIdiom" Padding="6" 
       TextColor="{OnPlatform Android=Black, iOS=Blue, UWP=Green}"
       BackgroundColor="Wheat"
       FontSize="{OnIdiom Tablet=Small, Phone=Micro, Desktop=Medium}"/>

En ese código XAML hacemos las dos comprobaciones, la plataforma y el dispositivo y según sea aplicaremos un color u otro.

Si quieres hacer solo esa comprobación, por ejemplo para que en dispositivos de Apple (iOS) tengan un color y que en el resto tenga otro, podemos hacerlo de esta forma:

<Label Text="DeviceIdiom: " TextColor="{OnPlatform iOS=Green, Default=Red}"/>

En ese caso usamos el valor (o propiedad) Default que se aplicará a las plataformas (o dispositivos si se usa con OnIdiom) para las que no haya un valor concreto.

Poner varios valores en el código XAML usando OnPlatform, OnIdiom, etc.

Otra de las cosas que me ha pasado mientras estoy con esto de crear aplicaciones para dispositivos móviles y concretamente con el iPhone (iOS), en el que el texto mostrado en uno de los tipos de botones que estoy usando (para simular un Switch) es que el texto se queda pegado a los bordes del botón, mientras que en Android y UWP se quedaba separado.

Para solucionarlo necesitaba hacer dos cosas:
1- La más simple es poder añadir espacios delante y detrás del texto. Esto sería fácil si esa asignación la hago por código (c#), pero yo quería hacerla en el código de diseño (XAML).
2- Cambiar el Padding izquierdo y derecho.

El problema es que para el primer caso, tenía que asignar espacios delante y detrás del texto, pero ni usando el valor 255 (ALT+255) lo conseguí, ya que al ejecutar el código esos espacios se ignoran .

En el segundo caso, es que al asignar valores separados por comas da error, ya que después de cada coma se espera una propiedad.

Por ejemplo, lo del espacio.
Fíjate en el siguiente código XAML el valor del texto (propiedad Text) lo asigno usando un valor diferente según la plataforma.
Si es iOS uso un espacio delante y detrás de la palabra ATRÁS, en el resto de plataformas (Default) use ese mismo texto pero sin espacios:

<Button x:Name="btnAtras" 
        Text="{OnPlatform iOS= ATRÁS , Default=ATRÁS}" 
        HorizontalOptions="FillAndExpand"
        Style="{StaticResource BtnNavegar}"
        Clicked="btnAtras_Clicked"/>

Y si ese texto llevase comas, pues… ni se podría poner.

Ese sería el caso para cuando queremos asignar un valor que tenga varios argumentos separados por comas, como es el caso de Padding si no queremos asignar un solo valor para los 4 costados. Ya que solo me interesaba para la parte iaquierda y derecha, que si lo asignase de forma normal sería algo así:

Padding="6,0"

El primer valor se asignará a la izquierda y derecha (Left y Right) y el segundo valor arriba y abajo (Top y Botton).
Que queremos asignar 4 valores diferentes, lo haremos así:

Margin="4,5,6,7"

Los valores serían: 4 para la izquierda, 5 para arriba, 6 para la derecha y 7 para abajo.

La solución: Poner el texto entre comillas simples (las dobles no se pueden usar, ni siquiera usando teclas de ESCAPE, algo así: \»).

Por cierto, esa solución también es válida para el código HTML, cuando quieres poner comillas dentro de unas comillas, por ejemplo un valor style en el texto asignado a algo mediante código. Pero esa es otra historia.

Dicho lo dicho, la cosa quedaría de esta forma, si lo que se busca es que el padding izquierdo y derecho sea 6 para la plataforma iOS y 0 para el resto y que el texto incluya comas o espacios delante y/o detrás, si esos espacios están entro otros caracteres no hay que hacer nada especial.

<Label x:Name="LabelStatus" Style="{StaticResource LabelStatusWiz}"
       Padding="{OnPlatform iOS='6,0', Default=0}"
       Text="{OnPlatform Android=' Dispositivo \'Android\' ', 
                         iOS=' Dispositivo iPhone (\'iOS\') ', 
                         UWP='Dispositivo UWP (\'Windows 10, XBox\')'}"/>

Como ves el valor del Padding para iOS es ‘6,0’ (entre comillas simples).
Fíjate también que en ese código (para el valor de la propiedad Text) he usado \’ para indicar que quiero usar una comilla simple, esto solo funcionará si el texto completo está encerrado entre comillas simples.

Aquí tienes unas capturas de UWP: Windows 10 en el equipo local o lo que es lo mismo, usando el escritorio (figura 1), para Android (figura 2) y para iOS en un iPhone 7 Plus (figuras 3 y 4).

Observa los colores de las etiquetas (fondo y texto), así como en la parte de abajo que hay texto entre comillas simples.

Y observa que el padding para el botón ese con el texto tan largo, pues como que no sirve de mucho, ya que el botón pilla todo el ancho de la pantalla, pero sería útil si ese botón se está mostrando en un sitio que no lo expanda.

Si quieres, como ejercicio de práctica, puedes hacer lo siguiente:

Ejercicio 1:

Añade un panel (StackLayout) con la orientación horizontal, de esa forma los controles que metas dentro de ese stack se mostrarán uno al lado del otro, pon un Switch seguido de un botón con el texto «Un Switch» y después otro Switch y otro botón con el texto «Otro Switch» (o lo que te de la gana pero que no sea demasiado largo).
En este caso el texto no pillará todo el ancho y verás que no se ve igual en iOS / iPhone que en Android o UWP/Windows.
Si no puedes probarlos en distintos dispositivos emuladores, te pondré la respuesta con algunas capturas de los tres sistemas que utilizo con Xamarin.Forms.

Nota:
Probando el ejercicio, compruebo que el poner más espacios al final, no tienen el efecto deseado, así que… si es iOS usa el valor 6,0 para la propiedad Padding.

Otra cosa interesante que puedes hacer (ejercicio 2) es que las etiquetas estén alineadas en el centro vertical, es decir, para que no se vean con el texto tan arriba, sino en el centro de la propia etiqueta.

Pulsa en las imágenes para verlas a tamaño real.

Figura 1. El ejemplo en un Windows 10 (UWP).

Figura 2. El ejemplo en un móvil de Android (Pixel 4a).

Figura 3. El ejemplo en un móvil iOS (iPhone 7 Plus). Observa que no se ve todo el texto.

Figura 4. El ejemplo en un móvil iOS (iPhone 7 Plus).

Si miras las capturas 3 y 4 verás que en la última ya se ve bien el texto, tanto de la etiqueta de arriba como en la que tiene el fondo verde (la que indica que estás usando la plataforma iOS), para solucionar esa error, he vuelto a usar lo comentado en este post.

En el caso de la etiqueta de Xamarin he ajustado el valor de Padding para el iOS que sea más pequeño que para el resto.

<Label Text="Welcome to Xamarin.Forms!" FontSize="Title" 
       BackgroundColor="#96d1ff" Padding="{OnPlatform iOS=10,Default=40}"
       VerticalOptions="CenterAndExpand" 
       HorizontalOptions="CenterAndExpand" />

Para la etiqueta donde se muestra la plataforma en la que se está usando la aplicación, he optado por cambiar el tamaño de la letra.

<Label x:Name="LabelDevicePlatform" Padding="6"
       FontSize="{OnPlatform iOS=Micro, Default=Small}"
       TextColor="White"
       BackgroundColor="{OnPlatform Android=#FA3F7E, iOS=#34C759, UWP=#0077DF}" />

Nota importante sobre los valores asignados cuando usamos OnPlatform, OnIdiom, etc.
Cuando usamos, por ejemplo, OnPlatform el Intellisense de Visual Studio (en este ejemplo estoy usando Microsoft Visual Studio Community 2019 Version 16.10.0) te muestra los valores de la plataforma (iOS, Android, UWP, Default, etc.), pero no te muestra el intellisense para los valores asignados.
Por ejemplo, en el valor de FontSize del código XAML anterior, he usado el valor Micro para iOS y Small para el resto (Default), esos valores hay que ponerlos como se deben poner, me explico: si el valor es Micro con la m mayúscula, debe estar así escrito, si pones micro (con la m en minúscula) te dará error… Esto lo sé porque lo escribí en minúscula y al ejecutar el programa en el iPhone me dijo que nones… 😉

Si al crear un proyecto no te aparecen los emuladores ni dispositivos

Pues eso… he creado un nuevo proyecto para Xamarin.Forms para estos ejemplos y ejercicios y al intentar probarlo en otra plataforma distinta a UWP, vamos al intentar probarlo en Android o iPhone no me salían las opciones de los emuladores o los dispositivos.

¿La solución?
Cerrar la solución y volverla a abrir, así de simple.

En la página de las soluciones a los ejercicios tienes las capturas del proyecto que he creado y que está publicado en GitHub (aquí más abajo tienes el enlace).

Y esto es todo.

Solo falta publicar el proyecto en GitHub, pero como lo he usado en la aplicación que tengo para las reservas de MKN (la empresa donde trabajo), tendré que crear un proyecto/solución para poder ponerlo en GitHub y así poder descargarlo, etc.
Ya avisaré. Avisado estás de que ya está publicado en GitHub 😉
La solución a los dos ejercicios las pondré en otro post y también estarán en GitHub.

El código en GitHub

elGuille-Ejemplos

Nos vemos.
Guillermo

Tutorial Google APIs – Código para acceder a los contactos con People API

Ya estás en la segunda entrega de este Tutorial sobre el uso de las APIs de Google.
Aquí te mostraré el código de Visual Basic y C# para acceder a los contactos de Google usando People API. Como te comenté en la priumera entrega sobre cómo crear un proyecto en Google Cloud, el API que usaremos es People API, no te confundas con Contacts API (como a mí me ocurrió) ya que parece que será la más adecuada, pero además de que Google no recomienda que se use Contacts API, la he probado y no he conseguido sacar prácticamente nada de información, y al que he sacado es de unos pocos contactos (que ni recordaba que los tenía creados).

Nota:
Está uno acostumbrado a que te marquen los errores horrográficos que esto de que no te los marque es un Orror! 😛

Bueno vamos empezar con el código de C# que es lo que la gente suele ver en internet (de VB no he visto ni un ejemplo, así que… ya mismo lo verás en primicia).

Después de escribir el párrafo anterior he cambiado de opinión y te voy a enseñar conjuntamente el código de los dos lenguajes, para que vayas comparando.

Código de C# y VB para mostrar los contactos de una cuenta de Google usando People API

Nota:
Debo decir que parte de este código lo he visto en algunos ejemplosencontrados tanto en la documentación de Google como en foros de C#. Pero principalmente de la documentación (escasa) de Google y en agunos casos, los ejemplos eran de java, ni siquiera de C#.

Como te comenté en el preámbulo del tutorial, debes añadir las referencias a los paquetes de NuGet que instalarán las APIs en tu proyecto. Yo he añadido todas las que te indiqué en esa primera página del tutorial. Por supuesto si quieres acceder, por ejemplo al calendario o a GMail, tendrás que añadir referencias a esas APIs, además de añadirlas al proyecto creado en Google Cloud Platform.

Veamos el código inicial (con las importaciones de los espacios de nombres) que será básicamente el mismo en todas las aplicaciones que hagas para acceder a las APIs de Google.

// Genéricas
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Util.Store;
// People API
using Google.Apis.People.v1;
using Google.Apis.People.v1.Data;
// Docs API
using Google.Apis.Docs.v1;
using Google.Apis.Docs.v1.Data;
// Drive API
using Google.Apis.Drive.v3;
using Google.Apis.Drive.v3.Data; // Para el tipo File

using System;
using System.Collections.Generic;
//using System.IO;
using System.Threading;
using System.Text;
using System.Linq;
'// Genéricas
Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Services
Imports Google.Apis.Util.Store
'// People API
Imports Google.Apis.People.v1
Imports Google.Apis.People.v1.Data
'// Docs API
Imports Google.Apis.Docs.v1
Imports Google.Apis.Docs.v1.Data
'// Drive API
Imports Google.Apis.Drive.v3
Imports Google.Apis.Drive.v3.Data '// Para el tipo File

Imports System
Imports System.Collections.Generic
Imports System.Threading
Imports System.Text
Imports System.Linq

Ahora vamos a crear una serie de campos estáticos/compartidos (static en C#, shared en VB) que usaremos también de forma genérica en todos los proyectos de acceso a Google APIs.

class Program
{
    // If modifying these scopes, delete your previously saved credentials
    // at ~/.credentials/docs.googleapis.com-dotnet-quickstart.json
    static string[] Scopes = { DocsService.Scope.Documents, 
                               DocsService.Scope.DriveFile, 
                               PeopleService.Scope.ContactsReadonly };

    static string ApplicationName = "Tutorial Google APIs VB";

    // Los datos del proyecto creado para VB
    static ClientSecrets secrets = new ClientSecrets()
    {
        ClientId = "430211665266-t68vl99t2q40v3lbctgbph23j2644bpj.apps.googleusercontent.com",
        ClientSecret = "Xqexl0FMPedNc1KYs0iJt22A"
    };

    static DocsService docService;
    static DriveService driveService;
    static PeopleService peopleService;

    static UserCredential Credential;
Class Program

    Shared Scopes As String() =
        {
            DocsService.Scope.Documents,
            DocsService.Scope.DriveFile,
            PeopleService.Scope.ContactsReadonly
        }

    Shared ApplicationName As String = "Tutorial Google APIs VB"

    Shared secrets As ClientSecrets = New ClientSecrets() With
        {
            .ClientId = "430211665266-t68vl99t2q40v3lbctgbph23j2644bpj.apps.googleusercontent.com",
            .ClientSecret = "Xqexl0FMPedNc1KYs0iJt22A"
        }

    Shared docService As DocsService
    Shared driveService As DriveService
    Shared peopleService As PeopleService

    Shared Credential As UserCredential

Lo que ese primer comentario indica (es de un quickstart de Google) es que si cambias los valores del array Scopes debes eliminar el directorio creado en Documentos\.credentials (ahora verás porqué).

El array Scopes contiene los ámbitos de la aplicación, es a qué estamos pidiendo permiso para acceder. Ahí he añadido los tres ámbitos que usaré en este tutorial, pero si solo quieres acceder a los contactos con People API, añadiendo PeopleService.Scope.ContactsReadonly sería suficiente. Si te fijas, estás pidiendo autorización (a OAuth) para acceder de forma de solo lectura a los contactos.
En realidad, estás pidiendo permisos para acceder a los Documentos y al Drive sin restricciones, pero a los contactos solo para leerlos. Estos dos últimos necesitan más permisos porque queremos crear ficheros, eliminarlos, lo mismo con los documentos.

Lo importante (también) es la asignación de ClientSecrets, ahí tendrás que poner los datos que te haya generado al configurar el OAuth de la aplicación. Eso que te muestro son los valores que en mi proyecto para este tutorial he pedido, el que se indique que la aplicación se llama Tutorial Google APis VB no significa que no se pueda usar para C#, ese título o nombre de aplicación no es restrictivo para nada.

Nota:
Normalmente esos valores se recomiendan que no se hagan públicos… ¡vaya el caso que hago! jejejeje… pero para este caso, me fio de ti, y sé que no se lo dirás a nadie y así lo mantenemos en secreto… 😉

Las tres declaraciones después de asignar el valor a secrets son para acceder a los servicios de las tres APIs que usaré en estos tutoriales, las defino a nivel de clase para que se puedan usar en todos los métodos que sean necesarios y no tener que pasarlas como argumento de llamada al método que lo necesite.

La última declaración (Credential) es para crear las credenciales del usuario. La asignación la haré en el método Main, y como es un valor compartido, también se podrá usar en cualquier parte de la clase.

El siguiente código es el método Main, en el que llamaremos al método que accede a los contactos y los muestra.

Y como verás en las capturas el correo que he usado no tenía ningún contacto y he tenido que crear uno para comprobar que funciona… las cosas del Guille…

static void Main(string[] args)
{
    Console.WriteLine("Tutorial Google APIs con C#");

    string credPath = System.Environment.GetFolderPath(
        Environment.SpecialFolder.Personal);
    
    // Directorio donde se guardarán las credenciales
    credPath = System.IO.Path.Combine(credPath, ".credentials/Tutorial-Google-APIs-VB");

    Credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
        secrets,
        Scopes,
        "user",
        CancellationToken.None,
        new FileDataStore(credPath, true)).Result;
    //Console.WriteLine("Credential file saved to: " + credPath);

    // Mostrar los contactos
    MostrarContactos();

    Console.WriteLine();
    Console.WriteLine("Pulsa una tecla.");
    Console.Read();
}
Public Shared Sub Main(ByVal args As String())

    Console.WriteLine("Tutorial Google APIs con Visual Basic")

    Dim credPath As String = System.Environment.GetFolderPath(
        Environment.SpecialFolder.Personal)

    '// Directorio donde se guardarán las credenciales
    credPath = System.IO.Path.Combine(credPath, ".credentials/Tutorial-Google-APIs-VB")

    Credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
        secrets,
        Scopes,
        "user",
        CancellationToken.None,
        New FileDataStore(credPath, True)).Result

    '// Mostrar los contactos
    MostrarContactos()

    Console.WriteLine()
    Console.WriteLine("Pulsa una tecla.")
    Console.Read()
End Sub

El valor de credPath es el direcorio donde se crearán las credenciales que vamos a usar en este programa. Se guardará en los documentos del usuario en una carpeta llamada Tutorial-Google-APIs-VB que estará incluida en .credentials.

Si haces cambios en los permisos o te han faltado permisos en la configuración de OAuth y los modificas, debes eliminar la carpeta del tutorial, a de .credentials la puedes dejar, por si la usas para almacenar las credenciales de otros programas que use el usuario.
También tendrás que borrarla si decides usar otra cuenta de Google (GMail) para acceder a esos contactos. Si solo tienes una cuenta de GMail, no hará falta que la borres salvo para los dos primeros casos.

Cuando ejecutemos la aplicación verás qué es lo que se guarda en esa carpeta.

A Credentials le asignamos las credenciales que queremos usar.

Aquí tengo que hacer una puntualización.
El valor de secrets se asigna con los valores que hemos indicado (de forma llana y clara) antes, pero también se puede hacer accediendo a un fichero que contiene esos valores, al menos así no se mostrarán directamente en el código los valores secretos. Aunque seguirán estando al alcance del usuario de la aplicación.

La forma de hacerlo lo pondré en otro ejemplo de este tutorial.

Ahora solo nos falta ver qué código contiene el método MostrarContactos.

¡Vamos a ello!

private static void MostrarContactos()
{
    // Create Drive API service.
    peopleService = new PeopleService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = Credential,
        ApplicationName = ApplicationName,
    });

    // Lista de los contactos (People)
    Console.WriteLine("Contactos:");

    // Este muestra todos los contactos
    GetPeople(peopleService, null);

    Console.WriteLine();
    Console.WriteLine($"Hay {total} contactos / People");
    Console.WriteLine();
}
Private Shared Sub MostrarContactos()
    peopleService = New PeopleService(New BaseClientService.Initializer() With
    {
        .HttpClientInitializer = Credential,
        .ApplicationName = ApplicationName
    })

    Console.WriteLine("Contactos:")

    GetPeople(peopleService, Nothing)

    Console.WriteLine()
    Console.WriteLine($"Hay {total} contactos / People")
    Console.WriteLine()
End Sub

Le asignamos el valor a peopleService para que pida esos permisos a Google, como ves se usa el protocolo HTTP.
Eso lo que hará es abrir una página en el navegador pidiendo permiso para acceder a los datos de una cuenta de GMail (ver la figura 1)

Figura 1. Seleccionar la cunta que queremos usar para acceder a sus contactos.

Esa primera pantalla nos pide que indiquemos a qué contactos queremos acceder, es decir, solo accederá a los contactos de la cuenta que indiques, y para indicar esa cuenta debes tener el password, si no… no hay nada que hacer.
Por tanto, esto que vamos a hacer es seguro, en el sentido de que yo no voy a acceder (ni puedo) a tus contactos… otra cosa es que mi aplicación se comunicara conmigo o guardara esa información en algún sitio… pero no te preocupes, no lo hace, además lo puedes comprobar por ti mismo viendo el código.

Una vez elegida la cuenta, te mostrará un aviso de que la aplicación no es segura o, en realidad, que no se ha verificado (ver la figura 2).
Tendrás que autorizarla para que pueda seguir.

Figura 2. DEbes mostrar la información oculta para permitir ir a la aplicación

Una vez hecho esto, Google te pedirá confirmación para dejar que la aplicación haga lo que se ha indicado en Scopes. Así que… otras tres pantallitas más de autorización (ver figuras 3 a 5).

Figura 3. Permiso para ver los contactos.
Figura 4. Acceder al Drive.
Figura 5. Acceder a los documentos.

Y una vez permitido estas cosas, debes terminar de aceptarlo (ver la figura 6 en la que está el resumen de los permisos que le darás a la aplicación).

Figura 6. Confirmación final de los permisos.

Después de pulsar en Permitir, el navegador te mostrará un mensaje de que puedes cerrar la página.

Estos permisos no te los volverá a pedir para usar esta aplicación. Y, por tanto, siempre accederá a la cuenta de GMail que has indicado.

Si quieres usar otra cuenta diferente, borra la carpeta esa que te comenté antes (o el fichero que contiene) y eso hará que te vuelva a mostrar todas estas pantallas.

Antes de seguir, espera un poco que me estoy meando de la risa… ¡ay!

Es que te dije que definí la variable peopleService (y las otras dos) a nivel de clase para no tener que usarla como argumento a un método y voy y pongo como argumento el valor de esa variable compartida… en fin… la cabeza del Guille…

Bueno, ya.

Sigamos con el código que accede a los contactos.

private static int total = 0;

static void GetPeople(PeopleService service, string pageToken)
{
    // Define parameters of request.
    PeopleResource.ConnectionsResource.ListRequest peopleRequest =
            service.People.Connections.List("people/me");

    //
    // Lista de campos a usar en RequestMaskIncludeField:
    // https://developers.google.com/people/api/rest/v1/people/get
    //

    peopleRequest.RequestMaskIncludeField = new List<string>()
            {"person.names", "person.phoneNumbers", "person.emailAddresses",
              "person.birthdays", "person.Addresses"
            };


    if (pageToken != null)
    {
        peopleRequest.PageToken = pageToken;
    }

    ListConnectionsResponse people = peopleRequest.Execute();

    if (people != null && people.Connections != null && people.Connections.Count > 0)
    {
        total += people.Connections.Count;
        foreach (var person in people.Connections)
        {
            Console.Write(person.Names != null ? ($"{person.Names.FirstOrDefault().DisplayName} - ") : "");
            Console.Write(person.PhoneNumbers != null ? ($"{person.PhoneNumbers.FirstOrDefault().Value} - ") : "");
            Console.Write(person.EmailAddresses != null ? ($"{person.EmailAddresses.FirstOrDefault().Value} - ") : "");
            Console.Write(person.Addresses != null ? ($"{person.Addresses.FirstOrDefault()?.City} - ") : "");
            if (person.Birthdays != null)
            {
                var fecha = "";
                var b = person.Birthdays.FirstOrDefault()?.Date;
                if (b != null)
                    fecha = $"{b.Day}/{b.Month}/{b.Year}";
                Console.Write($"{fecha}");
            }
            Console.WriteLine();
        }

        if (people.NextPageToken != null)
        {
            Console.WriteLine();
            Console.WriteLine($"{total} contactos mostrados hasta ahora. Pulsa una tecla para seguir mostrando contactos.");
            Console.WriteLine();
            Console.ReadKey();

            GetPeople(service, people.NextPageToken);
        }
    }
    else
    {
        Console.WriteLine("No se han encontrado contactos.");
        return;
    }
}
Private Shared total As Integer = 0

Private Shared Sub GetPeople(service As PeopleService, pageToken As String)

    Dim peopleRequest As PeopleResource.ConnectionsResource.ListRequest =
        service.People.Connections.List("people/me")

    peopleRequest.RequestMaskIncludeField = New List(Of String)() From {
            "person.names",
            "person.phoneNumbers",
            "person.emailAddresses",
            "person.birthdays",
            "person.Addresses"
        }

    If pageToken IsNot Nothing Then
        peopleRequest.PageToken = pageToken
    End If

    Dim people As ListConnectionsResponse = peopleRequest.Execute()

    If people IsNot Nothing AndAlso
            people.Connections IsNot Nothing AndAlso
            people.Connections.Count > 0 Then
        total += people.Connections.Count

        For Each person In people.Connections
            Console.Write(If(person.Names IsNot Nothing,
                          ($"{person.Names.FirstOrDefault().DisplayName} - "), ""))
            Console.Write(If(person.PhoneNumbers IsNot Nothing,
                          ($"{person.PhoneNumbers.FirstOrDefault().Value} - "), ""))
            Console.Write(If(person.EmailAddresses IsNot Nothing,
                          ($"{person.EmailAddresses.FirstOrDefault().Value} - "), ""))
            Console.Write(If(person.Addresses IsNot Nothing,
                          ($"{person.Addresses.FirstOrDefault()?.City} - "), ""))

            If person.Birthdays IsNot Nothing Then
                Dim fecha = ""
                Dim b = person.Birthdays.FirstOrDefault()?.Date
                If b IsNot Nothing Then fecha = $"{b.Day}/{b.Month}/{b.Year}"
                Console.Write($"{fecha}")
            End If

            Console.WriteLine()
        Next

        If people.NextPageToken IsNot Nothing Then
            Console.WriteLine()
            Console.WriteLine($"{total} contactos mostrados hasta ahora. " &
                              "Pulsa una tecla para seguir mostrando contactos.")
            Console.WriteLine()
            Console.ReadKey()
            GetPeople(service, people.NextPageToken)
        End If
    Else
        Console.WriteLine("No se han encontrado contactos.")
        Return
    End If
End Sub

El valor de peopleRequest debe ser siempre people/me o bien si te sabes el ID de otro usuario lo podrías indicar en lugar de «me«. Ese me significa que accedes a tus contactos.
He buscado por todas partes, y no he averiguado como saber ese famoso ID, ni siquiera de una de mis cuentas para poder saber si se puede acceder a la cuenta de alguien que no seas tú.
Cuando lo averigüe te lo cuento.

Los valores asignados a la lista RequestMaskIncludeField serán a los datos que queremos acceder y siempre deben empezar con person. seguido del campo al que queremos acceder. En este ejemplo estamos indicando que queremos acceder a: los nombres, teléfonos, emails, cumpleaños y domicilios.

Y como esos valores pueden ser nulos (y normalmente son una colección de valores), comprobamos si es nulo y en el caso de las colecciones accedemos al primero o al predeterminado, si hay algo lo mostramos y si no, se usa una cadena vacía.

Con el valor de Birtdays debemos hacer una comprobación más elaborada e incluso te recomendaría que usaras un try/catch si quieres acceder a los valores de la fecha, porque de las pruebas que he heco, en un caso me dio error al querer acceder al año… se ve que el contacto ese no quería dar su año de nacimiento y dio error al querer convertirlo a entero… en fin…

El NextPageToken nos indica que hay más páginas de datos, así que… si las hay volvemos a llamar a este método y seguimos mostrando los contactos que falten por acceder.

Y esto es todo lo que hay que hacer para mostrar los contactos usando People API. Otra cosa es que quieras crear contactos o modificarlos, en esos casos tendrás que modificar el valor del Scope de los contactos.
Si hago pruebas de crear o modificar te los pondré por aquí.

Para terminar, la captura del programa en funcionamiento. Ver la figura 7.

Figura 7. La cuenta que he usado para acceder a los contactos, ¡no tiene contactos!

Pues la sorpresa que me llevé… pensé que lo mismo fallaba algo, así que… me fui a google, creé un contacto y volvía a probar… ¡Esta vez sí que sí! (ver la figura 8).

Figura 8. Un contacto tengo en la cuenta que he usado para esta prueba.

Y esto es todo por ahora.

Ahora a esperar a la siguiente entrega.

Este código está publicado en GitHub: Tutorial-APIs-Google.

Nos vemos.
Guillermo

P.S.
Versión en inglés / English version.