Category Archives: iOS

Azure DevOps : Solution tout-en-un pour développer vos application mobiles

Qu’il s’agisse de projets personnels ou professionnels, open-source ou privés, un développeur a besoin de différents outils pour construire une application de qualité :

  • Gestion de projet
  • Gestion du code source
  • Intégration continue
  • Déploiement continu

De nombreux éditeurs existent pour offrir les différents outils nécessaires aux développeurs. Mais généralement, un éditeur propose un outil spécialisé sur un domaine unique afin d’être le meilleur sur celui-ci. Mais comme nous l’avons évoqué ci-dessus, on voit qu’il existe à minima 4 domaines à couvrir pour gérer le cycle de vie complet du développement d’une application.

Sans représenter une liste exhaustive des éditeurs présents sur le marché, vous trouverez ci-dessous un aperçu des principaux outils spécialisés utilisés par les développeurs mobiles : CircleCI, Travis CITeamCityJenkins, BitriseFastlane, HockeyApp, AppCenter, BuddyBuild, Nevercode, Firebase, AWS.

Le principal problème des éditeurs spécialisés, est qu’il est nécessaire d’avoir recours à plusieurs d’entre eux et de les faire cohabiter, pour créer une usine de développement logiciel digne de ce nom.

Sans être insurmontable, j’ai longtemps moi-même utilisé le quatuor Jira + TeamCity + Bitrise + HockeyApp, c’est un travail pouvant être long et coûteux à mettre en place. Tout le monde n’a pas nécessairement de temps à consacrer à cela, surtout dans les petites structures (startups, TPE-PME…).

C’est pourquoi d’autres éditeurs, plus connus et avec plus de moyens que ceux spécialisés, fournissent des solutions qui couvrent (plus ou moins) les 4 domaines évoqués précédemment : GitHub, AtlassianGitLab, Azure DevOps.

Ayant eu l’occasion de travailler sur la plupart d’entre eux, mon choix s’oriente actuellement sur Azure DevOps pour les raisons que je vais rapidement résumer ci-dessous.

Une solution tout-en-un hébergée dans le Cloud

Comme évoqué en introduction, de nombreux outils sont nécessaires pour gérer l’intégralité du cycle de développement d’une application et Azure DevOps offre l’ensemble des briques nécessaires, le tout hébergé dans le Cloud ce qui n’engendre aucun travail d’installation et de maintenance pour vous, hormis la configuration pour coller à vos besoins en terme de sécurité par exemple.

Azure Boards

Cette fonctionnalité sera le coeur de votre projet puisque c’est elle qui vous permettra de gérer l’organisation, la planification, l’avancement et le traitement des bugs de vos applications, que vous fonctionniez en mode Agile ou non.

Azure Repos

Toute application a besoin de stocker son code source et de permettre aux développeurs de l’équipe de collaborer sur celui-ci. Cette fonctionnalité offre tous ce qui est nécessaire du stockage des fichiers à la gestion des branches en passant par les commentaires et le suivi lors de la relecture de code.

Azure Pipelines

Afin d’assurer la qualité et la déliverabilité rapide de vos applications, l’automatisation de certaines tâches (exécution de tests unitaires, création de packages, publication sur les stores…) doit être au coeur du processus de développement. C’est exactement ce qu’offre cette fonctionnalité à travers de nombreux connecteurs pour iOS, Android, Xamarin, etc…

Azure Test Plans

Si vous développez des sites web ou des applications de bureau, votre démarche qualité vous amènera certainemet à vouloir tester ceux-ci de manière automatisée. C’est à cela que servira cette fonctionnalité qui malheureusment ne couvre pas (du moins pour l’instant) les applications mobiles.

Si vous souhaitez tester l’interface graphique de vos applications mobiles, il faudra dans l’immédiat vous orientez vers Visual Studio AppCenter (successeur de HockeyApp), qui est le complément idéal à Azure DevOps puisque celui-ci fournit tous les connecteurs nécessaires à son intégration.

Azure Artifacts

Quand vous développez avec .NET Core, Node.js, Python, etc… vous voulez souvent partager votre code entre différentes applications et pour cela vous créez des packages de type NuGet, Maven, npm, etc… Cette fonctionnalité vous permet d’héberger ces packages et d’exposer un référentiel que toutes vos applications peuvent consulter pour utiliser votre code partagé entre toutes vos applications.

Malheureusement pour les applications mobiles natives (Objective-C, Swift, Java, Kotlin), cette fonctionnalité n’offre rien de particulier pour elles et il faudra continuer de se reposer sur les anciennes solutions telles que Swift Package Manager, Cocoapods ou Carthage sur iOS.

En revanche les autres technologies (Xamarin, React Native…) pourront tirer partie de cette fonctionnalité sans trop de soucis.

Gratuit pour les petits projets (même privés)

Quand on démarre un projet, qu’il soit personnel ou professionnel, l’équipe est généralement restreinte au début et avec des moyens limités, que ce soit en temps et/ou en argent.

Pouvoir bénéficier d’une plateforme rapide à mettre en oeuvre et gratuite est donc quelque chose de très utile au lancement d’un projet et c’est exactement ce qu’apporte Azure DevOps.

Différents plans de licences existent mais le premier nommé tout simplement Basic Plan, offre l’usage de tous les services de la plateforme gratuitement dans une certaine limite.

 

Les éléments majeurs à retenir des captures ci-dessus :

  • Gratuit pour les 5 premiers utilisateurs, 6$/mois/utilisateur au delà
  • Nombre illimité de repos Git (privés ou publics)
  • 1800mn/mois pour exécuter vos travaux CI / CD en mode Cloud
  • Hébergez chez vous 1 agent pour vos travaux CI / CD sans limite de minutes
  • 2 Go de stockage pour vos packages applicatifs sur Azure Artifacts

Lorsque l’on développe des applications mobiles, il est évidemment nécessaire de pouvoir bénéficier d’une CI / CD qui fonctionne sur macOS afin de pouvoir créer ses applications pour iOS.

