Integrando o Protheus com Android – Parte 1
Olá amigos,
Estarei iniciando uma série de posts sobre a integração do Protheus com o Android, e vou tentar após essa série efetuar o mesmo processo com o IOS, já que temos essa necessidade também no mercado.
Nossa aplicação será a lista de clientes cadastrados no Protheus(SA1), e clicando em algum deles no dispositivo, teremos alguns detalhes desse cliente.
Os artigos serão divididos em 3 partes, sendo:
- Criação do WebService, onde será a ponte entre o Android e o Sistema Protheus, recebendo as requisições, executando a regra de negócio e retornando os resultados.
- Interface gráfica no Android, que será a tela que o usuário poderá interagir, essa interface terá 2 Activits, a primeira para listar os Clientes e a segunda para exibir os detalhes. Voltando a primeira caso o usuário deseja
- Interface de comunicação do Android, onde, vamos criar nossa classe DAO, usando o KSOAP2, que fará as requisições ao WebService.
A partir desses conceitos poderemos avançar criando aplicações mais robustas….
Para nossa aplicação estou usando nesse momento as seguintes IDE’s:
- Totvs Developer Studio, não o DevStudio
- Android Studio 5.0,
E vou usar também a lib KSOAP2 3.3.0.
Outra coisa é nossa APK terá como SDK mínimo a versão 4.2(API 17) do Android, acredito que seja o ideal e evitamos com isso muitas adaptações durante o desenvolvimento…rsrsr
Vamos em frente!
WebService
O WebService será nossa interface de comunicação junto ao Protheus, e iremos criar nele, algumas funções que recebe a requisição com os parâmetros(Filtro de clientes), executa a regra de negócio interna(Busca na tabela SA1), monta um objeto de retorno(Objeto com Array) e retorna.
Para isso vamos compilar no RPO que criamos no artigo do Criação de WebService Protheus, ou, um outro que tenha configurado para estudos e testes. Vou descrever abaixo cada parte desse fonte:
#INCLUDE "PROTHEUS.CH" #INCLUDE "APWEBSRV.CH" #INCLUDE "TOPCONN.CH" //======================================================================== // Estruturas //======================================================================== // Cliente WSSTRUCT STCliente WSDATA cA1COD AS String OPTIONAL WSDATA cA1NOME As String OPTIONAL WSDATA cA1END As String OPTIONAL WSDATA cA1CEP As String OPTIONAL WSDATA cA1BAIRRO As String OPTIONAL WSDATA cA1EST As String OPTIONAL WSDATA cRet AS String OPTIONAL WSDATA cMessage As String OPTIONAL ENDWSSTRUCT // Lista de Clientes WSSTRUCT STClientes WSDATA aRegs As ARRAY OF STCliente OPTIONAL WSDATA cRet AS String OPTIONAL WSDATA cMessage As String OPTIONAL ENDWSSTRUCT //======================================================================== // WSSERVICE //======================================================================== WSSERVICE WSDLAndr DESCRIPTION "Serviço para estudo .::blog.leonardods.com.br" // Propriedades WSDATA _cClienteDe As String WSDATA _cClienteAte As String // Estruturas WSDATA _STCliente As STCliente WSDATA _STClientes As STClientes // Metodos WSMETHOD ClienteLST DESCRIPTION "Lista de clientes do Protheus" ENDWSSERVICE //======================================================================== // METODOS //======================================================================== // Listagem de clientes WSMETHOD ClienteLST WSRECEIVE _cClienteDe, _cClienteAte WSSEND _STClientes WSSERVICE WSDLAndr Local cAlias Local cCodIni := ::_cClienteDe Local cCodFin := ::_cClienteAte // Cria o objeto de retorno ::_STClientes := WSClassNew("STClientes") ::_STClientes:cRet := "[T]" ::_STClientes:cMessage := "" ::_STClientes:aRegs := {} IF _cClienteDe > _cClienteAte cCodIni := ::_cClienteAte cCodFin := ::_cClienteDe EndIF DbSelectArea("SA1") cAlias := SA1->(GetArea()) SA1->(DbSetOrder(1)) SA1->(DbSeek(xFilial("SA1")+cCodIni)) While SA1->(!EOF()) .And. SA1->A1_COD <= cCodFin oSTCliente := WSClassNew("STCliente") oSTCliente:cA1COD := SA1->A1_COD oSTCliente:cA1NOME := SA1->A1_NOME oSTCliente:cA1END := SA1->A1_END oSTCliente:cA1CEP := SA1->A1_CEP oSTCliente:cA1BAIRRO := SA1->A1_BAIRRO oSTCliente:cA1EST := SA1->A1_EST Aadd(::_STClientes:aRegs, oSTCliente) SA1->(DbSkip()) End IF Len(::_STClientes:aRegs) == 0 ::_STClientes:cRet := "[F]" ::_STClientes:cMessage := "Essa consulta nao teve resultados!" EndIF DbCloseArea("SA1") RETURN .T.
#INCLUDE "PROTHEUS.CH" #INCLUDE "APWEBSRV.CH" #INCLUDE "TOPCONN.CH"
Linhas 1 a 3 – Temos as includes, que são bibliotecas para permitir certas funcionalidades ao fonte, como comunicação com o banco, etc.
WSSTRUCT STCliente WSDATA cA1COD AS String OPTIONAL WSDATA cA1NOME As String OPTIONAL WSDATA cA1END As String OPTIONAL WSDATA cA1CEP As String OPTIONAL WSDATA cA1BAIRRO As String OPTIONAL WSDATA cA1EST As String OPTIONAL WSDATA cRet AS String OPTIONAL WSDATA cMessage As String OPTIONAL ENDWSSTRUCT
Linhas 10 a 20 – Criamos uma classe para facilitar o tratamento de cada registro a ser retornado, nesse caso essa representa um objeto Cliente, contendo o CODIGO, NOME, ENDEREÇO, CEP, BAIRRO e ESTADO, acrescento também mais duas propriedades sempre, CRET que representa o sucesso ou não no retorno desse objeto, e CMESSAGE que representa o que quero informar ao aplicativo que requisitou, podendo ser alguma informação útil.
Como podem ver cada propriedade possui em seguida ao trecho “AS String”, isso informa o tipo do conteúdo, podendo ser STRING, INTEGER, ARRAY, etc.
WSSTRUCT STClientes WSDATA aRegs As ARRAY OF STCliente OPTIONAL WSDATA cRet AS String OPTIONAL WSDATA cMessage As String OPTIONAL ENDWSSTRUCT
Linhas 23 a 28 – Representa a classe de lista, ou seja, ele irá encapsular uma lista de objetos Clientes, que será o retorno do nosso WebService. Como podem ver a propriedade AREGS é um array de Clientes.
WSSERVICE WSDLAndr DESCRIPTION "Serviço para estudo .::blog.leonardods.com.br" // Propriedades WSDATA _cClienteDe As String WSDATA _cClienteAte As String // Estruturas WSDATA _STCliente As STCliente WSDATA _STClientes As STClientes // Metodos WSMETHOD ClienteLST DESCRIPTION "Lista de clientes do Protheus" ENDWSSERVICE
Linhas 33 a 46 – Declaração de nosso WebService, onde, ele contém as propriedades que vamos trabalhar e os métodos, que são os eventos invocados pela nossa aplicação Mobile. Nesse caso nosso WebService possui 4 propriedades(_cClienteDe, _cClienteAte, _STCliente, _STClientes) e 1 método(ClienteLST).
Só uma observação é que nesse momento estamos renomeando nossas propriedades, acrescentando um “_” antes para facilitar o entendimento durante o projeto, é ideal trabalhar assim, principalmente em projetos grandes, onde, de cara sabemos que essas variáveis fazem parte de todo o projeto. Outra coisa que podem observar é que as propriedades _cClienteDe e _cClienteAte, não existem até então, mas, elas serão usadas como parâmetros de entrada em nossos métodos, e portanto devemos informar isso ao WebService.
No caso das classes, podem observar que elas possuem os nomes meio dificeis STCliente, STClientes.. por que não usei apenas CLIENTE e CLIENTES, acrescentei o prefixo “ST”, que significa “Struct”, isso é uma forma de trabalhar minha. Pois as classes são semelhantes as Users Functions, ou seja, quando compilados, passam a existir em todo o RPO, e esses nomes não posso usar mais em outras situações, e como o nome CLIENTE é comum, poderia atrapalhar projetos futuros ou até existentes. Pense nisso em seus projetos.
WSMETHOD ClienteLST WSRECEIVE _cClienteDe, _cClienteAte WSSEND _STClientes WSSERVICE WSDLAndr
Na linha 53 – Estamos iniciando nosso método ClienteLST, ou seja, Listar Clientes, esse método irá receber 2 parâmetros de entrada _cClienteDe e _cClienteAte e isso não é opcional, devem ser obrigatoriamente informados, quando não existirem devemos informar como vazias, usando aspas duplas por exemplo.
Esse método irá retornar o objeto _STClientes, que como vimos acima representa uma lista de clientes.
Local cAlias
Linha 55 – Declaração da variavel cAlias, que será a grosso modo, nosso arquivo temporário contendo os registros da SA1.
Local cCodIni := ::_cClienteDe
Linha 56 – Declaração da variavel cCodIni que irá representar o filtro inicial, no caso o “DE”
Local cCodFin := ::_cClienteAte
Linha 57 – Declaração da variavel cCodFin que irá representar o filtro final no caso o “ATE”
::_STClientes := WSClassNew("STClientes")
Linha 60 – Criamos a classe de retorno, exigida pelo metodo
::_STClientes:cRet := "[T]"
Linha 61 – Definimos a propriedade que representa o status do retorno como “[T]”, ou seja, inicialmente nosso retorno será de sucesso
::_STClientes:cMessage := ""
Linha 62 – Apenas declaramos a propriedade de mensagem
::_STClientes:aRegs := {}
Linha 63 – Definimos nossa propriedade de array que irá conter todos os objetos de clientes.
IF _cClienteDe > _cClienteAte cCodIni := ::_cClienteAte cCodFin := ::_cClienteDe EndIF
Linha 65 a 68 – Nesse ponto estamos tratando a situação do usuário ter enviado pelo WebService, os filtros invertidos. Explicando melhor: Quando abrimos a tabela no Protheus, deixamos ela numa ordem que fique fácil a busca pelos registros, por exemplo, quando abrimos a tabela SA1, e vamos buscar pelo codigo de cada clientes, é ideal, que essa tabela esteja ordenado pelo codigo, assim podemos ir pegando os registros a medida que vamos lendo… saltando os registros que não interessar. Do contrario, teriamos que ir para o final da tabela, para pegar um registro, depois, voltar ao inicio para pegar o segundo registro, em seguida ir ao final de novo… ou seja, so de explicar já cansa, imagina executando… rsrsrsr.
Quando executamos um filtro, no nosso caso do código 20 ao 35, o ideal é que achemos o codigo 20 e irmos descendo a tabela ate o codigo 35. Mas, o usuario pode filtrar da seguinte forma, do 35 ao 20. E ai??!! O sistema iria localizar o codigo 35, já que ele informou isso primeiro, e não iria achar o restante, pois, a tabela não é de baixo pra cima. Entende?
Nesse caso estamos fazendo o seguinte, caso o usuário informe ao contrário, 35 a 20, nós invertemos, o cCodIni recebe o 20 e o cCodFin recebe o 35… Ufa!! Espero não ter sido chato demais explicando tanto… as vezes é melhor falar…rsrsrs
DbSelectArea("SA1")
Linha 70 – Abrimos a tabela SA1
cAlias := SA1->(GetArea())
Linha 71 – Esse trecho as vezes é necessario quando trabalhamos com muita repetiçao de dados, para entregar a tabela da forma que recebemos, nesse momento estamos pegando da forma que está e guardando essa informação.
SA1->(DbSetOrder(1))
Linha 73 – Estamos ordenando a tabela pelo código do cliente, ou seja, do menor para o maior.
SA1->(DbSeek(xFilial("SA1")+cCodIni))
Linha 74 – Nesse momento estamos estamos localizando o primeiro registro, ou seja, o usuário irá dizer a partir de qual registro ele precisa, então localizamos esse registro.
While SA1->(!EOF()) .And. SA1->A1_COD <= cCodFin
Linha 76 – Nesse momento, estamos posicionado no primeiro registro solicitado caso encontrado, ou não, vai depender se tivermos encontrado ou não estamos no final da tabela, que vai me dizer é o SA1->(!EOF())
oSTCliente := WSClassNew("STCliente")
Linha 77 – Caso tenhamos encontrado o primeiro registro, criamos um objeto Cliente.
oSTCliente:cA1COD := SA1->A1_COD oSTCliente:cA1NOME := SA1->A1_NOME oSTCliente:cA1END := SA1->A1_END oSTCliente:cA1CEP := SA1->A1_CEP oSTCliente:cA1BAIRRO := SA1->A1_BAIRRO oSTCliente:cA1EST := SA1->A1_EST
Linhas 78 a 83 – Nessas linhas nós pegamos os dados de cada coluna da tabela do registro posicionado e gravamos no objeto criado anteriormente.
Aadd(::_STClientes:aRegs, oSTCliente)
Linha 85 – Nesse momento nós pegamos o objeto criado na linha 77 e inserimos na nossa lista de objetos.
SA1->(DbSkip())
Linha 86 – Nesse momento nós saltamos a linha, ou seja, a ideia é percorrer a tabela, então é nesse momento que mudamos para a linha seguinte, e voltamos a lógica para a linha 76, que vai fazer a analise de novo, se estamos no final da tabela SA1->(!EOF()), ou se no novo registro que estamos ainda atende a regra, se é menor que o filtro Até.
Repetimos esse processo tantas vezes quantas forem necessárias, enquanto atender ao processo…
IF Len(::_STClientes:aRegs) == 0 ::_STClientes:cRet := "[F]" ::_STClientes:cMessage := "Essa consulta nao teve resultados!" EndIF
Linhas 89 a 92 – Nesse momento nós avaliamos a lista que montamos, caso nessa lista esteja vazia, nós marcamos a propriedade como INSUCESSO “[F]”, e informamos isso na mensagem de retorno, na linha 91.
DbCloseArea("SA1")
Linha 94 – Nós fechamos a tabela, e retornamos a lista de objetos, estando vazia ou não.
RETURN .T.
Na linha 96 – É interessante dizer que devemos sempre retornar .T., pois, esse retorno é para o WebService, caso retornemos .F., ou ocorrer isso em algum momento do código, o Protheus vai entender como erro, e vai gerar uma exceção.
Pronto pessoal, esse é o código todo comentado. Espero não ter sido detalhista demais a ponto de ser chato, nem rápido demais a ponto de ficar confuso. É interessante lembrar que para entender isso que foi explicado, é necessário no minimo um conhecimento de lógica de programação. Vou pensar em algo futuramente sobre isso… quem sabe não dou uma aula online…rsrsr
Precisamos compilar esse fonte em nosso RPO, portanto, copie ou digite o código em um novo fonte no seu TotvDeveloperStudio, salve como WSDLAndr.prwe compile em nosso ambiente WEBService.
Após ter compilado, já podemos ver o serviço ativo no WebService, abra seu browser e entre na URL, no meu caso é http://127.0.0.1:8090. Deverá estar vendo o serviço como abaixo.
Se clicarmos nesse serviço, WSDLANDR, podemos ver o que o serviço oferece, que é justamente o método que criamos, CLIENTELST.
Precisamos copiar a URL gerada na Descrição do Serviço, copie para usarmos no passo seguinte que é o teste do serviço. Clique na descrição do serviço para copiar a URL.
URL a ser copiada…
Podemos agora testar esse serviço criado, para isso vamos usar uma ferramente muito bacana e free, chamada SoapUI. Baixe a ferramenta e instale. É bem simples, existe um post sobre ela aqui no blog.
Após a instalação e execução, veremos a tela como a seguinte. Nessa tela clique em File >> New Soap Project. É nesse ponto que devemos “colar” a URL no campo INITIAL WSDL, e clique em OK.
Após a confirmação, já veremos na primeira tela da esquerda nosso metodo criado CLIENTELST. Clicando na mesma, ele exibe nosso objeto para envio na parte do centro.
Os parametros estão com as interrogações e devemos substituir pelos valores que devemos testar. Lembrando que caso não utilizemos aquele valor, devemos remover a interrogação, deixando o campo em branco.
Vamos fazer um teste inserindo uma margem entre o usuario 000001 e o 000100. E clicamos na seta verde.
Como podemos ver na primeira tela a direita, teremos o retorno dos dados, claro que serao diferentes do seus, mas, a estrutura se manterá.
Agora vamos fazer um teste com valores que não tem. No meu caso vou usar o numero 1.
O retorno será a mensagem que programamos para termos ideia do que houve no sistema, e não apenas retornar vazio.
Estou colocando o fonte em anexo, para facilitar, caso tenha ficado ruim acompanhar as linhas pelo blog. Basta clicar na imagem abaixo.
Pronto pessoal, com isso finalizamos a primeira parte de três. Nosso próximo passo será a criação da aplicação no Android, sem comunicação ainda com o WebService, onde esse será nossa terceira e última parte.
Enquanto preparo, baixem a nossa IDE, que usaremos o Android Studio, e deem uma passeada pelo blog, vendo os demais artigos e assine nossa newsletter, para acompanhar as novidades!
Fica ai a dica…
10 Comments
Leave a Reply
E a continuidade deste tópico? Vai fazer? Já está pronta…. ou tem previsão?
A ideia é muito boa!
Abraço,
Murilo.
Olá Murilo,
Vai sim meu amigo, já estou finalizando a segunda parte e acredito que logo logo estarei publicando…Vlw!!
Volte sempre.
Bom dia.
Muito bom esse post, parabéns… Você sabe me informar quando será publicado a Parte 02 do post?
Obrigado.
Abraços.
Olá amigo, estou um tanto ocupado desde o último post, mas, em breve estarei retomando.
Olá Leonardo, me interessei muito nessa sequência de posts.
Estou no aguardo das sequências seguintes.
Se puderes avisar ou enviar o mateiral ficaria grato.
Muito bom o tutorial. Quando vai sair a segunda parte?
Tentei por conta própria realizar a consumo, mas aparece um erro que não sei o que é.
Olá, tem uma previsão da postagem das outras partes?
Muito bom!! ansioso para o próximo !
valeu!
Cadê a segunda parte??? hehehe