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:

  1. 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.
  2. 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
  3. 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:

  1. Totvs Developer Studio, não o DevStudio
  2. 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.

PrtScr capture

Se clicarmos nesse serviço, WSDLANDR, podemos ver o que o serviço oferece, que é justamente o método que criamos, CLIENTELST.

PrtScr capture_4

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.

PrtScr capture_5

URL a ser copiada…

PrtScr capture_6

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.

PrtScr capture_2

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.

PrtScr capture_3

 

Vamos fazer um teste inserindo uma margem entre o usuario 000001  e o 000100. E clicamos na seta verde.

PrtScr capture_4

 

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.

PrtScr capture_5

 

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.

PrtScr capture

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

  1. 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.

Leave a Reply to admin

Clique aqui para cancelar a resposta.

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="">

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.