Force est de constater que les solutions proposant des fonctions CI / CD gratuites tournant sur macOS dans le Cloud sont quasiment inexistantes et c’est là une des forces d’Azure Pipelines à travers lequel vous bénéficiez de cette possibilité, même si vous utilisez le Basic Plan.

Usine logicielle pour tous types de projets

Les développeurs d’applications mobiles ne travaillent pas tous sur les technologies natives poussées par Apple et Google, à savoir Objective-C / Swift sur iOS ou bien Java / Kotlin sur Android.

D’autres solutions telles que Xamarin, React Native, Flutter, etc… existent sur le marché et rencontrent un succès plus ou moins important. Et bien là aussi, Azure Pipelines apporte le support de ces typologies d’applications à travers des connecteurs fournis clés en main. Et quand le connecteur n’existe pas directement, il est possible de s’en sortir via l’utilisation de scripts shell ou bien en réalisant et proposant soit même un connecteur aux équipes de Microsoft.

Mais une application mobile a généralement besoin de webservices pour fonctionner et ceux-ci sont développés sur des environnements technologiques totalement différents : .NET Core, Java, Node.js, Go, Ruby on Rails, etc…

Azure Pipelines et Azure Artifacts proposent là aussi le support de ces environnements technologiques afin de pouvoir gérer l’ensemble des applications sur une seule et même plateforme. Plus besoin d’avoir différentes plateformes, et donc différentes compétences techniques, en fonction de la typologie du projet à gérer.

Conclusion

Le but n’était pas ici de vous faire un cours magistral sur ce qu’offre Azure DevOps, mais plus une présentation très succinte des différentes briques offertes et voir pourquoi cette solution peut (doit) être considérée pour gérer vos applications mobiles.

Si vous cherchez une solution tout-en-un couvrant la quasi intégralité du cycle de développement d’une application mobile et hébergée dans le Cloud, alors je ne peux que vous recommander de vous intéresser à cette plateforme qui vous offrira tout ce dont vous avez besoin.

Si en plus vous cherchez une plateforme pour gérer des projets open-source ou bien une équipe de taille réduite, comme lors du démarrage d’une start-up, là aussi vous trouverez probablement votre bonheur de par son modèle économique.

Cela ne signifie pas qu’il faille totalement abandonner les autres solutions du marché qui ont fait et font encore leurs preuves au quotidien, avec pour conséquence de devoir changer toutes vos habitudes de travail.

On voit d’ailleurs que de nombreux éditeurs tendent vers cette voie d’une plateforme tout-en-un puisque GitHub (racheté par Microsoft) s’est récemment doté de fonctionnalités de CI / CD via GitHub Actions (encore en version Beta à l’heure actuelle).

C’est également le cas d’Atlassian via sa suite très connue (Jira, Confluence, Bitbucket) qui propose des fonctionnalités CI / CD au travers de Bitbucket Pipelines, mais qui ne sont pas capables à l’heure actuelle, d’exécuter des choses sur macOS et donc impossible de gérer les applications iOS.

ITMS-90668 : The executable file contains incomplete bitcode

Comme toute personne qui développe des applications mobiles iOS avec Xamarin, vous utiliserez certainement à un moment donné des SDKs tierces (réseaux sociaux, régies publicitaires, etc…).

Une fois votre application développée, viendra le moment de la soumettre sur l’AppStore et dans la plupart des cas cela fonctionnera sans aucun problème. Mais il arrivera parfois que des soucis surviennent.

C’est ce qui m’est arrivé il y a 15 jours environ pendant la phase de soumission sur l’AppStore, quand j’ai reçu un message d’erreur comme celui visible ci-dessous (cette capture ne correspond pas à mon application mais la source d’erreur est la même).

Ce message indique qu’un SDK utilisé dans l’application pose un souci car il contient un “bitcode incomplet” alors que très probablement d’autres SDKs dans la même application utilisent pour leur part un bitcode complet. Apple refuse apparemment d’accepter les applications qui mixent les deux.

Après de multiples recherches sur Internet qui traitent dans la majorité des cas de la résolution de cette erreur pour les gens travaillant sur Xcode, je suis tombé sur cette page sur l’outil de suivi de bugs de Xamarin qui fait état d’un problème connu.

La solution de contournement (workaround / fix) évoqué dans le ticket fonctionne parfaitement.

Pour cela, il suffit d’éditer le fichier .csproj de votre application Xamarin.iOS et d’ajouter les lignes suivantes à la fin, juste avec la balise </project> :

<Target Name="BeforeCodesign">
  <Exec Command="$(_SdkDevPath)\Toolchains\XcodeDefault.xctoolchain\usr\bin\bitcode_strip -r %(_Frameworks.FullPath) -o %(_Frameworks.FullPath)" />
</Target>

Ces quelques lignes permettent de supprimer tout le bitcode inutiles des SDKs embarqués dans votre application au moment où celle-ci est compilée et juste avant d’appliquer la signature du package.

Une fois ces quelques lignes ajoutées et l’application recompilée, la soumission sur l’AppStore ne devrait plus poser aucun souci.

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.

Reconnaissance vocale et analyse de texte dans vos applications Xamarin.iOS

Bien qu’existante depuis très longtemps dans l’univers informatique, la reconnaissance vocale est une fonctionnalité encore peu utilisée au sein des applications (aussi bien sur mobile que sur desktop).

Depuis l’annonce de Siri par Apple en 2011, les assistants personnels ont le vent en poupe et une nouvelle étape a été franchie sur iOS 10 lors de la WWDC 2016 puisque le moteur de reconnaissance vocal utilisé par Siri est désormais accessible aux développeurs.

Voyons comment utiliser celui-ci pour intégrer la reconnaissance vocale au sein d’une application Xamarin.iOS, et comment le coupler à des APIs plus anciennes mais relativement méconnues, pour faire de l’analyse de texte.

