GraphQL iOS: Usando funções de nuvem em um aplicativo Swift

Neste artigo, mostrei a você como usar o GraphQL com o NodeJS Cloud Code no Back4app.

E neste guia, mostrei a você como usar o Apollo GraphQL iOS Client com Swift.

Agora vamos juntar tudo isso e fazer muitas operações complicadas produzindo um código realmente fácil. E também vamos fazer com que o XCode gere automaticamente qualquer código de API para nós, mesmo quando mudarmos de classe.

Parece bom? Então, pegue um café e vamos em frente!

Antes de começar…

Você precisará de uma conta do Back4app com um aplicativo criado. Se ainda não tiver uma, siga este guia e você terá tudo em pouco tempo.

Alguns softwares que nos ajudarão…

Também usaremos algumas coisas para acelerar o processo.

É claro que você pode fazer tudo manualmente se preferir, mas não recomendo esse caminho, especialmente para iniciantes.
Ao adicionar pacotes manualmente, você também precisará fazer a manutenção manual dos pacotes, o que pode ser problemático depois de algum tempo.

Não é necessário usar os gerenciadores de pacotes que demonstrarei aqui, mas recomendo enfaticamente que você use pelo menos alguns gerenciadores de pacotes em vez de fazer o gerenciamento manual. Como eu disse antes, o gerenciamento manual pode se tornar peculiar com o tempo.

Usarei o Cocoapods para o gerenciamento de pacotes do iOS e o NPM para o gerenciamento do desktop, mas, novamente, fique à vontade para usar o que preferir.

As instruções para a instalação do Cocoapods podem ser encontradas neste site.

As instruções para a instalação do NPM podem ser encontradas neste site.

Criando nossas classes

Implementaremos três classes: Proprietário, Cão e Raça.

Nosso modelo implicará isso:

  • Um Owner pode ter vários Dogs
  • Um cão pode ter apenas uma raça

