APIs e Microsserviços

17 abr, 2015

Obtendo atualizações dos reservatórios de água da SABESP

Publicidade

reservatorio-jaguari-sistema-cantareira

Introdução

Um problema bastante atual (pelo menos em São Paulo) é a falta d’água. Esse é um problema crônico, porém teve muito impacto no final do ano passado e início deste ano.

Uma API foi criada para que possamos acompanhar a situação de cada um dos mananciais da SABESP.

Detalhes da API

Essa dica foi enviada pelo meu amigo William Bruno.

O endereço da API é https://sabesp-api.herokuapp.com/, e nela temos algumas informações sobre os mananciais da SABESP.

Além do nome do manancial, obtemos os dados de volume armazenado, pluviometria do dia, pluviometria acumulada do mês e média histórica do mês.

A API retorna o seguinte JSON:

[
    {
        "name": "Cantareira",
        "data": [
            {
                "key": "volume armazenado",
                "value": "18,9 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "16,4 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "206,3 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "178,0 mm"
            }
        ]
    },
    {
        "name": "Alto Tietê",
        "data": [
            {
                "key": "volume armazenado",
                "value": "22,8 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "7,3 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "186,4 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "172,4 mm"
            }
        ]
    },
    {
        "name": "Guarapiranga",
        "data": [
            {
                "key": "volume armazenado",
                "value": "85,0 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "11,2 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "200,6 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "153,2 mm"
            }
        ]
    },
    {
        "name": "Alto Cotia",
        "data": [
            {
                "key": "volume armazenado",
                "value": "64,6 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "1,0 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "150,4 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "149,1 mm"
            }
        ]
    },
    {
        "name": "Rio Grande",
        "data": [
            {
                "key": "volume armazenado",
                "value": "97,3 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "2,6 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "199,4 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "186,3 mm"
            }
        ]
    },
    {
        "name": "Rio Claro",
        "data": [
            {
                "key": "volume armazenado",
                "value": "43,9 %"
            },
            {
                "key": "pluviometria do dia",
                "value": "4,0 mm"
            },
            {
                "key": "pluviometria acumulada no mês",
                "value": "235,2 mm"
            },
            {
                "key": "média histórica do mês",
                "value": "245,9 mm"
            }
        ]
    }
]

 

Os dados obtidos na requisição acima são do dia em que escrevi este texto – 31 de março; caso queiramos obter dados de dias anteriores, precisamos passar a data que desejamos na URL da API, como por exemplo https://sabesp-api.herokuapp.com/2015-01-01.

Iniciando o Projeto iOS

Eu sempre gosto de testar as APIs com projetos onde posso imaginar uma aplicação real.

Para os testes de hoje, escolhi o iOS como destino da aplicação.

Não é meu objetivo ensinar a programar em iOS, porém você pode baixar o projeto neste link e, se tiver alguma dúvida, eu terei o prazer em ajudar.

Abaixo estão as telas com as configurações que escolhi na criação do projeto.

Primeiro, escolha a template para seu projeto – nesse caso, Single View Application, e clique em Next.

ios-1

Na próxima tela, preencha as informações solicitadas para Product Name, Organization Name, Organization Identifier, Language e Devices.

ios-2

Lembre-se de deixar desmarcada a opção Use Core Data e de selecionar a linguagem Swift.

Codificando

Apesar de não ser a minha intenção neste artigo ensinar o desenvolvimento em Swift, vou tentar explicar os códigos utilizados.

O projeto inteiro está disponível no meu GitHub, fique à vontade para duplicar, melhorar e contribuir.

Arquivo Manancial.swift

import Foundation

struct Manancial {
    var name:String
    var volume:String
    var rainDay:String
    var rainMonth:String
    var rainAvg:String
    
    init(manancialDic:NSDictionary) {
        NSLog("%@", manancialDic);
        
        name = manancialDic["name"] as String
        
        var data = manancialDic["data"] as NSArray
        volume = data[0]["value"] as String
        rainDay = data[1]["value"] as String
        rainMonth = data[2]["value"] as String
        rainAvg = data[3]["value"] as String
    }
    
}

Neste arquivo, temos a estrutura que será utilizada para facilitar a manipulação dos dados obtidos da API.

No construtor dessa classe, recebemos um NSDictionary que tem o JSON da API.

Para cada manancial, temos um array chamado data que possui 4 elementos.