Speech Recognition API au service des développeurs

Comme nous l’évoquions précédemment, Apple a rendu disponible dans iOS 10 le moteur qui propulse Siri pour la reconnaissance vocale. Pour cela, une nouvelle classe est à la disposition des développeurs : SFSpeechRecognizer.

Ce qu’il est important de noter pour utiliser cette classe, c’est qu’une connexion réseau est nécessaire car tout ce qui sera enregistré via le microphone, sera envoyé à Apple pour que les serveurs qui propulsent Siri, traitent et analysent les informations avant de vous renvoyer la transcription du texte.

Cette phase de communication avec les serveurs Apple a lieu tout au long du processus de reconnaissance vocale (vous récupérez la transcription au fil de l’eau), ce qui fait que si vous utilisez la fonctionnalité trop souvent, cela a tendance à consommer beaucoup de données (sur votre forfait 3G/4G) et d’énergie sur la batterie de votre téléphone/tablette.

C’est pourquoi Apple impose certaines limitations (ex: 1mn maximum pour chaque enregistrement audio) et a également mis en place des quotas d’utilisation permettant à une application d’effectuer un nombre limité de requêtes par jour (un message d’erreur est renvoyé si le quota est dépassé).

Comme pour tout ce qui touche à la vie privée chez Apple, avant de pouvoir utiliser cette API, il est nécessaire de demander l’autorisation à l’utilisateur et de réagir en conséquence selon la réponse qu’il donnera :

SFSpeechRecognizer.RequestAuthorization((SFSpeechRecognizerAuthorizationStatus status) =>
{
  switch (status)
  {
    case SFSpeechRecognizerAuthorizationStatus.Authorized:
      break;
    case SFSpeechRecognizerAuthorizationStatus.Denied:
      break;
    case SFSpeechRecognizerAuthorizationStatus.NotDetermined:
      break;
    case SFSpeechRecognizerAuthorizationStatus.Restricted:
      break;
  }
});

Pour que tout ceci fonctionne sans soucis, il est également nécessaire d’ajouter 2 lignes dans le fichier Info.plist, afin d’indiquer à l’application que vous avez besoin d’utiliser la reconnaissance vocale et le microphone.

Speech Recognition - Info.plist

Maintenant que les bases sont posées pour pouvoir utiliser les composants nécessaires, il ne reste plus qu’à écrire le code. Celui-ci repose sur 3 classes importantes :

  • AVAudioEngine ==> Le moteur audio permettant d’enregistrer ce que l’utilisateur dira via le microphone de son téléphone ou de sa tablette
  • SFSpeechRecognizer ==> Le moteur de reconnaissance vocal qui communiquera avec les serveurs Apple
  • SFSpeechAudioBufferRecognitionRequest ==> Le buffer audio qui servira à stocker les données renvoyées par le moteur audio avant d’être envoyées chez Apple pour analyse

Un exemple de code étant toujours plus parlant qu’un long discours, voici ci-dessous le code associé à notre application de démonstration, qui initie l’enregistrement quand l’utilisateur tape sur un bouton, et met fin à l’enregistrement quand l’utilisateur relâche ce même bouton.

private AVAudioEngine AudioEngine;
private SFSpeechRecognizer SpeechRecognizer;
private SFSpeechAudioBufferRecognitionRequest LiveSpeechRequest;
private SFSpeechRecognitionTask RecognitionTask;
private string sentence;

partial void StartSpeechRecognition(UIButton sender)
{
  SpeechRecognizer = new SFSpeechRecognizer(NSLocale.FromLocaleIdentifier("fr"));
  if (!SpeechRecognizer.Available)
    return;

  AudioEngine = new AVAudioEngine();
  if (AudioEngine.InputNode == null)
    return;

  NSError audioError;
  LiveSpeechRequest = new SFSpeechAudioBufferRecognitionRequest();

  var recordingFormat = AudioEngine.InputNode.GetBusOutputFormat(0);
  AudioEngine.InputNode.InstallTapOnBus(0, 1024, recordingFormat, (AVAudioPcmBuffer buffer, AVAudioTime when) => LiveSpeechRequest.Append(buffer));
  AudioEngine.Prepare();
  AudioEngine.StartAndReturnError(out audioError);
  if (audioError != null)
    return;

  RecognitionTask = SpeechRecognizer.GetRecognitionTask(LiveSpeechRequest, (SFSpeechRecognitionResult result, NSError taskError) =>
  {
    if (taskError != null)
      return;

    if (!result.Final)
    {
      SpeechLabel.Text = result.BestTranscription.FormattedString;
      return;
    }
 
    sentence = result.BestTranscription.FormattedString;
    ParseSpeechRecognition();
  });
}

partial void StopSpeechRecognition(UIButton sender)
{
  AudioEngine?.Stop();
  AudioEngine?.InputNode?.RemoveTapOnBus(0);
  LiveSpeechRequest?.EndAudio();
}

Outre l’initialisation du moteur audio via la définition de paramètres sur la source d’enregistrement (AudioEngine.InputNode), la partie importante repose sur la fonction de “callback” passée à la méthode GetRecognitionTask, qui permet de récupérer la transcription du texte (result.BestTranscription.FormattedString) au fil de l’eau, et de savoir si la reconnaissance est terminée ou non (result.Final).

Une fois tous les éléments assemblés et en réalisant un peu de cosmétique autour de l’interface graphique, cela donne le résultat visible dans la vidéo ci-dessous :

Comme on peut le voir dans cette vidéo, avec juste quelques lignes de code, il est possible d’intégrer la reconnaissance vocale dans une application. Mais hormis si l’on souhaite juste permettre à l’utilisateur de remplir des zones de texte sans rien saisir au clavier, il serait plus intéressant de pouvoir analyser ce texte.

NSLinguisticTagger : Une classe méconnue mais très pratique

C’est à ce moment qu’une classe relativement méconnue surgit, bien qu’elle existe dans iOS depuis la version 5.0. Cette classe porte un nom relativement explicite : NSLinguisticTagger.

