Category Archives: Xamarin.tvOS

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9814)

En informatique, les problèmes les plus simples sont parfois les plus bizarres à comprendre et c’est ce qui m’est arrivé il y a 2 jours sur un prototype Xamarin.iOS sur laquelle je travaille.

L’application est on ne peut plus classique puisqu’elle fait simplement des appels à des webservices pour afficher des données sous forme d’une liste.

Alors que tout fonctionne correctement au début, en passant les webservices de HTTP à HTTPS, l’erreur, dont la description est très parlante (NSURLSession/NSURLConnection HTTP load failed kCFStreamErrorDomainSSL, -9814), se produit de manière subite.

La première réaction a été de se dire que j’ai oublié de configurer ATS (App Transport Security) dans le fichier Info.plist de l’application. Après vérification, ce n’est pas le cas puisque la configuration autorise tous les appels comme indiqué ci-dessous.

<key>NSAppTransportSecurity</key>
  <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
  </dict>
</key>

Après recherche dans la documentation officielle Apple, le code 9814 signifie que le certificat SSL est expiré mais ce n’est pas le cas puisque celui-ci expire en 2019.

D’où peut donc bien venir cette erreur dans ce cas ?

Après plus d’une heure à tourner en rond, en cherchant sur StackOverflow et toutes les sources classiques d’aide pour un développeur, une illumination m’est apparue : “Vérifions quelle date est utilisée par mon périphérique actuellement”.

BINGO !!! Le périphérique qui n’a apparemment jamais réussi à se synchroniser avec une source de temps (ou défini manuellement dans les paramètres du système), utilise une date en Janvier 1970.

L’erreur ne signifie pas dans ce cas que le certificat est réellement expiré, mais qu’il n’a pas réussi à être validé dans la séquence normale de validation des communications HTTPS.

Je partage donc cette petite mésaventure qui vous évitera de perdre du temps, simplement parce que le périphérique utilisé n’était pas à l’heure. Après vérification, la même erreur s’applique sur tvOS si jamais vous travaillez sur cette plateforme.

Advertisements

Akavache : Stockage de données en cache dans vos applications Xamarin

Afin de palier aux problèmes de connectivité inhérents à toutes applications mobiles, le stockage de données sur le périphérique de l’utilisateur est une quasi nécessité, sauf si l’on accepte que l’utilisateur ne puisse utiliser l’application sans connexion internet (parti pris de nombreuses personnes).

La solution la plus souvent utilisée dans le monde des applications mobiles repose sur SQLite, qui a l’avantage d’être à la fois léger, performant et d’utiliser les standards du marché puisqu’il s’agit d’un moteur de base de données SQL classique.

Ce type de base de données a toutefois quelques inconvénients dont le plus important concerne la difficulté de modifier le schéma de la base lors d’une montée de version de l’application.

Afin de palier à ces problèmes, différentes alternatives existent (stockage clé/valeur, base NoSQL, etc…) et nous allons aborder l’une de celles-ci nommée Akavache.

Présentation de ce qu’est / ce que n’est pas Akavache

