Calendario de Xamarin.Android

API de calendario

Un nuevo conjunto de API de calendario introducidas en Android 4 admite aplicaciones diseñadas para leer o escribir datos en el proveedor de calendarios. Estas API admiten una gran cantidad de opciones de interacción con datos de calendario, incluida la capacidad de leer y escribir eventos, asistentes y recordatorios. Con el proveedor de calendarios de la aplicación, los datos que agregue a través de la API aparecerán en la aplicación de calendario integrada que viene con Android 4.

Adición de permisos en curso

Al trabajar con las nuevas API de calendario en la aplicación, lo primero que debe hacer es agregar los permisos adecuados al manifiesto de Android. Los permisos que necesita agregar son android.permisson.READ_CALENDAR y android.permission.WRITE_CALENDAR, en función de si está leyendo o escribiendo datos del calendario.

Uso del contrato de calendario

Una vez establecidos los permisos, puede interactuar con los datos del calendario mediante la clase CalendarContract. Esta clase proporciona un modelo de datos que las aplicaciones pueden usar cuando interactúan con el proveedor de calendario. CalendarContract permite a las aplicaciones resolver los URI en entidades de calendario, como calendarios y eventos. También proporciona una manera de interactuar con varios campos de cada entidad, como el nombre y el identificador de un calendario, o la fecha de inicio y finalización de un evento.

Echemos un vistazo a un ejemplo que usa la API de calendario. En este ejemplo, examinaremos cómo enumerar calendarios y sus eventos, así como cómo agregar un nuevo evento a un calendario.

Enumerar calendarios

En primer lugar, vamos a examinar cómo enumerar los calendarios que se han registrado en la aplicación de calendario. Para ello, podemos crear una instancia de CursorLoader. Introducido en Android 3.0 (API 11), CursorLoader es la manera preferida de consumir un ContentProvider. Como mínimo, es necesario especificar el URI de contenido para los calendarios y las columnas que queremos devolver; esta especificación de columna se conoce como proyección.

Llamar al método CursorLoader.LoadInBackground nos permite consultar un proveedor de contenido para obtener datos, como el proveedor de calendario. LoadInBackground realiza la operación de carga real y devuelve un Cursor con los resultados de la consulta.

CalendarContract nos ayuda a especificar tanto el contenido Uri como la proyección. Para obtener el contenido Uri para consultar calendarios, simplemente podemos usar la propiedad CalendarContract.Calendars.ContentUri como esta:

var calendarsUri = CalendarContract.Calendars.ContentUri;

Usar CalendarContract para especificar qué columnas de calendario queremos es igualmente sencillo. Solo agregamos campos de la clase CalendarContract.Calendars.InterfaceConsts a una matriz. Por ejemplo, el código siguiente incluye el identificador del calendario, el nombre para mostrar y el nombre de la cuenta:

string[] calendarsProjection = {
    CalendarContract.Calendars.InterfaceConsts.Id,
    CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName,
    CalendarContract.Calendars.InterfaceConsts.AccountName
};

Es importante incluir Id si usa un SimpleCursorAdapter para enlazar los datos a la interfaz de usuario, como veremos en breve. Con el URI de contenido y la proyección en su lugar, se crea una instancia de CursorLoader y se llama al método CursorLoader.LoadInBackground para devolver un cursor con los datos del calendario, como se muestra a continuación:

var loader = new CursorLoader(this, calendarsUri, calendarsProjection, null, null, null);
var cursor = (ICursor)loader.LoadInBackground();

La interfaz de usuario de este ejemplo contiene un ListView, con cada elemento de la lista que representa un único calendario. El siguiente XML muestra el marcado que incluye ListView:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
  <ListView
    android:id="@android:id/android:list"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" />
</LinearLayout>

Además, es necesario especificar la interfaz de usuario para cada elemento de lista, que colocamos en un archivo XML independiente de la siguiente manera:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
  <TextView android:id="@+id/calDisplayName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="16dip" />
  <TextView android:id="@+id/calAccountName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="12dip" />
</LinearLayout>

Desde este punto, es solo un código Android normal para enlazar los datos del cursor a la interfaz de usuario. Usaremos un SimpleCursorAdapter como se indica a continuación:

string[] sourceColumns = {
    CalendarContract.Calendars.InterfaceConsts.CalendarDisplayName,
    CalendarContract.Calendars.InterfaceConsts.AccountName };

int[] targetResources = {
    Resource.Id.calDisplayName, Resource.Id.calAccountName };      

SimpleCursorAdapter adapter = new SimpleCursorAdapter (this,
    Resource.Layout.CalListItem, cursor, sourceColumns, targetResources);

ListAdapter = adapter;

En el código anterior, el adaptador toma las columnas especificadas en la matriz sourceColumns y las escribe en los elementos de la interfaz de usuario de la matriz targetResources para cada entrada de calendario en el cursor. La actividad usada aquí es una subclase de ListActivity; incluye la propiedad ListAdapter a la que se establece el adaptador.

Esta es una captura de pantalla que muestra el resultado final, con la información del calendario mostrada en ListView:

CalendarDemo running in emulator, displaying two calendar entries

Enumerar eventos de calendario

A continuación, veamos cómo enumerar los eventos de un calendario determinado. Basándose en el ejemplo anterior, presentaremos una lista de eventos cuando el usuario seleccione uno de los calendarios. Por lo tanto, tendremos que controlar la selección de elementos en el código anterior:

ListView.ItemClick += (sender, e) => {
    int i = (e as ItemEventArgs).Position;

    cursor.MoveToPosition(i);
    int calId =
        cursor.GetInt (cursor.GetColumnIndex (calendarsProjection [0]));

    var showEvents = new Intent(this, typeof(EventListActivity));
    showEvents.PutExtra("calId", calId);
    StartActivity(showEvents);
};

En este código, vamos a crear una intención para abrir una actividad de tipo EventListActivity, pasando el identificador del calendario en la intención. Necesitaremos el identificador para saber qué calendario se va a consultar para los eventos. En el método OnCreate de EventListActivity, podemos recuperar el identificador de Intent como se muestra a continuación:

_calId = Intent.GetIntExtra ("calId", -1);

Ahora vamos a consultar los eventos de este identificador de calendario. El proceso para consultar eventos es similar a la forma en que hemos consultado una lista de calendarios anteriormente, solo esta vez trabajaremos con la clase CalendarContract.Events. El código siguiente crea una consulta para recuperar eventos:

var eventsUri = CalendarContract.Events.ContentUri;

string[] eventsProjection = {
    CalendarContract.Events.InterfaceConsts.Id,
    CalendarContract.Events.InterfaceConsts.Title,
    CalendarContract.Events.InterfaceConsts.Dtstart
};

var loader = new CursorLoader(this, eventsUri, eventsProjection,
                   String.Format ("calendar_id={0}", _calId), null, "dtstart ASC");
var cursor = (ICursor)loader.LoadInBackground();

En este código, primero obtenemos el contenido Uri de los eventos de la propiedad CalendarContract.Events.ContentUri. A continuación, especificamos las columnas de evento que queremos recuperar en la matriz eventsProjection. Por último, se crea una instancia de CursorLoader con esta información y se llama al método LoadInBackground del cargador para devolver un elemento Cursor con los datos del evento.

Para mostrar los datos de eventos en la interfaz de usuario, podemos usar el marcado y el código igual que hicimos antes para mostrar la lista de calendarios. De nuevo, usamos SimpleCursorAdapter para enlazar los datos a ListView como se muestra en el código siguiente:

string[] sourceColumns = {
    CalendarContract.Events.InterfaceConsts.Title,
    CalendarContract.Events.InterfaceConsts.Dtstart };

int[] targetResources = {
    Resource.Id.eventTitle,
    Resource.Id.eventStartDate };

var adapter = new SimpleCursorAdapter (this, Resource.Layout.EventListItem,
    cursor, sourceColumns, targetResources);

adapter.ViewBinder = new ViewBinder ();       
ListAdapter = adapter;

La principal diferencia entre este código y el código que usamos anteriormente para mostrar la lista de calendarios es el uso de ViewBinder, que se establece en la línea:

adapter.ViewBinder = new ViewBinder ();

La clase ViewBinder nos permite controlar aún más cómo enlazamos valores a vistas. En este caso, se usa para convertir la hora de inicio del evento de milisegundos en una cadena de fecha, como se muestra en la siguiente implementación:

class ViewBinder : Java.Lang.Object, SimpleCursorAdapter.IViewBinder
{    
    public bool SetViewValue (View view, Android.Database.ICursor cursor,
        int columnIndex)
    {
        if (columnIndex == 2) {
            long ms = cursor.GetLong (columnIndex);

            DateTime date = new DateTime (1970, 1, 1, 0, 0, 0,
                DateTimeKind.Utc).AddMilliseconds (ms).ToLocalTime ();

            TextView textView = (TextView)view;
            textView.Text = date.ToLongDateString ();

            return true;
        }
        return false;
    }    
}

Esto muestra una lista de eventos como se muestra a continuación:

Screenshot of example app displaying three calendar events

Agregar un evento de calendario

Hemos visto cómo leer los datos del calendario. Ahora vamos a ver cómo agregar un evento a un calendario. Para que esto funcione, asegúrese de incluir el permiso android.permission.WRITE_CALENDAR mencionado anteriormente. Para agregar un evento a un calendario, haremos lo siguiente:

  1. Cree una instancia ContentValues.
  2. Use claves de la clase CalendarContract.Events.InterfaceConsts para rellenar la instancia ContentValues.
  3. Establezca las zonas horarias para las horas de inicio y finalización del evento.
  4. Use ContentResolver para insertar los datos del evento en el calendario.

En el código siguiente se muestran estos pasos:

ContentValues eventValues = new ContentValues ();

eventValues.Put (CalendarContract.Events.InterfaceConsts.CalendarId,
    _calId);
eventValues.Put (CalendarContract.Events.InterfaceConsts.Title,
    "Test Event from M4A");
eventValues.Put (CalendarContract.Events.InterfaceConsts.Description,
    "This is an event created from Xamarin.Android");
eventValues.Put (CalendarContract.Events.InterfaceConsts.Dtstart,
    GetDateTimeMS (2011, 12, 15, 10, 0));
eventValues.Put (CalendarContract.Events.InterfaceConsts.Dtend,
    GetDateTimeMS (2011, 12, 15, 11, 0));

eventValues.Put(CalendarContract.Events.InterfaceConsts.EventTimezone,
    "UTC");
eventValues.Put(CalendarContract.Events.InterfaceConsts.EventEndTimezone,
    "UTC");

var uri = ContentResolver.Insert (CalendarContract.Events.ContentUri,
    eventValues);

Tenga en cuenta que si no establecemos la zona horaria, se producirá una excepción de tipo Java.Lang.IllegalArgumentException. Dado que los valores de tiempo de evento deben expresarse en milisegundos desde la época, creamos un método GetDateTimeMS (en EventListActivity) para convertir nuestras especificaciones de fecha en formato milisegundos:

long GetDateTimeMS (int yr, int month, int day, int hr, int min)
{
    Calendar c = Calendar.GetInstance (Java.Util.TimeZone.Default);

    c.Set (Java.Util.CalendarField.DayOfMonth, 15);
    c.Set (Java.Util.CalendarField.HourOfDay, hr);
    c.Set (Java.Util.CalendarField.Minute, min);
    c.Set (Java.Util.CalendarField.Month, Calendar.December);
    c.Set (Java.Util.CalendarField.Year, 2011);

    return c.TimeInMillis;
}

Si agregamos un botón a la interfaz de usuario de la lista de eventos y ejecutamos el código anterior en el controlador de eventos clic del botón, el evento se agrega al calendario y se actualiza en nuestra lista, como se muestra a continuación:

Screenshot of example app with calendar events followed by Add Sample Event button

Si abrimos la aplicación de calendario, veremos que el evento también se escribe allí:

Screenshot of calendar app displaying the selected calendar event

Como puede ver, Android permite un acceso eficaz y con facilidad para recuperar y conservar los datos de calendario, lo que permite a las aplicaciones integrar sin problemas las funcionalidades del calendario.