Celle classe est capable, à partir d’un texte plus ou moins long, de “tagger” celui-ci, c’est-à-dire d’identifier tous ses composants (noms, pronoms, verbes, adverbes, déterminants, etc…) mais également d’identifier certains éléments marquants (noms de lieux, noms de sociétés…). Malheureusement cette dernière possibilité ne fonctionne qu’en Anglais.

Différentes langues sont supportées par NSLinguisticTagger (Anglais, Français, Allemand…) afin de couvrir les langues les plus fréquemment rencontrées sur iOS.

Comment fonctionne tout ceci ? C’est relativement simple comme on peut le voir dans l’exemple de code ci-dessous :

private void ParseSpeechRecognition()
{
  var options = NSLinguisticTaggerOptions.OmitWhitespace | NSLinguisticTaggerOptions.OmitPunctuation | NSLinguisticTaggerOptions.JoinNames;
  var schemes = NSLinguisticTagger.GetAvailableTagSchemesForLanguage("fr");
  var tagger = new NSLinguisticTagger(schemes, options);
  tagger.AnalysisString = sentence;
  tagger.EnumerateTagsInRange(new NSRange(0, sentence.Length), NSLinguisticTag.SchemeLexicalClass, options, HandleNSLingusticEnumerator);
}

private void HandleNSLingusticEnumerator(NSString tag, NSRange tokenRange, NSRange sentenceRange, ref bool stop)
{
  var token = sentence.Substring((int)tokenRange.Location, (int)tokenRange.Length);
  Console.WriteLine($"{token} = {tag}");
}

A partir d’une liste d’options (ignorer les espaces, ignorer la ponctuation, etc…) et d’une liste de types d’éléments analysables pour une langue (appelées “schemes”), on énumère les “tags” contenus dans le texte à analyser (AnalysisString).

La méthode permettant d’énumérer les tags du texte, prend en paramètre une fonction de “callback” qui sera appelée pour chaque tag identifié.

Si on exécute le code précédent avec la phrase utilisée dans la vidéo de notre application de démonstration, nous obtenons le résultat ci-dessus dans la fenêtre de sortie de Xamarin Studio.

2016-11-27 09:02:57.714 SpeechDemo[919:415497] C' = Pronoun
2016-11-27 09:02:57.714 SpeechDemo[919:415497] est = Verb
2016-11-27 09:02:57.714 SpeechDemo[919:415497] bientôt = Adverb
2016-11-27 09:02:57.715 SpeechDemo[919:415497] Noël = Noun
2016-11-27 09:02:57.715 SpeechDemo[919:415497] j' = Pronoun
2016-11-27 09:02:57.715 SpeechDemo[919:415497] espère = Verb
2016-11-27 09:02:57.716 SpeechDemo[919:415497] que = Conjunction
2016-11-27 09:02:57.716 SpeechDemo[919:415497] vous = Pronoun
2016-11-27 09:02:57.716 SpeechDemo[919:415497] passerez = Verb
2016-11-27 09:02:57.716 SpeechDemo[919:415497] tous = Adjective
2016-11-27 09:02:57.717 SpeechDemo[919:415497] de = Preposition
2016-11-27 09:02:57.717 SpeechDemo[919:415497] bonnes = Adjective
2016-11-27 09:02:57.717 SpeechDemo[919:415497] fêtes = Noun

Comme on le voit ci-dessus, à partir des informations renvoyées par NSLinguisticTagger, il est possible d’envisager les traitements nécessaires à la logique propre à une application (ex: extraire les noms et rechercher des valeurs particulières pour filtrer des résultats).

Pour aller encore plus loin…

Comme nous l’avons vu dans cet article, en couplant la reconnaissance vocale, désormais disponible dans iOS 10, avec un outil de taggage comme NSLinguisticTagger, de nouveaux horizons s’ouvrent pour intégrer des fonctionnalités inédites au sein des applications mobiles.

Toutefois si vous voulez aller encore plus, il existe d’autres éléments qu’il est toujours bon de connaitre, à commencer par la catégorisation de contenus.

Si vous réalisez des applications pour OSX, Apple a mis à disposition un framework appelé Latent Semantic Mapping (LSM) qui permet de catégoriser un contenu (ex: une page web, un fichier…) à partir d’une base d’autres contenus. Une session dédiée à ce framework avait été présentée lors de la WWDC 2011 si vous souhaitez en savoir plus à ce sujet.

Si vous voulez faire une analyse plus poussée du texte renvoyé par la reconnaissance vocale (ex: extraire des entités, identifier le sujet de fond d’un document, etc…) il existe différentes APIs basées sur le Machine Learning qui satisferont tous vos désirs.

On pourra citer comme exemple un projet sur lequel mise beaucoup Microsoft, à savoir Cognitive Services et plus particulièrement la brique Language Understanding Intelligent Service (LUIS). Des alternatives telles que TextRazor méritent également le coup d’oeil.

Editer un XIB avec Xcode 8 peut avoir des conséquences inattendues sur le rendu de votre application Xamarin

Apple a rendu disponible la version finale de Xcode 8 courant Septembre et comme beaucoup de développeurs, vous avez probablement effectué la mise à jour si vous voulez pouvoir tirer parti des nouveautés d’iOS 10.

Ayant déjà essuyer les plâtres de ce type de mise à jour par le passé, notamment lors des différentes montées de version de Swift (de 1.0 à 3.0), je laisse désormais passer un peu de temps pour voir si d’autres personnes rencontrent des soucis ou non, même si j’avais déjà testé de manière superficielle cette version sur une 2ème machine de développement à sa sortie.

J’ai donc appliqué le même principe avec cette dernière mise à jour, et j’ai effectué des tests un plus poussés cette semaine pour voir si une application Xamarin.iOS sur laquelle travaille mon équipe, fonctionnait toujours correctement une fois la mise à jour effectuée.