AkavacheComme sa description l’indique sur son repository Github (https://github.com/akavache/Akavache) vu qu’Akavache est un projet open-source, il s’agit d’un système de stockage de données asynchrone de type clé/valeur à destination des applications native (iOS, Android, UWP, WPF, OSX…) .

Un système de stockage clé/valeur ne fonctionne pas comme une base de données, ce qui sous-entend que pour pouvoir récupérer une donnée, il est nécessaire de connaitre la clé utilisée par celle-ci, ou bien de faire une lecture séquentielle des données pour retrouver la valeur souhaitée.

Les valeurs stockées par Akavache sont gérées sous forme de tableau d’octets, quels que soient leur forme originale (document JSON, image, chaine de caractères…). Il est possible de définir pour chacune des clés stockées, une date d’expiration du contenu au delà de laquelle les données seront automatiquement supprimées.

Sous le capot, Akavache utilise une base de données SQLite pour stocker les données. Cette base de données ne contient qu’une seule table servant à stocker les clés, valeurs et quelques autres éléments (type de données, date d’expiration…).

Pour être clair, Akavache n’est pas là pour concurrencer les bases de données comme SQLite, mais pour offrir une alternative sur une utilisation très ciblée qu’est la mise en cache de données.

Installation au sein d’une application Xamarin

Pour utiliser Akavache au sein d’une application Xamarin, rien de plus simple car cette solution se présente sous la forme de paquets NuGet, qu’il est possible de trouver assez facilement via la boite de dialogue que vous pouvez voir ci-dessous.

Akavache - NuGet Packages

On peut voir ci-dessous que la dernière version publiée (5.0.0) a été finalisée il y a seulement quelques jours (le 4 Novembre 2016).

Utilisation des APIs proposées par la bibliothèque

Comme pour toute application .NET, tout commence par référencer l’espace de noms :

using Akavache;

Une fois cette opération effectuée, tout se passe via l’utilisation de la classe nommée BlobCache, sur laquelle il est nécessaire de définir le nom de l’application (utilisé pour créé la base de données SQLite).

BlobCache.ApplicationName = "AkavacheDemo";

Il ne reste plus ensuite qu’à insérer et récupérer des données. Pour cela il existe différentes possibilités :

var userAccount = new UserAccount
{
  Id = 12345,
  FirstName = "Stephane",
  LastName = "Cordonnier"
};

BlobCache.InMemory.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));
BlobCache.LocalMachine.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));
BlobCache.Secure.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));
BlobCache.UserAccount.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));

BlobCache.LocalMachine.GetObject<UserAccount>("userAccount").Subscribe(u =>
{
  Console.WriteLine($"{u.Id} - {u.FirstName} - {u.LastName}");
});

BlobCache.LocalMachine.GetAllObjects<UserAccount>().Subscribe(userAccounts =>
{
  foreach (var u in userAccounts)
    Console.WriteLine($"{u.Id} - {u.FirstName} - {u.LastName}");
});

La classe UserAccount est un POCO, initialisé et stocké dans différents conteneurs mis à disposition par Akavache. Ces conteneurs sont au nombre de quatre :

  • InMemory => Stockage non persistent (données perdues au redémarrage de l’application)
  • LocalMachine => Stockage persistent dans une base SQLite
  • Secure => Stockage persistent dans une base SQLite avec cryptage des données
  • UserAccount => Stockage persistent dans une base SQLite. La base de données est stockée dans un répertoire qui est automatiquement sauvegardé par le mécanisme standard de la plateforme (ex : synchronisation iTunes / iCloud sur iOS)

Pour récupérer les données, il suffit d’interroger le même conteneur que celui utilisé pour le stockage. Si la clé de l’objet recherché est connue, la méthode GetObject<T>(…) est disponible. Pour récupérer tous les objets en se basant sur leur type, il suffit d’utiliser la méthode GetAllObjects<T>().

La suppression de données se fait de la même manière en appelant les méthodes InvalidateObject, InvalidateObjects ou InvalidateAllObjects.

Téléchargement et mise en cache de données

Outre la mise en cache de données réalisée par programmation, Akavache propose des fonctionnalités de téléchargement et de mise en cache de données directement depuis Internet.

BlobCache.UserAccount.DownloadUrl(...);
BlobCache.UserAccount.LoadImageFromUrl(...);

Ces fonctionnalités sont très pratiques pour la mise en cache de données provenant par exemple de webservices renvoyant des flux JSON. En revanche pour ce qui est de la mise en cache d’images, il faut garder à l’esprit que les données sont stockées en arrière plan dans une base SQLite, et il n’est pas forcément recommandé de stocker de gros flux binaires (ex: des images HD) dans ce type de base de données.

Conclusion

Si vous cherchez une solution rapide à mettre en oeuvre pour faire de la mise en cache simple de données, Akavache est une solution efficace et performante qui mérite que l’on s’attarde dessus.