Nossas propriedades serão:

  • Owner (Proprietário)
    • nome (String)
    • Idade (Número)
    • endereço (String)
    • hasDogs (Relação com o cão, pois um proprietário pode ter vários cães)
  • Cão
    • nome (String)
    • aniversário (Data)
    • breed (Apontar para Breed, pois um cão tem apenas uma Breed)
  • Raça
    • Nome (String

Crie essas classes de forma gráfica ou programática e preencha com alguns dados. Minhas classes ficaram assim:

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

Raça

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

Cão

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

Proprietário

Traga o código da nuvem, querida!

Está na hora de usar o Cloud Code. Mas, desta vez, também quero me aprofundar no assunto.

Eu já lhe disse que o Cloud Code é compatível com os módulos NPM? Sim, é! E isso pode lhe poupar MUITO tempo!

Sabe aquela funcionalidade bacana que você queria ter e alguém já a fez? Bem, se for um módulo NPM, você pode usar!

Já existe um artigo sobre como usar módulos neste tutorial, portanto, para esta implementação, usarei o Moment para que possamos usar a data de aniversário de um cão e calcular sua idade em meses.

Portanto, meu arquivo package.json terá a seguinte aparência:

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

O * significa que estarei usando a versão mais recente.

Meu código de nuvem ficou como abaixo. Ele está bem comentado, portanto não entrarei em muitos detalhes aqui, mas você pode ver na primeira linha que estou usando o módulo NPM do Moment:

// Instanciando o módulo Moment NPM
const moment = require('moment')

Parse.Cloud.define("retrieveOwnersOrderedByAgeDescending", async req => {
    /*
        Essa função recupera todos os proprietários ordenados por idade decrescente.
        Se não houver proprietários, uma matriz vazia será recuperada.
    */
    const query = new Parse.Query("Owner"); 
    query.descending("age")
    const results = await query.find();

    return results;
});

Parse.Cloud.define("retrieveAllGermanShepherds", async req => {
    /*
        Essa função recupera os cães que têm a raça "pastor alemão"
        Todas as propriedades dos cães são recuperadas.
        Se não houver cães, uma matriz vazia será recuperada.
    */
    const breedQuery = new Parse.Query("Breed");
    breedQuery.equalTo("name", "German Shepherd") 
    const query = new Parse.Query("Dog"); 
    query.matchesQuery("breed", breedQuery);
    const results = await query.find();
    return results;
});

Parse.Cloud.define("retrieveDogIncludingBreedByName", async req => {
    /*
        Essa função recupera os detalhes específicos do cão, incluindo os detalhes de suas raças (nome), por nome do cão
        Todas as propriedades de Dogs e Breed são recuperadas.
        Se não houver cães, será recuperado o valor nulo.
    */
    const query = new Parse.Query("Dog"); 
    query.equalTo("name", req.params.name);
    query.include("breed")
    const results = await query.find();
    return results;
});

Parse.Cloud.define("retrieveDogsNamesWithAgesInMonths", async req => {
    /*
        Essa função recupera os nomes dos cães com a propriedade de aniversário convertida em meses usando o momento do módulo NPM
        Somente o nome dos cães é recuperado e a idade é calculada em meses.
        Se não houver cães, uma matriz vazia será recuperada.
    */
    let dogs = [];
    const query = new Parse.Query("Dog"); 
    const results = await query.find();

    for (let i = 0; i < results.length; i ++){
        // cálculo da idade em meses usando o módulo 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;
});

Meu arquivo schema.graphql que expõe meus quatro métodos ficou assim:

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")
}

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

Se você não entender a sintaxe, consulte este artigo na seção “Uma pequena etapa extra”.
Apenas observe o [DogAge!]! no getDogsAgesInMonths. Como estarei recuperando um objeto gerado pelo sistema, ele não terá um esquema para o GraphQL interpretar, portanto, preciso criar o tipo também para que nosso cliente possa interpretá-lo.

Testando… Testando…

É hora de fazer alguns testes!

Vamos acessar o console do Parse GraphQL e executar nossas consultas, verificando seus resultados:

Consulta para getAllOwnersAgeDescending:

query{
  getAllOwnersAgeDescending {
    nome
    idade
    address
  }
}

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

Consulta para getAllGermanShepherds:

consulta {
  getAllGermanShepherds {
    nome
    birthday
  }
}

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

Consulta para getThisDogWithBreed:

consulta{
  getThisDogWithBreed(name: "Fido"){
    nome
    aniversário
    raça{
      name
    }
  }
}

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

Consulta para getDogsAgesInMonths:

consulta{
  getDogsAgesInMonths
}

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

Tudo funcionando! Ótimo! Agora é hora de…

Um pouco de XCoding

Temos tudo o que precisamos do Back4app instalado e funcionando. É hora de consumir isso no XCode.

Este guia mostrou como colocar isso em funcionamento, mas desta vez fomos um pouco mais avançados: temos vários métodos, um dos quais espera variáveis, portanto, precisaremos fazer algumas alterações.

Se você ainda não leu esse artigo, sugiro enfaticamente que o faça, pois ele é muito detalhado.

Vamos começar criando o aplicativo e instalando o cliente Apollo, conforme descrito no artigo.

Depois disso, abra o arquivo Main.storyboard, clique no botão Objetos no canto superior direito e arraste e solte uma Table View no View Controller:

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

Altere o tamanho da Table View para que corresponda a toda a View do View Controller, arrastando os cantos:

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

E crie restrições para todos os quatro lados clicando no botão Add New Constraints (Adicionar novas restrições) no canto inferior direito da tela. Clique nos quatro lados (marcadores vermelhos) e clique no botão Add Constraints (Adicionar restrições) para atribuir essas novas restrições:

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

Agora, com a Table View selecionada (clique nela para selecionar), vamos adicionar uma Prototype Cell. Clique no Attributes Inspector (Inspetor de atributos) no canto superior direito e substitua o 0 por um 1 na caixa Prototype Cells (Células de protótipo):

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

Agora, clique na Prototype Cell recém-criada para selecioná-la e dê a ela um bom identificador: “cell” (célula).
Nós o usaremos mais tarde no código para identificar essa célula.

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

Agora, temos de vincular nossa fonte de dados e o delegado da exibição de tabela ao nosso controlador de exibição.
Mantendo pressionada a tecla Control do teclado, clique na Table View e arraste-a até o ícone amarelo na parte superior do View Controller, aquele que diz View Controller quando você passa o mouse.
Ao liberar o link Table View, uma pequena janela pop-up será exibida. Escolha DataSource e Delegate na janela pop-up:

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

Agora, isso cobrirá o básico, mas ainda precisaremos invocar uma atualização nessa exibição de tabela depois de recuperarmos os dados do Back4app. Para isso, precisamos criar um Outlet e conectá-lo à nossa interface de usuário.

A maneira mais fácil de fazer isso é clicar em Show Assistant Editor (Mostrar editor assistente) no canto superior direito da tela, para que você possa ver lado a lado a interface do usuário e o código.

Em seguida, clique com o botão Control na Table View e arraste-a para o código:

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

Ao soltar, você verá uma janela pop-up. Preencha o nome para ela e clique em Connect (Conectar):

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

Feito isso, você deve ter uma boa saída conectada (observe o pequeno círculo preenchido no número da linha), de modo que agora podemos invocar o código para essa Table View:

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

Com tudo isso feito, podemos ir para o nosso arquivo ViewController.swift porque é hora de…

Nosso arquivo GraphQL

Nosso guia do GraphQL mostrou como criar o arquivo GraphQL. Para este projeto, o meu ficou assim:

consulta findAllOwners{
    getAllOwnersAgeDescending{
        nome
        idade
        address
    }
}

consulta findAllGermanShepherds{
    getAllGermanShepherds {
        name
    }
}

consulta findThisDog ($name: String!){
    getThisDogWithBreed(name: $name){
        nome
        aniversário
        raça{
            name
        }
    }
}

consulta agesInMonths{
    getDogsAgesInMonths
}

Alguns códigos Swift

Mais uma vez, já falamos sobre como configurar o Apollo em Swiftneste guia, mas o código de hoje tem algumas alterações.
Como vamos exibir os dados graficamente em nossa Table View, precisamos incluir dois métodos para que nossa Data Source e Delegate sejam executados. Esses métodos são

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

e

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

Não vou falar muito sobre isso, pois eles estão documentados no código. Lembre-se de que esses dois métodos são obrigatórios para que o Table View funcione.

Seu código completo deve ter a seguinte aparência:

//
// ViewController.swift
// GraphQLApollo
//
// Criado por Venom em 19/08/19.
// Direitos autorais © 2019 Venom. Todos os direitos reservados.
//

importar UIKit
importar Apollo

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    @IBOutlet weak var tableView: UITableView!
    
    var list = [] as [String] // Essa matriz conterá os valores a serem exibidos
    
    // Inicialização do cliente Apollo.
    // Mais sobre isso aqui: 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": "f1wwm6uWqQoxazys3QQrrtY4fKuQuxYvNYJmYQJP"
        ]
        
        let url = URL(string: "https://parseapi.back4app.com/graphql")!
        
        return ApolloClient(
            networkTransport: HTTPNetworkTransport(
                url: url,
                configuration: configuration
            )
        )
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Realizar qualquer configuração adicional após o carregamento da exibição.
        
        apollo.fetch(query: FindAllOwnersQuery()) { result in
            guard let data = try? result.get().data else { return }
            
            for name in data.getAllOwnersAgeDescending {
                //Apende cada nome à nossa matriz de lista
                self.list.append(name.name!)
            }
            
            //a linha abaixo forçará o recarregamento de nossa exibição de tabela depois que recuperarmos os dados
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // Essa função é chamada por nossa exibição de tabela por meio da fonte de dados e do delegado.
        // Ela retorna apenas o número de objetos a serem exibidos pelo Table View.
        // É obrigatório para que a Table View funcione.
        return list.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // Essa função é chamada por nossa exibição de tabela por meio da fonte de dados e do delegado.
        // Ela cria as células a serem exibidas com o identificador que definimos no Storyboard
        // É obrigatório para que o Table View funcione.
        let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
        cell.textLabel?.text = list[indexPath.row]
        return(cell)
    }
}