Une interface graphique relativement simple basée sur des XIB

Quand on réalise une application iOS, on peut réaliser l’interface graphique sous 3 formes, en pouvant combiner celles-ci si nécessaire :

  • Par code => Il faut être motivé si les écrans sont complexes
  • A base de XIB => La solution historique utilisée sur iOS depuis ses débuts
  • A base de storyboards => La solution désormais préconisée par Apple

Nous ne débattrons pas ici des avantages et inconvénients de chaque solution et pour l’application sur laquelle nous travaillons, nous avons préféré retenir les XIB avec également la solution par code sur certains composants.

Ci-dessous, vous trouverez l’exemple d’un écran de l’application réalisé par XIB sous Xcode 7, avec l’utilisation de contraintes pour gérer facilement les différentes tailles/résolutions de périphériques. Cet écran contient également un composant entièrement réalisé par code (le calendrier sur l’onglet “Mois”).

Xcode8 - Application avant édition #1  Xcode8 - Application avant édition #2

Ouverture du XIB sous Xcode 8 sans apporter aucune modification…

Après avoir mis à jour les différents outils (Xamarin Studio, Xcode, etc…) sur une machine de développement, j’ai tout simplement ouvert le XIB de cet écran depuis Xcode 8 afin de pouvoir tirer parti des nouveautés de l’environnement Apple (ex: la nouvelle prévisualisation sur différents périphériques).

A l’ouverture du XIB, Xcode 8 fait un certain nombre de modifications dont vous n’avez pas nécessairement conscience, et enregistre automatiquement celles-ci. Le mécanisme de synchronisation entre Xamarin Studio et Xcode fait en sorte que ces modifications soient immédiatement répercutées côté Xamarin à la prochaine compilation de l’application.

… et là c’est le drame une fois l’application compilée et exécutée

Après être retourné sous Xamarin Studio et avoir compiler et exécuter l’application, je retourne sur l’écran en question et là surprise, celui-ci ne fonctionne plus comme auparavant.

Les différences sont subtiles (disparition du liseré rouge sous l’onglet courant, disparition de certaines lignes dans le contrôle calendrier…). Vous pouvez voir ci-dessous le résultat :

Xcode8 - Application après édition #1 Xcode8 - Application après édition #2

Mais que se passe-t-il vu que nous avons juste ouvert le XIB ?

Comme je le disais précédemment, lors de l’ouverture du XIB, Xcode 8 fait quelques modifications sans vous prévenir dans la structure du fichier, dont vous pouvez voir un extrait ci-dessous grâce au client Git que nous utilisons pour nos projets (Atlasssian SourceTree).

Xcode8 - Différences dans le fichier XIB

Comme on peut le voir, certaines lignes ont été ajoutées et/ou modifiées (en vert) et d’autres lignes ont purement et simplement été supprimées (en rouge).

