GraphQL iOS: Usare le funzioni cloud in un’applicazione Swift

In questo articolo vi ho mostrato come utilizzare GraphQL con NodeJS Cloud Code in Back4app.

In questa guida, invece, vi ho mostrato come utilizzare Apollo GraphQL iOS Client con Swift.

Ora mettiamo tutto insieme e realizziamo molte operazioni complicate producendo codice davvero semplice. E facciamo in modo che XCode generi automaticamente qualsiasi codice API anche quando cambiamo classe.

Vi sembra una buona idea? Allora prendete un caffè e andate avanti!

Prima di iniziare…

È necessario un account Back4app con un’applicazione creata. Se non ne avete ancora uno, seguite questa guida e avrete tutto in un attimo.

Alcuni software che ci aiuteranno…

Utilizzeremo anche alcuni software per velocizzare le cose.

Naturalmente, se preferite, potete fare tutto manualmente, ma non vi consiglio questa strada, soprattutto per i principianti.
Aggiungendo manualmente i pacchetti, si dovrà anche mantenere manualmente i pacchetti, il che può essere fastidioso dopo un po’ di tempo.

Non è necessario usare i gestori di pacchetti che illustrerò qui, ma consiglio vivamente di usare almeno alcuni gestori di pacchetti invece di fare la gestione manuale. Come ho detto in precedenza, la gestione manuale può diventare bizzarra nel tempo.

Utilizzerò Cocoapods per la gestione dei pacchetti di iOS e NPM per la gestione del desktop, ma anche in questo caso, sentitevi liberi di usare quello che più vi aggrada.

Le istruzioni per installare Cocoapods si trovano su questo sito.

Le istruzioni per l’installazione di NPM sono disponibili su questo sito.

Creare le classi

Verranno implementate 3 classi: Proprietario, Cane e Razza.

Il nostro modello implica che:

  • Un proprietario può avere più cani
  • Un cane può avere solo una razza