Vamos executá-lo!

É hora de executar nosso código! Pressione Command + R e, se tudo estiver certo, você verá nossos nomes na exibição de tabela:

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

Legal, não é?
Agora podemos até mesmo alterar nosso código para executar nossos outros métodos GraphQL e ver como ele funcionará!

Vamos alterar nossa consulta GraphQL para executar nosso método getAllGermanShepherds:

    override func viewDidLoad() {
        super.viewDidLoad()
        // Fazer qualquer configuração adicional após o carregamento da exibição.
        
        apollo.fetch(query: FindAllGermanShepherdsQuery()) { result in
            guard let data = try? result.get().data else { return }
            
            print(data.getAllGermanShepherds)
            
            for name in data.getAllGermanShepherds {
                //Apende cada nome à nossa matriz de lista
                self.list.append(name.name!)
            }
            
            //a linha abaixo forçará o recarregamento de nossa exibição de tabela depois que recuperarmos os dados
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    }

e o resultado…

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

E se quisermos passar uma variável para nossa consulta?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Fazer qualquer configuração adicional após o carregamento da exibição.
        
        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 {
                //Apende cada nome à nossa matriz de lista
                self.list.append(name.name! + " - " + (name.breed?.name!)!)
            }
            
            //a linha abaixo forçará o recarregamento de nossa exibição de tabela depois que recuperarmos os dados
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    }