Parmi les lignes supprimées, on retrouve tout ce qui concerne la définition du positionnement par défaut des éléments graphiques dans la vue (les lignes commençant par <rect key=”frame” x=”…” y=”…” width=”…” height=”…” />.

Vous vous demandez peut être pourquoi un tel impact sur le rendu de l’écran ?

Et bien parce que dans celui-ci, certains éléments sont construits par code (le liseré rouge ou le contrôle calendrier) avec du code ressemblant à quelque chose comme ceci :

public override void ViewDidLoad()
{
   base.ViewDidLoad();
   ...

   _selectedBottomLayer = new CALayer();
   _selectedBottomLayer.Frame = new CGRect(0, DayButton.Bounds.Size.Height - SELECTED_LAYER_THICKNESS, DayButton.Bounds.Width, SELECTED_LAYER_THICKNESS);
   View.Layer.AddSublayer(_selectedBottomLayer);

   ...
}

Rien que des choses assez classiques pour les personnes qui font de l’iOS, mais qui ne fonctionnent plus comme auparavant à cause de la modification opérée par Xcode 8.

La suppression de la position par défaut a un impact dans le cycle de vie de construction d’une vue

Le fait que Xcode 8 aie supprimé le positionnement par défaut des éléments n’a pas d’impact sur les éléments positionnés par contraintes, mais pour les éléments positionnés par code et sans contraintes (les contraintes ne peuvent pas toujours être utilisées), cette modification a un impact très important.

Dans l’exemple précédent, le positionnement du liseré rouge est calculé dans la méthode ViewDidLoad. Le problème étant qu’au moment où cette méthode est exécutée dans le cycle de vie iOS, les contraintes appliquées sur les composants graphiques n’ont pas encore été calculées.

Là où au préalable la bouton sur lequel étaient basés les calculs avait un cadre défini comme ceci (x=0, y=0, width=106.5, height=45), il n’a désormais plus de positionnement par défaut ce qui fait qu’au moment ou la méthode ViewDidLoad s’exécute, le bouton a les caractéristiques suivantes (x=0, y=0, width=1000, height=1000).

Donc le liseré rouge est bien construit mais avec des coordonnées totalement farfelues qui le positionnent en dehors de la zone visible de l’écran. Le problème est le même pour les lignes qui ont disparues du contrôle calendrier.

Quelle est la solution pour résoudre les soucis dans ce cas ?

La solution est relativement simplement à mettre en oeuvre mais peut nécessiter de revoir complètement le cycle de vie qui a été utilisé dans les différents écrans.

Puisque le calcul de positionnement ne peut être effectué correctement au moment du ViewDidLoad, il suffit de le faire à un moment plus opportun.

Il n’y a pas une réponse qui pourra répondre à tous les cas possibles que vous pourriez rencontrer mais iOS propose différentes méthodes à surcharger dans lesquelles vous pouvez effectuer les traitements nécessaires à vos besoins.

Parmi les méthodes classiques que l’on a l’habitude d’utiliser pour effectuer les traitements nécessaires à des calculs de positionnement, on retrouve celles-ci (liste non exhaustive) :

public override void UpdateViewConstraints()
{
}

public override void ViewDidAppear(bool animated)
{
}

public override void ViewDidLayoutSubviews()
{
}

public override void ViewWillAppear(bool animated)
{
}

public override void ViewWillLayoutSubviews()
{
}

En conclusion, même si dans la plupart des cas la montée de version de Xcode n’aura pas d’impact sur vos applications, il peut arriver que dans certains cas cela ne soit pas si transparent que cela.

Que Xcode 8 supprime des données du XIB est une chose qui parait assez logique, certainement pour privilégier le système de contraintes créé par Apple, mais le fait que cela change le comportement et l’affichage d’une application qui fonctionnait sans soucis jusque là, est plus dérangeant sur le fond.

On ne répètera jamais assez qu’avant de faire une montée de version quelle qu’elle soit (outils de développement, packages NuGet…), la mise en place de tests approfondis (unitaires, graphiques, etc…) pour vérifier tous les impacts est un élément crucial pour la qualité de vos applications.

How to remove the IOHIDLibFactory error when you start your Xamarin.iOS application in the simulator

When you will develop Xamarin.iOS applications, you will use the iOS simulator to test your application without having to use a real device like an iPhone or an iPad.

In Xamarin Studio (like in Visual Studio), the output window gives you some useful information to help you to debug your application (e.g. when you use Console.WriteLine(…) to print values).

When you start your application in the iOS simulator, it might happen that you see a lot of errors like this :

Error loading /System/Library/Extensions/IOHIDFamily.kext/Contents/PlugIns/
IOHIDLib.plugin/Contents/MacOS/IOHIDLib: dlopen(/System/Library/Extensions/
IOHIDFamily.kext/Contents/PlugIns/IOHIDLib.plugin/Contents/MacOS/IOHIDLib, 262):
no suitable image found. Did find: /System/Library/Extensions/IOHIDFamily.kext/Contents/
PlugIns/IOHIDLib.plugin/Contents/MacOS/IOHIDLib: mach-o, but not built for iOS simulator

Or also :

Cannot find function pointer IOHIDLibFactory for factory 13AA9C44-6F1B-11D4-907C-0005028F18D5 in CFBundle/CFPlugIn 0x7be04110 </System/Library/Extensions/
IOHIDFamily.kext/Contents/PlugIns/IOHIDLib.plugin> (bundle, not loaded)

It’s not a problem for your application and has no impact on it, but it’s often annoying to have a lot of errors in the output window, each time you start your application.

If you want to remove these errors, you just have to edit your iOS build settings, and change the linker behavior value from “Don’t link” to “Link Frameworks SDKs Only“.

Xamarin Studio - iOS Build Settings

Note that activating this option will increase the compile time of your application because the linker will have more work to do to generate your package.

Introducing the new HttpClient implementation in Xamarin.iOS

In the upcoming days/weeks, Xamarin will release a whole set of new versions for its products to develop cross-platform mobile applications.

One of the most important (from my perspective) is Xamarin Studio 6.0 which will include lots of new features. If you want more information about those features, you can find a complete list on this page.

Today, I would like to talk about one of them which allows you to choose which implementation you want to use for network communications for your application iOS.

When you want to write cross-platform mobile applications, you avoid to use platform-specific APIs such as NSURLSession in iOS. Instead, you will prefer to use APIs that are available on all platforms such as the HttpClient class available in the .NET framework.

While this solution has many benefits for reusing your code on all platforms, the performance is worse than using specific API to the platform.

Fortunately, Xamarin Studio 6.0 (available on the Beta channel at this time) will introduce an option to use the underlying native APIs (NSURLSession, CFNetwork…) above HttpClient.

To activate this option, you just have to edit your Xamarin.iOS project settings and to go to the iOS Build section. On the screenshot below, you can see that 2 new options are available :

  • HttpClient Implementation
  • SSL / TLS Implementation

By default, your project will use the .NET implementation but you can switch to native APIs.

Xamarin Studio 6.0 - Choose your HttpClient implementation

There are many choices available and if you want more details, leave your cursor on the “blue I” on the right side of the selection lists. Below, the details about choices available for the HttpClient implementation.

Xamarin Studio 6.0 - HttpClient implementation tooltip

As mentioned in this tooltip, the usage of native APIs will result of better performance, support for latest standards (e.g. TLS 1.2) and smaller executable size.

An SSL error has occurred when trying to use the Outlook REST API on iOS 9 or OSX 10.11 (El Capitan)

In a previous article, we talked about an issue when you try to stream an Office 365 Videos from Azure Media Services in your application running on iOS 9.

It seems that Microsoft is actually doing some modifications on the infrastructure of the Office 365 platform, especially around the SSL certificates, because today I’m facing the same error when using the Outlook REST API.

I’m trying to call the following URL which should return the last 10 messages stored in the inbox  of the authenticated user :

https://outlook.office365.com/api/v2.0/Me/MailFolders('Inbox')/Messages

But I get the following error message, which indicates there’s an issue with SSL certificates :

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

Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made."
UserInfo={NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSUnderlyingError=0x7d96cd90 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorCodeKey=-9802, _kCFStreamErrorDomainKey=3, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x7afb9d50>, kCFStreamPropertySSLPeerCertificates=<CFArray 0x7ae63490 [0xa97098]>{type = immutable, count = 2, values = (
 0 : <cert(0x7afb8570) s: outlook.com i: Microsoft IT SSL SHA1>
 1 : <cert(0x7afb96d0) s: Microsoft IT SSL SHA1 i: Baltimore CyberTrust Root>
)}}}, _kCFStreamErrorCodeKey=-9802, NSErrorFailingURLStringKey=https://outlook.office365.com/api/v2.0/Me/MailFolders('Inbox')/Messages, NSErrorPeerCertificateChainKey=<CFArray 0x7ae63490 [0xa97098]>{type = immutable, count = 2, values = (
 0 : <cert(0x7afb8570) s: outlook.com i: Microsoft IT SSL SHA1>
 1 : <cert(0x7afb96d0) s: Microsoft IT SSL SHA1 i: Baltimore CyberTrust Root>
)}, NSErrorClientCertificateStateKey=0, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x7afb9d50>, NSErrorFailingURLKey=https://outlook.office365.com/api/v2.0/Me/MailFolders('Inbox')/Messages}