Le nostre proprietà saranno:

  • Proprietario
    • nome (stringa)
    • età (Numero)
    • indirizzo (stringa)
    • hasDogs (relazione con Dog, poiché un proprietario può avere più cani)
  • Cane
    • nome (stringa)
    • compleanno (Data)
    • razza (indicare la razza, poiché un cane ha una sola razza)
  • Razza
    • Nome (stringa

Creare queste classi graficamente o programmaticamente e popolarle con alcuni dati. Le mie classi sono finite così:

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

Razza

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

Cane

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

Proprietario

Portate il Codice Cloud, baby!

È tempo di un po’ di Cloud Code. Ma questa volta voglio approfondire anche questo aspetto.

Vi ho mai detto che Cloud Code è compatibile con i moduli NPM? Lo è! E può farvi risparmiare un sacco di tempo!

Avete presente quella funzionalità che avreste voluto avere e che qualcun altro ha già fatto? Beh, se è un modulo NPM, potete usarlo!

C’è già un articolo su come usare i moduli in questo tutorial, quindi per questa implementazione userò Moment, in modo da poter usare il compleanno di un cane e calcolare la sua età in mesi.

Quindi il mio file package.json avrà questo aspetto:

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

L’* significa che userò l’ultima versione.

Il mio codice Cloud è finito come sotto. È ben commentato, quindi non entrerò molto nel dettaglio, ma si può vedere nella prima riga che sto usando il modulo NPM Moment:

// Istanziare il modulo NPM Moment
const moment = require('moment')

Parse.Cloud.define("retrieveOwnersOrderedByAgeDescending", async req => {
    /*
        Questa funzione recupera tutti i proprietari ordinati per età decrescente.
        Se non ci sono proprietari, viene recuperato un array vuoto.
    */
    const query = new Parse.Query("Owner"); 
    query.descending("età")
    const results = await query.find();

    restituisce i risultati;
});

Parse.Cloud.define("retrieveAllGermanShepherds", async req => {
    /*
        Questa funzione recupera i cani che hanno la razza "Pastore tedesco".
        Vengono recuperate tutte le proprietà dei cani.
        Se non ci sono cani, viene recuperato un array vuoto.
    */
    const breedQuery = new Parse.Query("Breed");
    breedQuery.equalTo("name", "German Shepherd") 
    const query = new Parse.Query("Dog"); 
    query.matchesQuery("razza", breedQuery);
    const results = await query.find();
    restituisce i risultati;
});

Parse.Cloud.define("retrieveDogIncludingBreedByName", async req => {
    /*
        Questa funzione recupera i dettagli di un cane specifico, compresi quelli della sua razza (nome), per nome del cane.
        Vengono recuperate tutte le proprietà dei cani e della razza.
        Se non ci sono cani, viene recuperato null.
    */
    const query = new Parse.Query("Dog"); 
    query.equalTo("name", req.params.name);
    query.include("razza")
    const results = await query.find();
    restituisce i risultati;
});

Parse.Cloud.define("retrieveDogsNamesWithAgesInMonths", async req => {
    /*
        Questa funzione recupera i nomi dei cani con la proprietà compleanno convertita in mesi utilizzando il modulo NPM moment.
        Vengono recuperati solo i nomi dei cani e l'età viene calcolata in mesi.
        Se non ci sono cani, viene recuperato un array vuoto.
    */
    let dogs = [];
    const query = new Parse.Query("Dog"); 
    const results = await query.find();

    for (let i = 0; i < results.length; i ++){
        // calcolo dell'età in mesi utilizzando il modulo 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;
});

Il mio file schema.graphql, che espone i miei quattro metodi, risulta così:

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

tipo DogAge {
    dogName: String!
    dogAgeInMonths: String!
}

Se non si comprende la sintassi, consultare questo articolo nella sezione “Un piccolo passo in più”.
Si noti solo il [DogAge!]! su getDogsAgesInMonths. Poiché recupererò un oggetto generato dal sistema, non avrà uno schema che GraphQL possa interpretare, quindi devo creare anche il tipo, in modo che il nostro client possa interpretarlo.

Test… Test…

È ora di fare qualche test!

Andiamo alla nostra console Parse GraphQL ed eseguiamo le nostre query, verificandone i risultati:

Query per getAllOwnersAgeDescending:

query{
  getAllOwnersAgeDescending {
    nome
    età
    indirizzo
  }
}

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

Query per getAllGermanShepherds:

query {
  getAllGermanShepherds {
    nome
    compleanno
  }
}

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

Query per getThisDogWithBreed:

query{
  getThisDogWithBreed(nome: "Fido"){
    nome
    compleanno
    razza{
      nome
    }
  }
}

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

Query per getDogsAgesInMonths:

query{
  getCaniEtàNeiMesi
}

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

Tutto funziona! Ottimo! Ora è il momento di…

Un po’ di XCoding

Abbiamo tutto ciò che ci serve da Back4app e siamo pronti. È ora di consumarlo su XCode.

Questa guida ha mostrato come farlo funzionare, ma questa volta siamo un po’ più avanzati: abbiamo più metodi, uno dei quali si aspetta delle variabili, quindi dovremo apportare alcune modifiche.

Se non avete ancora seguito l’articolo, vi consiglio vivamente di farlo, perché è molto dettagliato.

Iniziamo creando l’applicazione e installando il client Apollo come descritto nell’articolo.

Dopodiché, aprite il file Main.storyboard, fate clic sul pulsante Oggetti nell’angolo in alto a destra e trascinate una Vista tabella nel Controllore della vista:

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

Modificare le dimensioni della vista Tabella in modo che corrisponda all’intera vista del controllore della vista, trascinando gli angoli:

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

Creare i vincoli per tutti e quattro i lati facendo clic sul pulsante Aggiungi nuovi vincoli in basso a destra dello schermo. Fare clic sui quattro lati (marcatori rossi) e fare clic sul pulsante Aggiungi vincoli per assegnare i nuovi vincoli:

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

Ora, con la vista Tabella selezionata (fare clic su di essa per selezionarla), aggiungiamo una cella prototipo. Fare clic sull’Ispettore Attributi in alto a destra e sostituire lo 0 con un 1 nella casella Celle prototipo:

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

Ora, fare clic sulla nuova cella prototipo creata per selezionarla e assegnarle un buon identificatore: “cella”.
Lo utilizzeremo più avanti nel codice per identificare la cella.

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

Ora dobbiamo collegare l’origine dati e il delegato dalla vista Tabella al controllore della vista.
Tenendo premuto il tasto Control sulla tastiera, fare clic sulla Vista tabella e trascinarla sull’icona gialla in cima al controllore della vista, quella che al passaggio del mouse dice View Controller.
Rilasciando il collegamento alla Vista tabella, apparirà una piccola finestra a comparsa. Scegliere sia DataSource che Delegate nel popup:

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

Ora, questo copre le basi, ma dobbiamo ancora invocare un aggiornamento della vista tabella dopo aver recuperato i dati da Back4app. Per farlo, dobbiamo creare un’uscita e collegarla alla nostra interfaccia utente.

Il modo più semplice per farlo è fare clic su Show Assistant Editor in alto a destra, in modo da poter vedere fianco a fianco l’interfaccia utente e il codice.

Quindi, fare clic con Control sulla vista tabella e trascinarla nel codice:

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

Quando si rilascia, si aprirà una finestra a comparsa. Inserite il nome e fate clic su Connetti:

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

Fatto ciò, si dovrebbe avere una bella uscita collegata (si noti il piccolo cerchio riempito sul numero di riga) e quindi si può ora richiamare il codice per quella Vista tabella:

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

Fatto questo, possiamo andare al nostro file ViewController.swift perché è il momento di…

Il nostro file GraphQL

La nostra guida GraphQL vi ha mostrato come creare il vostro file GraphQL. Per questo progetto, il mio era così:

query findAllOwners{
    getAllOwnersAgeDescending{
        nome
        età
        indirizzo
    }
}

query findAllGermanShepherds{
    getAllGermanShepherds {
        nome
    }
}

query findThisDog ($nome: Stringa!){
    getThisDogWithBreed(nome: $nome){
        nome
        compleanno
        razza{
            nome
        }
    }
}

query agesInMonths{
    getCaniEtàNeiMesi
}

Un po’ di codice Swift

Anche in questaguida abbiamo già spiegato come configurare Apollo in Swift, ma il codice di oggi presenta alcune modifiche.
Dato che visualizzeremo i dati graficamente sulla nostra Table View, dobbiamo includere due metodi per l’esecuzione della nostra Data Source e del Delegate. Questi metodi sono

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

e

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

Non mi dilungherò molto su questo aspetto, poiché è documentato nel codice. Tenete solo presente che questi due metodi sono obbligatori per il funzionamento della nostra Table View.

Il codice completo dovrebbe assomigliare a questo:

//
// ViewController.swift
// GraphQLApollo
//
// Creato da Venom il 19/08/19.
// Copyright © 2019 Venom. Tutti i diritti riservati.
//

importare UIKit
importare Apollo

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    @IBOutlet weak var tableView: UITableView!
    
    var list = [] as [String] // Questo array conterrà i valori da visualizzare
    
    // Inizializzazione del client Apollo.
    // Per saperne di più: 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(stringa: "https://parseapi.back4app.com/graphql")!
        
        return ApolloClient(
            networkTransport: HTTPNetworkTransport(
                url: url,
                configurazione: configurazione
            )
        )
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Effettua qualsiasi impostazione aggiuntiva dopo il caricamento della vista.
        
        apollo.fetch(query: FindAllOwnersQuery()) { result in
            guard let data = try? result.get().data else { return }
            
            for name in data.getAllOwnersAgeDescending {
                //Applica ogni nome al nostro array di liste
                self.list.append(nome.nome!)
            }
            
            //la riga sottostante forzerà il ricaricamento della nostra vista tabella dopo aver recuperato i dati
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // Questa funzione viene richiamata dalla nostra tableview attraverso l'origine dati e il delegato.
        // Restituisce semplicemente il numero di oggetti che la Table View deve mostrare.
        // È obbligatoria per il funzionamento della vista tabella.
        restituisce list.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // Questa funzione viene richiamata dalla nostra Table View attraverso l'origine dati e il delegato.
        // Crea le celle da visualizzare con l'identificatore impostato nello Storyboard.
        // È obbligatoria per il funzionamento della Vista tabella.
        let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
        cell.textLabel?.text = list[indexPath.row]
        return(cell)
    }
}

Eseguiamolo!

È ora di eseguire il nostro codice! Premete Command + R e, se tutto è corretto, dovreste vedere i nostri nomi nella vista Tabella:

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

Forte, eh?
Ora possiamo anche cambiare il nostro codice per eseguire altri metodi GraphQL e vedere come funziona!

Cambiamo la nostra query GraphQL per eseguire il metodo getAllGermanShepherds:

    override func viewDidLoad() {
        super.viewDidLoad()
        // Eseguiamo qualsiasi impostazione aggiuntiva dopo il caricamento della vista.
        
        apollo.fetch(query: FindAllGermanShepherdsQuery()) { result in
            guard let data = try? result.get().data else { return }
            
            print(data.getAllGermanShepherds)
            
            for name in data.getAllGermanShepherds {
                //Aggiunge ogni nome all'array della nostra lista
                self.list.append(nome.nome!)
            }
            
            //la riga sottostante forzerà il ricaricamento della nostra vista tabella dopo aver recuperato i dati
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    }

e il risultato…

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

E se volessimo passare una variabile alla nostra query?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Eseguiamo qualsiasi impostazione aggiuntiva dopo il caricamento della vista.
        
        apollo.fetch(query: FindThisDogQuery(nome: "Fido")) { result in
            guard let data = try? result.get().data else { return }
            
            print(data.getThisDogWithBreed)
            
            for name in data.getThisDogWithBreed {
                //Aggiunge ogni nome all'array della nostra lista
                self.list.append(nome.nome! + " - " + (nome.razza?.nome!)!
            }
            
            //la riga sottostante forzerà il ricaricamento della nostra vista tabella dopo aver recuperato i dati
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    }

Ora sappiamo che Fido è un carlino:

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

E che dire del metodo che restituisce un nuovo tipo? Testiamo anche questo:

   override func viewDidLoad() {
        super.viewDidLoad()
        // Eseguiamo qualsiasi impostazione aggiuntiva dopo il caricamento della vista.
        
        apollo.fetch(query: AgesInMonthsQuery()) { result in
            guard let data = try? result.get().data else { return }

            print(data.getDogsAgesInMonths)

            for dog in data.getDogsAgesInMonths {
                //Aggiunge ogni nome all'array della nostra lista
                self.list.append(dog.dogName + " - " + dog.dogAgeInMonths)
            }

            //la riga sottostante forzerà il ricaricamento della nostra vista tabella dopo aver recuperato i dati
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    } 

e il risultato:

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

Conclusione

Il codice cloud è bello. GraphQL è superfigo. Apollo porta un nuovo livello di meraviglia alle applicazioni native iOS. Con tutti e tre gli strumenti insieme, avete la possibilità di creare applicazioni molto complesse con uno sforzo minimo.

Ora che siete in grado di creare un flusso completamente funzionante per un’applicazione, facile da creare e da mantenere nel tempo, la domanda da porsi è: cosa creerete dopo?

Spero che questo articolo vi sia piaciuto! Presto ne pubblicheremo altre! Restate sintonizzati!

Quale software può essere utilizzato per utilizzare le funzioni cloud nell’app Swift?

Dipenderà dalla tua scelta. Dovresti usare un software che sia facile da usare e utilizzare. Ho usato i due software elencati di seguito nella pratica precedente.

– Cocoapods per la gestione dei pacchetti iOS
– NPM per la gestione dei pacchetti Desktop

Qual è la magia dell’NPM?

Il codice cloud è compatibile con i moduli NPM. Questa è un’ottima cosa. Ti aiuterà a risparmiare un sacco di tempo. Ti darà anche un vantaggio sulla concorrenza. Quindi, la compatibilità NPM con i codici cloud ti darà una spinta.


Leave a reply

Your email address will not be published.