GraphQL iOS : Utiliser les fonctions cloud dans une application Swift

Dans cet article, je vous ai montré comment utiliser GraphQL avec NodeJS Cloud Code dans Back4app.

Et dans ce guide, je vous ai montré comment utiliser Apollo GraphQL iOS Client avec Swift.

Maintenant, rassemblons tout cela et faisons des tas d’opérations compliquées en produisant du code vraiment simple. Et faisons en sorte que XCode génère automatiquement n’importe quel code d’API pour nous, même lorsque nous changeons de classe.

Cela vous convient ? Alors prenez un café et commencez !

Avant de commencer…

Vous aurez besoin d’un compte Back4app avec une application créée. Si vous n’en avez pas encore, suivez ce guide et vous aurez tout en un rien de temps.

Quelques logiciels qui nous aideront…

Nous allons également utiliser quelques logiciels pour accélérer les choses.

Bien sûr, vous pouvez tout faire manuellement si vous préférez, mais je ne recommande pas cette voie, surtout pour les débutants.
En ajoutant manuellement des paquets, vous devrez aussi les maintenir manuellement, ce qui peut être gênant au bout d’un certain temps.

Vous n’êtes pas obligé d’utiliser les gestionnaires de paquets que je vais vous présenter ici, mais je vous recommande fortement d’utiliser au moins quelques gestionnaires de paquets au lieu de faire de la gestion manuelle. Comme je l’ai déjà dit, la gestion manuelle peut devenir bizarre avec le temps.

J’utiliserai Cocoapods pour la gestion des paquets iOS et NPM pour la gestion des paquets desktop, mais encore une fois, n’hésitez pas à utiliser ce qui vous convient.

Les instructions pour installer Cocoapods se trouvent sur ce site.

Les instructions pour l’installation de NPM se trouvent sur ce site.

Création de nos classes

Nous allons mettre en place 3 classes : Propriétaire, Chien et Race.

Notre modèle impliquera que :

  • Un propriétaire peut avoir plusieurs chiens
  • Un chien ne peut avoir qu’une seule race