The cause of this error is still the same as explained in our previous article : App Transport Security (ATS).

To solve the issue, you need to add some lines in your Info.plist to configure ATS to ignore the SSL error.

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains<key>
  <dict>
    <key>outlook.office365.com</key>
    <dict>
      <key>NSExceptionRequiresForwardSecrecy</key>
      <false/>
    </dict>
  </dict>
</dict>

You can also edit this file using the editor in Xcode rather than manually edit the XML file :

Office365 Outlook REST API - App Transport Security

NSURLSession/NSURLConnection HTTP load failed when trying to stream an Office 365 Video on iOS 9

Yesterday, I was playing with Office 365 Video and I wanted to be able to play/stream videos from Office 365 into an iOS app.

The first step to be able to read a video was to retrieve the streaming URL of that video in Azure Media Services (which is used behind Office 365 to offer streaming capabilities).

It’s pretty simple using the Office 365 REST API :

https://tenant.sharepoint.com/portals/hub/_api/VideoService/Channels(guid'01234567-abcd-cded-1234-1234567890ab')/Videos(guid'01234567-abcd-cded-1234-1234567890ab')/GetPlaybackUrl(0)

When you execute that request, you retrieve the URL to play/stream the video from Azure Media Services in HLS format (HTTP Live Streaming). This URL should look something like :

https://cvprdb302v.cloudvideo.azure.net/api/ManifestProxy?playbackUrl=https://cdn-cvprdb302m01.streaming.mediaservices.windows.net/.../Manifest(format=m3u8-aapl)&token=...

To play/stream the video into an iOS app, you can use the built-in video player (AVPlayer) which is compatible with HLS format. To do that, you just have to write few lines of code :

let URL = NSURL(string: "...")  // Put here the URL retrieved from GetPlaybackUrl
let playerViewController = AVPlayerViewController()
playerViewController.player = AVPlayer(URL: URL!)
presentViewController(playerViewController, animated: true, completion: nil)
playerViewController.player?.play()

If you run this code on iOS 8, there’s no problem and everything should work fine. But if you run the same code on iOS 9, it should throw an exception like that :

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

You have to wonder why the same code don’t work on the last version of iOS. The answer is quite simple : App Transport Security (ATS).

In iOS 9, there are new security mechanisms that have been implemented to ensure that, when an app uses network communications, the traffic is encrypted to ensure confidentiality.

But if you look the URL of the video, it uses HTTPS so the traffic is encrypted. Then why an exception was thrown ? It seems that the ciphers used to encrypt the traffic are considered as unsafe by iOS 9.

What to do to be able to play/stream the video in that case ? It’s possible to configure how ATS works by modifying the info.plist file of your app.

There are many ways to do that and the most easiest is to add the following key/values :

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

For in-house apps or development purposes, it’s very useful but not secured because ATS is completely disabled. If you read Apple’s documentation, it also could be a reason to reject your app if you want to publish it on the AppStore.

If you want to manage more precisely which URLs can be used without problems with ATS, you have to add exception domains in your info.plist as you can see above :

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>mediaservices.windows.net</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSExceptionRequiresForwardSecrecy</key>
      <false/>
    </dict>
    <key>cloudvideo.azure.net</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSExceptionRequiresForwardSecrecy</key>
      <false/>
 </dict>
</dict>

With these settings, we configure ATS to allow more ciphers to encrypt HTTPS traffic, for two domains (and all sub-domains) used by Azure Media Services to stream videos.

If you prefer to use the editor available in Xcode to edit your info.plist, rather than manually editing the XML file, you just have to add key/values like :

Office365 Video - App Transport Security

Now if we execute our app, we are able to play/stream the video without any exceptions. 😉

Use ADAL for iOS/OSX with O365 Discovery REST API

You want to access your Office 365 data from an iOS / OSX application and don’t know how to start ? You are in the right place 😉

In this article, we will see the first step to write an application connected to Office 365 which consists to discover what services are available for the current user and what are the endpoints URLs for each.

When you want to connect to Office 365 from an application and use the REST API to retrieve data, you need to authenticate against Azure Active Directory (each user accounts are stored in AAD).

To help us to achieve this goal, Microsoft has released a library named ADAL (Active Directory Azure Library) which was written in Objective-C and can be used with iOS and OSX.

You can download ADAL for free on GitHub : https://github.com/AzureAD/azure-activedirectory-library-for-objc

The next step to be able to write an application which operates with Office 365 is to declare it in the Azure directory associated to your Office 365 tenant. If you are a global administrator of your tenant, you can achieve this by using the Azure Management Portal : https://manage.windowsazure.com

On the Azure Management Portal, go to Active Directory section, select the appropriate directory, select applications and create a new native application.

After you fill the requested information (a name and a redirect URL), you have to declare for each service (SharePoint, Exchange, Active Directory…) what permissions are needed by your application as you can see below.

Azure Manage Portal - Application Permissions

We are now ready to write code to connect to Office 365 and to discover what services are available for our user account.

In your application (it works in the same way for iOS and OSX), add the ADAL library in your project as usual when you use a third party library/framework.

    NSString *clientId = @"12345678-abcd-1234-abcd-1234567890ab";
    NSURL *redirectUri = [NSURL URLWithString:@"https://beecomedigitaldemo"];

    ADAuthenticationContext *authenticationContext = [ADAuthenticationContext authenticationContextWithAuthority:@"https://login.windows.net/common" error:nil];
    [authenticationContext acquireTokenWithResource:@"https://api.office.com/discovery/" clientId:clientId redirectUri:redirectUri completionBlock:^(ADAuthenticationResult *result) {
        if (result.status == AD_SUCCEEDED) {
            // Authentication succeeded
        }
        else if (result.status == AD_USER_CANCELLED) {
            // Authentication cancelled by the user
        }
        else {
            // Authentication failed
        }
    }];