E agora sabemos que Fido é um Pug:

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

E quanto ao método que retorna um novo tipo? Vamos testá-lo também:

   override func viewDidLoad() {
        super.viewDidLoad()
        // Realizar qualquer configuração adicional após o carregamento da exibição.
        
        apollo.fetch(query: AgesInMonthsQuery()) { result in
            guard let data = try? result.get().data else { return }

            print(data.getDogsAgesInMonths)

            for dog in data.getDogsAgesInMonths {
                //Apende cada nome à nossa matriz de lista
                self.list.append(dog.dogName + " - " + dog.dogAgeInMonths)
            }

            //A linha abaixo forçará o recarregamento de nossa exibição de tabela depois que recuperarmos os dados
            DispatchQueue.main.async { self.tableView.reloadData() }
        }
    } 

e o resultado:

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

Conclusão

O código de nuvem é legal. O GraphQL é muito legal. O Apollo traz um novo nível de grandiosidade aos seus aplicativos nativos do iOS. Com todos os três juntos, você tem uma grande capacidade de criar aplicativos muito complexos com o mínimo de esforço.

Agora que você é capaz de criar um fluxo totalmente funcional para um aplicativo, que é fácil de criar e manter ao longo do tempo, a questão realmente é: o que você vai criar em seguida?

Espero que você tenha gostado! Teremos mais novidades em breve! Fique ligado!

Qual software pode ser usado para utilizar funções de nuvem no aplicativo Swift?

Dependerá da sua escolha. Você deve usar um software que seja fácil de usar e operar. Usei os dois abaixo na prática.

– Cocoapods para gerenciamento de pacotes iOS
– NPM para gerenciamento de pacotes Desktop

Qual é a mágica do NPM?

O código em nuvem é compatível com os módulos NPM. Isso é ótimo. Vai te ajudar a economizar muito tempo. Também te dará uma vantagem sobre seus concorrentes. Portanto, a compatibilidade do NPM com os códigos em nuvem vai te dar um impulso.


Leave a reply

Your email address will not be published.