Nos propriétés seront les suivantes

  • Propriétaire
    • nom (Chaîne)
    • âge (Nombre)
    • adresse (Chaîne)
    • hasDogs (Relation avec le chien, car un propriétaire peut avoir plusieurs chiens)
  • chien
    • name (Chaîne)
    • date d’anniversaire (Date)
    • race (Pointer vers Race, car un chien n’a qu’une seule race)
  • Race
    • Nom (Chaîne

Créez ces classes graphiquement ou par programme et remplissez-les avec des données. Mes classes ont fini par ressembler à ceci :

screen-shot-2019-08-15-at-12-08-46

Race

screen-shot-2019-08-15-at-13-50-39

Chien

screen-shot-2019-08-15-at-12-15-45

Propriétaire

Le code cloud, c’est parti !

C’est l’heure du Cloud Code. Mais cette fois-ci, je veux aussi aller plus loin.

Vous ai-je déjà dit que Cloud Code est compatible avec les modules NPM? C’est le cas ! Et il peut vous faire gagner BEAUCOUP de temps !

Vous savez, cette fonctionnalité cool que vous vouliez avoir, et que quelqu’un d’autre a déjà fait ? Eh bien, s’il s’agit d’un module NPM, vous pouvez l’utiliser !

Il y a déjà un article sur l’utilisation des modules dans ce tutoriel, donc pour cette implémentation, je vais utiliser Moment pour pouvoir utiliser la date d’anniversaire d’un chien et calculer son âge en mois.

Mon fichier package.json ressemblera donc à ceci :

{
      "dependencies" : {
           "moment" : "*"
      }
}

L’astérisque (*) signifie que j’utiliserai la dernière version.

Mon code Cloud a fini par ressembler à ce qui suit. Il est bien commenté donc je ne vais pas entrer dans les détails ici, mais vous pouvez voir sur la toute première ligne que j’utilise le module Moment NPM :

// Instanciation du module Moment NPM
const moment = require('moment')

Parse.Cloud.define("retrieveOwnersOrderedByAgeDescending", async req => {
    /*
        Cette fonction récupère tous les propriétaires classés par âge décroissant.
        S'il n'y a pas de propriétaires, un tableau vide est récupéré.
    */
    const query = new Parse.Query("Owner") ; 
    query.descending("age")
    const results = await query.find() ;

    retourne les résultats ;
}) ;

Parse.Cloud.define("retrieveAllGermanShepherds", async req => {
    /*
        Cette fonction récupère les chiens de la race "Berger allemand"
        Toutes les propriétés des chiens sont récupérées.
        S'il n'y a pas de chien, un tableau vide est récupéré.
    */
    const breedQuery = new Parse.Query("Breed") ;
    breedQuery.equalTo("nom", "Berger allemand") 
    const query = new Parse.Query("Dog") ; 
    query.matchesQuery("race", breedQuery) ;
    const results = await query.find() ;
    retourne les résultats ;
}) ;

Parse.Cloud.define("retrieveDogIncludingBreedByName", async req => {
    /*
        Cette fonction récupère les détails d'un chien spécifique, y compris les détails de sa race (nom) par nom de chien
        Toutes les propriétés des chiens et des races sont récupérées.
        S'il n'y a pas de chien, null est récupéré.
    */
    const query = new Parse.Query("Dog") ; 
    query.equalTo("nom", req.params.nom) ;
    query.include("race")
    const results = await query.find() ;
    retour des résultats ;
}) ;

Parse.Cloud.define("retrieveDogsNamesWithAgesInMonths", async req => {
    /*
        Cette fonction récupère les noms des chiens avec la propriété anniversaire convertie en mois en utilisant le moment du module NPM
        Seuls les noms des chiens sont récupérés et l'âge est calculé en mois.
        S'il n'y a pas de chien, un tableau vide est récupéré.
    */
    let dogs = [] ;
    const query = new Parse.Query("Dog") ; 
    const results = await query.find() ;

    for (let i = 0 ; i < results.length ; i ++){
        // calcul de l'âge en mois à l'aide du module NPM Moment
        let ageInMonths = moment.duration(moment().diff(results[i].get("birthday"))).humanize() ;
        
        let newDog = {
            "dogName" : results[i].get("name"),
            "dogAgeInMonths" : ageInMonths
        }

        dogs.push(newDog) ;
    }

    return dogs ;
}) ;

Mon fichier schema.graphql exposant mes quatre méthodes se présente comme suit :

extend type Query {
  getAllOwnersAgeDescending : [OwnerClass !]! @resolve(to : "retrieveOwnersOrderedByAgeDescending")
  getAllGermanShepherds : [ClasseChien !]! @resolve(to : "retrieveAllGermanShepherds")
  getThisDogWithBreed (name:String) : [ClasseChien !]! @resolve(to : "retrieveDogIncludingBreedByName")
  getDogsAgesInMonths : [DogAge !]!  @resolve(to : "retrieveDogsNamesWithAgesInMonths")
}

type DogAge {
    dogName : String !
    dogAgeInMonths : Chaîne !
}

Si vous ne comprenez pas la syntaxe, consultez cet article dans la section “Une petite étape supplémentaire”.
Remarquez simplement le [DogAge !]! dans getDogsAgesInMonths. Comme je vais récupérer un objet généré par le système, il n’aura pas de schéma à interpréter par GraphQL, donc je dois aussi créer le type pour que notre client puisse l’interpréter.

Test… Test…

Il est temps de tester !

Allons dans notre Parse GraphQL Console et lançons nos requêtes, en vérifiant les résultats :

Requête pour getAllOwnersAgeDescending :

requête{
  getAllOwnersAgeDescending {
    nom
    âge
    adresse
  }
}

screen-shot-2019-08-16-at-09-17-50

Requête pour getAllGermanShepherds :

requête {
  getAllGermanShepherds {
    nom
    anniversaire
  }
}

screen-shot-2019-08-16-at-09-46-24

Requête pour getThisDogWithBreed :

requête{
  getThisDogWithBreed(name : "Fido"){
    nom
    anniversaire
    race{
      nom
    }
  }
}

screen-shot-2019-08-16-at-09-50-05

Requête pour getDogsAgesInMonths :

requête{
  getDogsAgesInMonths
}

screen-shot-2019-08-16-at-09-50-51

Tout fonctionne ! C’est parfait ! Il est maintenant temps de…

Un peu de XCoding

Nous avons tout ce dont nous avons besoin de la part de Back4app. Il est temps de le consommer sur XCode.

Ce guide a montré comment faire fonctionner XCode, mais cette fois nous sommes un peu plus avancés : nous avons plusieurs méthodes, l’une d’entre elles attend des variables, nous allons donc devoir faire quelques changements.

Si vous n’avez pas encore suivi cet article, je vous conseille vivement de le faire car il est très détaillé.

Commençons par créer l’application et installer le client Apollo comme décrit dans l’article.

Ensuite, ouvrez votre fichier Main.storyboard, cliquez sur le bouton Objets dans le coin supérieur droit et glissez-déposez un Table View dans le View Controller :

screen-shot-2019-08-19-at-13-00-29

Modifiez la taille de votre Table View pour qu’elle corresponde à l’ensemble de la vue du View Controller en faisant glisser les coins :

screen-shot-2019-08-19-at-13-01-01

Créez des contraintes pour les quatre côtés en cliquant sur le bouton Ajouter de nouvelles contraintes en bas à droite de l’écran. Cliquez sur les quatre côtés (marqueurs rouges) et cliquez sur le bouton Ajouter des contraintes pour attribuer ces nouvelles contraintes :

screen-shot-2019-08-19-at-13-01-17

Maintenant que la vue Tableau est sélectionnée (cliquez dessus pour la sélectionner), ajoutons une cellule prototype. Cliquez sur l’inspecteur d’attributs en haut à droite et remplacez le 0 par un 1 dans la case Cellules de prototype :

screen-shot-2019-08-19-at-13-01-30

Cliquez maintenant sur la cellule prototype nouvellement créée pour la sélectionner et donnez-lui un bon identifiant : “cell”.
Nous l’utiliserons plus tard dans le code pour identifier cette cellule.

screen-shot-2019-08-19-at-13-13-07

Nous devons maintenant relier notre source de données et notre délégué de la vue Tableau à notre contrôleur de vue.
Tout en maintenant la touche Contrôle de votre clavier, cliquez sur la vue Tableau et faites-la glisser jusqu’à l’icône jaune située en haut de votre contrôleur de vue, celle qui indique Contrôleur de vue lorsque vous la survolez.
En relâchant le lien Table View à cet endroit, une petite fenêtre s’affiche. Choisissez DataSource et Delegate dans la fenêtre popup :

screen-shot-2019-08-19-at-13-02-09

Cela couvre les bases, mais nous aurons toujours besoin d’invoquer un rafraîchissement sur ce Table View après avoir récupéré nos données de Back4app. Pour ce faire, nous devons créer un Outlet et le connecter à notre interface utilisateur.

La façon la plus simple de le faire est de cliquer sur Show Assistant Editor en haut à droite de l’écran, de sorte que vous puissiez voir côte à côte l’interface utilisateur et le code.

Contrôlez ensuite votre Table View et faites-la glisser vers le code :

screen-shot-2019-08-19-at-17-10-53

Lorsque vous relâchez le bouton, vous obtenez une fenêtre contextuelle. Remplissez le nom et cliquez sur Connect :

screen-shot-2019-08-19-at-17-08-14

Ceci fait, vous devriez avoir un joli Outlet connecté (remarquez le petit cercle rempli sur le numéro de ligne) et nous pouvons maintenant invoquer le code pour cette Table View :

screen-shot-2019-08-19-at-17-08-34

Une fois tout cela fait, nous pouvons aller dans notre fichier ViewController.swift car il est temps de…

Notre fichier GraphQL

Notre guide GraphQL vous a montré comment créer votre fichier GraphQL. Pour ce projet, le mien se termine ainsi :

query findAllOwners{
    getAllOwnersAgeDescending{
        nom
        âge
        adresse
    }
}

requête findAllGermanShepherds{
    getAllGermanShepherds {
        nom
    }
}

query findThisDog ($name : String !){
    getThisDogWithBreed(name : $name){
        nom
        anniversaire
        race{
            nom
        }
    }
}

requête agesInMonths{
    getDogsAgesInMonths
}

Un peu de code Swift

Une fois de plus, nous avons déjà couvert comment configurer Apollo en Swift surce guide, mais le code d’aujourd’hui a quelques changements.
Comme nous allons afficher les données graphiquement sur notre Table View, nous devons inclure deux méthodes à exécuter pour notre Data Source et notre Delegate. Ces méthodes sont les suivantes

func tableView(_ tableView : UITableView, numberOfRowsInSection section : Int) -> Int

et

func tableView(_ tableView : UITableView, cellForRowAt indexPath : IndexPath) -> UITableViewCell

Je ne vais pas m’étendre sur ce sujet car il est documenté dans le code. Gardez simplement à l’esprit que ces deux méthodes sont obligatoires pour que notre Table View fonctionne.

Votre code complet devrait ressembler à ceci :

//
// ViewController.swift
// GraphQLApollo
//
// Créé par Venom le 19/08/19.
// Copyright © 2019 Venom. Tous droits réservés.
//

import UIKit
import Apollo

class ViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
    @IBOutlet weak var tableView : UITableView !
    
    var list = [] as [String] // Ce tableau contiendra les valeurs à afficher.
    
    // Initialisation du client Apollo.
    // Plus d'informations ici : https://www.back4app.com/docs/ios/swift-graphql
    let apollo : ApolloClient = {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = [
            "X-Parse-Application-Id" : "lAcIOgR0ndd7R4uMqovhNAoudpi6tMsrOI9KCuyr",
            "X-Parse-Client-Key" : "f1wwm6uWqQoxazys3QrrtY4fKuQuxYvNYJmYQJP"
        ]
        
        let url = URL(string : "https://parseapi.back4app.com/graphql") !
        
        return ApolloClient(
            networkTransport : HTTPNetworkTransport(
                url : url,
                configuration : configuration
            )
        )
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Effectue toute configuration supplémentaire après le chargement de la vue.
        
        apollo.fetch(query : FindAllOwnersQuery()) { result in
            guard let data = try ? result.get().data else { return }
            
            for name in data.getAllOwnersAgeDescending {
                //Applique chaque nom à notre tableau de liste
                self.list.append(name.name !)
            }
            
            //la ligne ci-dessous forcera le rechargement de notre Table View après avoir récupéré les données
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    }
    
    func tableView(_ tableView : UITableView, numberOfRowsInSection section : Int) -> Int {
        // Cette fonction est appelée par notre tableView par l'intermédiaire de la source de données et du délégué.
        // Elle renvoie simplement le nombre d'objets que la vue de table doit afficher.
        // Elle est obligatoire pour qu'une vue de tableau fonctionne.
        return list.count
    }
    
    func tableView(_ tableView : UITableView, cellForRowAt indexPath : IndexPath) -> UITableViewCell {
        // Cette fonction est appelée par notre TableView via la source de données et le délégué.
        // Elle crée les cellules à afficher avec l'identifiant que nous avons défini dans le Storyboard.
        // Cette fonction est obligatoire pour qu'une vue de table fonctionne.
        let cell = UITableViewCell(style : UITableViewCell.CellStyle.default, reuseIdentifier : "cell")
        cell.textLabel ?.text = list[indexPath.row]
        return(cell)
    }
}

Exécutons-le !

Il est temps d’exécuter notre code ! Appuyez sur Commande + R et si tout va bien, vous devriez voir nos noms dans votre vue Tableau :

screen-shot-2019-08-19-at-17-26-09

Cool, hein ?
Nous pouvons maintenant modifier notre code pour exécuter nos autres méthodes GraphQL et voir comment cela fonctionne !

Modifions notre requête GraphQL pour exécuter notre méthode getAllGermanShepherds :

    override func viewDidLoad() {
        super.viewDidLoad()
        // Effectuer toute configuration supplémentaire après le chargement de la vue.
        
        apollo.fetch(query : FindAllGermanShepherdsQuery()) { result in
            guard let data = try ? result.get().data else { return }
            
            print(data.getAllGermanShepherds)
            
            for name in data.getAllGermanShepherds {
                //Applique chaque nom à notre tableau de liste
                self.list.append(name.name !)
            }
            
            //la ligne ci-dessous forcera un rechargement de notre Table View après avoir récupéré les données
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    }

et le résultat…

screen-shot-2019-08-20-at-08-45-40

Et si nous voulions passer une variable à notre requête ?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Effectue toute configuration supplémentaire après le chargement de la vue.
        
        apollo.fetch(query : FindThisDogQuery(name : "Fido")) { result in
            guard let data = try ? result.get().data else { return }
            
            print(data.getThisDogWithBreed)
            
            for name in data.getThisDogWithBreed {
                //Applique chaque nom à notre tableau de liste
                self.list.append(name.name ! + " - " + (name.breed ?.name !)!)
            }
            
            //la ligne ci-dessous forcera un rechargement de notre Table View après avoir récupéré les données
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    }

Nous savons maintenant que Fido est un Carlin :

screen-shot-2019-08-20-at-08-52-29

Et cette méthode qui renvoie un nouveau type ? Testons-la également :

   override func viewDidLoad() {
        super.viewDidLoad()
        // Effectuer toute configuration supplémentaire après le chargement de la vue.
        
        apollo.fetch(query : AgesInMonthsQuery()) { result in
            guard let data = try ? result.get().data else { return }

            print(data.getDogsAgesInMonths)

            for dog in data.getDogsAgesInMonths {
                //Applique chaque nom à notre tableau de liste
                self.list.append(dog.dogName + " - " + dog.dogAgeInMonths)
            }

            //la ligne ci-dessous va forcer le rechargement de notre Table View après avoir récupéré les données
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    } 

et le résultat :

screen-shot-2019-08-20-at-10-16-21

Conclusion

Le code cloud, c’est cool. GraphQL, c’est super cool. Apollo apporte un nouveau niveau de génialité à vos applications natives iOS. Avec ces trois éléments réunis, vous disposez d’une puissance considérable pour créer des applications très complexes avec un minimum d’effort.

Maintenant que vous êtes capable de créer un flux fonctionnel pour une application, qui est facile à créer et à maintenir dans le temps, la question est vraiment : qu’allez-vous créer ensuite ?

J’espère que cet article vous a plu ! Nous en avons d’autres qui sortiront bientôt ! Restez à l’écoute !

Quel logiciel peut être utilisé pour utiliser les fonctions cloud dans l’application Swift ?

Cela dépend de votre choix. Privilégiez un logiciel facile à utiliser. J’ai utilisé les deux logiciels ci-dessous pour la pratique ci-dessus.

– Cocoapods pour la gestion des paquets iOS
– NPM pour la gestion des paquets Desktop

Quelle est la magie du NPM ?

Le code cloud est compatible avec les modules NPM. C’est un atout majeur : il vous fera gagner un temps précieux et vous donnera un avantage concurrentiel. La compatibilité NPM avec le code cloud vous donnera donc un avantage considérable.


Leave a reply

Your email address will not be published.