To authenticate against Azure Active Directory, we have to use the ADAuthenticationContext class available in ADAL.

To instanciate a context, you need to indicate what is the authentication authority you will use. The default authority to logon with Azure Active Directory is https://login.windows.net/common but if it’s applicable for your company, you can specify another authority.

After the instanciation of the context, you have to acquire an authentication token for the resource which you will request later.

In our case, we want to discover services offered by Office 365. The resource associated is https://api.office.com/discovery/ (be careful to not omit the trailing slash character in the URL otherwise it will not work).

When you want to get a token by calling the acquireTokenWithResource method, you have to set a clientId and a redirectUri. These values are those you have declared in the Azure Management Portal.

This method executes asynchronously so when it’s finished, the completionBlock passed in the last parameter is executed. To ensure that the request was successfull, you can test the result status. If everything was good, status will be equal to AD_SUCCEED. Otherwise it will be equal to AD_FAILED or AD_USER_CANCELLED.

Now that we are successfully authenticated and we have a valid authentication token, we can discover what services are available. To do it, we just have to send a request to the discovery REST API endpoint.

// In-house wrapper of NSURLSession to simplify HTTP communications
BCDHttpClient *httpClient = [[BCDHttpClient alloc] init];

NSURL *serviceURL = [NSURL URLWithString:@"https://api.office.com/discovery/v1.0/me/services"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:serviceURL];
[request setValue:[NSString stringWithFormat:@"Bearer %@", result.accessToken] forHTTPHeaderField:@"Authorization"];

[httpClient dataWithRequest:request completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) {
    if (error == nil) {
        id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        NSLog(@"%@", jsonObject);
    }
}];

As you can see above, we build a NSMutableURLRequest with the URL of the Office 365 discovery endpoint : https://api.office.com/discovery/v1.0/me/services

To call this endpoint and retrieve information associated to our user profile, we need to add an Authorization HTTP header with the following format : “Bearer our_access_token“.

The access token is the one that have been retrieved by ADAL and is accessible in the result parameter of the completion block.

Then we execute the request by using our in-house implementation of a HTTP client (just a lightweight wrapper around NSURLSession). If the request complete successfully, we deserialize the retrieved data (in JSON format) and print them in the console.

You can see below the result printed in our console for all services found for the current authenticated user. In this example, our user have access to SharePoint and Exchange with many different services for each (MyFiles, RootSite, Calendar, Contacts and Mail).

{
    "@odata.context" = "https://api.office.com/discovery/v1.0/me/$metadata#allServices";
    value =     (
                {
            "@odata.editLink" = "services('MyFiles@O365_SHAREPOINT')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('MyFiles@O365_SHAREPOINT')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = MyFiles;
            entityKey = "MyFiles@O365_SHAREPOINT";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://beecomedigitaldemo-my.sharepoint.com/_api/v1.0/me";
            serviceId = "O365_SHAREPOINT";
            serviceName = "Office 365 SharePoint";
            serviceResourceId = "https://beecomedigitaldemo-my.sharepoint.com/";
        },
                {
            "@odata.editLink" = "services('RootSite@O365_SHAREPOINT')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('RootSite@O365_SHAREPOINT')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = RootSite;
            entityKey = "RootSite@O365_SHAREPOINT";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://beecomedigitaldemo.sharepoint.com/_api";
            serviceId = "O365_SHAREPOINT";
            serviceName = "Office 365 SharePoint";
            serviceResourceId = "https://beecomedigitaldemo.sharepoint.com/";
        },
                {
            "@odata.editLink" = "services('Contacts@O365_EXCHANGE')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('Contacts@O365_EXCHANGE')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = Contacts;
            entityKey = "Contacts@O365_EXCHANGE";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://outlook.office365.com/api/v1.0";
            serviceId = "O365_EXCHANGE";
            serviceName = "Office 365 Exchange";
            serviceResourceId = "https://outlook.office365.com/";
        },
                {
            "@odata.editLink" = "services('Mail@O365_EXCHANGE')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('Mail@O365_EXCHANGE')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = Mail;
            entityKey = "Mail@O365_EXCHANGE";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://outlook.office365.com/api/v1.0";
            serviceId = "O365_EXCHANGE";
            serviceName = "Office 365 Exchange";
            serviceResourceId = "https://outlook.office365.com/";
        },
                {
            "@odata.editLink" = "services('Calendar@O365_EXCHANGE')";
            "@odata.id" = "https://api.office.com/discovery/v1.0/me/services('Calendar@O365_EXCHANGE')";
            "@odata.type" = "#Microsoft.DiscoveryServices.ServiceInfo";
            capability = Calendar;
            entityKey = "Calendar@O365_EXCHANGE";
            providerId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
            providerName = Microsoft;
            serviceAccountType = 2;
            serviceApiVersion = "v1.0";
            serviceEndpointUri = "https://outlook.office365.com/api/v1.0";
            serviceId = "O365_EXCHANGE";
            serviceName = "Office 365 Exchange";
            serviceResourceId = "https://outlook.office365.com/";
        }
    );
}

As mentioned before, we have to declare what permissions are needed by our application. But what happens exactly for the user if he’s not agree with these permissions ?

When ADAL tries to acquire an access token and if the user account is not stored in the same directory as your tenant, he must consent to authorize the application for the permissions you are requested.

ADAL - User ConsentIf the user doesn’t consent to authorize your application to use the required permissions, ADAL returns an AD_FAILED status when you call acquireTokenWithResource. The consent is required only once. For subsequent requests the same consent is used by ADAL.

Now you are theorically able to use ADAL and Office 365 Discovery REST API to authenticate and retrieve information about available services for the current user  😉