Considerando que os elementos sempre serão apresentados na mesma ordem, podemos utilizar os índices fixos para obter os dados de volume, rainDay, rainMonth e rainAvg.

Arquivo ViewController.swift

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var volumeLabel: UILabel!
    @IBOutlet weak var dayLabel: UILabel!
    @IBOutlet weak var monthLabel: UILabel!
    @IBOutlet weak var avgLabel: UILabel!
    @IBOutlet weak var page:UIPageControl!
    @IBOutlet weak var level:UIView!
    
    var mananciais: NSArray!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        nameLabel.text = "Carregando..."
        volumeLabel.hidden = true
        dayLabel.hidden = true
        monthLabel.hidden = true
        avgLabel.hidden = true
        level.hidden = true

        loadData();
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func loadData() {
        let baseURL = NSURL(string: "https://sabesp-api.herokuapp.com/")
        let sharedSession = NSURLSession.sharedSession()
        let downloadTask: NSURLSessionDownloadTask = sharedSession.downloadTaskWithURL(baseURL!, completionHandler: { (location: NSURL!, response:NSURLResponse!, error: NSError!) -> Void in
            if (error == nil) {
                let dataObject = NSData(contentsOfURL: location)
                self.mananciais = NSJSONSerialization.JSONObjectWithData(dataObject!, options: nil, error: nil) as NSArray
                
                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    self.page.numberOfPages = self.mananciais.count
                    self.showPage(0)
                })
            } else {
                NSLog("%@", error)
                let networkIssueController = UIAlertController(title: "Error", message: "Unable to load data. Connectivity error!", preferredStyle: .Alert)
                let okButton = UIAlertAction(title: "OK", style: .Default, handler: nil)
                networkIssueController.addAction(okButton)
                self.presentViewController(networkIssueController, animated: true, completion: nil)
                
                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    self.nameLabel.text = "Erro"
                    self.volumeLabel.hidden = true
                    self.dayLabel.hidden = true
                    self.monthLabel.hidden = true
                    self.avgLabel.hidden = true
                    self.level.hidden = true
                })
            }
        })
        
        downloadTask.resume()
    }
    
    func showPage(index:Int) {
        let manancial = Manancial(manancialDic: self.mananciais[index] as NSDictionary)
        
        self.nameLabel.text = "\(manancial.name)"
        self.volumeLabel.text = "\(manancial.volume)"
        self.dayLabel.text = "\(manancial.rainDay)"
        self.monthLabel.text = "\(manancial.rainMonth)"
        self.avgLabel.text = "\(manancial.rainAvg)"
        
        self.volumeLabel.hidden = false
        self.dayLabel.hidden = false
        self.monthLabel.hidden = false
        self.avgLabel.hidden = false
        self.level.hidden = false
        
        var volume:NSDecimalNumber = NSDecimalNumber(string: manancial.volume.stringByReplacingOccurrencesOfString(" %", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil).stringByReplacingOccurrencesOfString(",", withString: ".", options: NSStringCompareOptions.LiteralSearch, range: nil))
        
        var pixel = CGFloat(volume) / 100 * self.view.frame.size.height

        let y:CGFloat = self.view.frame.size.height - pixel;
        
        self.level.frame.origin.y = y
        self.level.frame.size.height = pixel
    }

    @IBAction func pageChanged() {
        showPage(page.currentPage);
    }

}

Na função viewDidLoad, é chamada uma função loadData que é responsável por solicitar os dados para a API.

Após obter o JSON com o resultado da API, fazemos a serialização dele para um objeto do tipo NSArray.

Esse objeto será nossa fonte de dados para executar a paginação e apresentar os dados da tela.

Cada elemento desse array é um NSDictionary que será enviado para a função showPage quando necessário.

O próximo passo é obter a primeira página e, para isso, passamos o primeiro elemento do array para a função showPage. Essa função tem o código para mostrar os dados do manancial na tela.

O resultado final da app podemos ver na imagem abaixo:

ios-3

Conclusão

Conhecemos mais uma API interessante que podemos explorar em muitos formatos.

Decidi usar o iOS com Swift para mostrar que, mesmo com uma API simples, podemos fazer um uso criativo e entregar uma experiência interessante para o usuário.

Ainda que não seja meu objetivo ensinar a programar em Swift, esse pode ser um ponto de partida, pois é simples e não tem muita dificuldade para começar a ver algum resultado.

Caso tenha alguma dúvida, crítica ou sugestão, envie sua mensagem que eu responderei com prazer.