En revanche si vous cherchez une solution puissante de stockage et d’interrogation comme peut l’être une base de données SQLite, mais sans les inconvénients de celle-ci (ex: gestion du schéma de la base), alors Akavache n’est clairement pas la solution adaptée.

Il faudra alors plutôt se diriger vers d’autres solutions telles que les bases NoSQL, ce qui fera certainement l’objet d’un futur article sur ce blog.

React to changes of focus in your Xamarin.tvOS applications

The focus engine introduced in UIKit for tvOS 9.0 is one of the key concepts you should know if you want to develop applications for the Apple TV.

It allows you to know what is the control that is currently focused on the screen, and to be notified when the focus changes from a view to another view (e.g. from a textfield to a button, from a collection view cell to the next cell, etc…).

Today I have created my first and simple application that uses a UICollectionView with few cells as you can see on the screenshot below. I was proud of my work until I realized that when the application is running, there is no visual indication that gives a feedback to the user of which cell is currently selected (cell #0 or cell #1).

tvOS - CollectionView without focus

I then looked for solutions to surround the focused cell with a white border to give an instant feedback to the user. The focus engine gives you this capability through the DidUpdateFocus method.

This method exposes a context to retrieve which views are focused (the previous and the next), and an animation coordinator to change attributes of these views within an animation.

When you know this method, it is very simple to accomplish your goal. The example below shows you how to remove the border for the old focused view, and add a border for the new focused view.

public override void DidUpdateFocus(UIFocusUpdateContext context, UIFocusAnimationCoordinator coordinator)
{
  base.DidUpdateFocus(context, coordinator);
  coordinator.AddCoordinatedAnimations(() =>
  {
    if (context.PreviouslyFocusedView != null)
    {
      var previousCell = (UICollectionViewCell)context.PreviouslyFocusedView;
      previousCell.Layer.BorderColor = UIColor.Clear.CGColor;
      previousCell.Layer.BorderWidth = 0;
    }

    if (context.NextFocusedView != null)
    {
      var nextCell = (UICollectionViewCell)context.NextFocusedView;
      nextCell.Layer.BorderColor = UIColor.White.CGColor;
      nextCell.Layer.BorderWidth = 5;
    }
  }, null);
}

The visual feedback is now more understandable for the user and he knows that the currently focused view is the cell #1.

tvOS - CollectionView with border when focused

It is good but if I want to have the same feature that is standard on the Apple TV (the cells sizes increase/decrease when they are focused/unfocused), what I need to do ?

If your cell is only composed with an UIImageView, you can select the option called “Adjusts image when focused” on the properties of this view in Xcode.

tvOS - ImageView adjusts image when focused

But when your cells are more complex like the sample below (each cell is composed with an UIImageView, a translucent UIView with a UILabel above), then you probably want to increase the size all the items inside the cell at the same time.

tvOS - CollectionView with scale when focused

No problem to achieve this goal with the same method that was previously mentioned. We just need to apply a transformation to the cell (CGAffineTransform.MakeScale(…)) when it receive the focus, and revert it (CGAffineTransform.MakeIdentity()) when the focus is lost.

public override void DidUpdateFocus(UIFocusUpdateContext context, UIFocusAnimationCoordinator coordinator)
{
  base.DidUpdateFocus(context, coordinator);
  coordinator.AddCoordinatedAnimations(() =>
  {
    if (context.PreviouslyFocusedView != null)
    {
      var previousCell = (UICollectionViewCell)context.PreviouslyFocusedView;
      previousCell.Transform = CGAffineTransform.MakeIdentity();
    }

    if (context.NextFocusedView != null)
    {
      var nextCell = (UICollectionViewCell)context.NextFocusedView;
      nextCell.Transform = CGAffineTransform.MakeScale(1.25f, 1.25f);
    }
  }, null);
}

As we have seen in this article, it’s pretty easy to interact with the focus engine of tvOS to apply any modifications on views, depending on the focus state of each of them.