API de Endereços Para Pré-Cadastro em APPs Android - Parte 2
Blog /Android /API de Endereços Para Pré-Cadastro em APPs Android - Parte 2
(3539) (5)
Neste artigo damos continuidade a série de dois conteúdos sobre API de endereços no Android, mais precisamente, a API ViaCEP.
É necessário que você já tenha consumido o primeiro artigo da série, artigo também com vídeo. Caso ainda não, acesse ele pelo link a seguir:
API de Endereços Para Pré-Cadastro em APPs Android - Parte 1.
A parte um da série tem a discussão sobre a importância de trabalhar bem os formulários no Android, além de algumas outras explicações necessárias para a necessidade de uma API de endereços para pré-cadastro de dados.
Aqui vamos implementar o trecho, do aplicativo de marketplace, que permite ao usuário buscar o CEP ao invés de somente buscar o endereço utilizando o CEP.
Antes de prosseguir, não deixe de se inscrever 📫 na lista de e-mails do Blog para receber, em primeira mão, todos os novos conteúdos de desenvolvimento de aplicativos Android do Blog.
Abaixo os tópicos que serão abordados:
API de endereços com busca por CEPs;
Projeto de exemplo - Android:
Acrescentando classes de negócio no domínio do problema;
Construindo a ZipCodeSearchActivity, Activity de busca por CEP;
Atualizando o domínio do problema;
Vídeo com implementação passo a passo do código - Parte dois;
API de endereços com busca por CEPs
A API de endereços que estamos utilizando trabalha também com busca de CEP. O usuário informar a rua, cidade e estado e então uma lista de endereços, incluindo o CEP de cada um, é retornada.
No caso da API do ViaCEP, é retornado no máximo 100 endereços. Veja o exemplo a seguir:
Simples busca por CEPs na API do ViaCEP
"cep": "29166-767",
"logradouro": "Rua dos Bem-te-vis",
"bairro": "Morada de Laranjeiras",
"localidade": "Serra",
"ibge": "3205002",
"cep": "29168-590",
"bairro": "Porto Canoa",
"cep": "29173-517",
"bairro": "Costa Bela",
Com zero ou mais endereços, sempre é retornado um array JSON. O que a API não informa é sobre como é feita a ordenação dos endereços. Algo como: o mais relevante primeiro, por exemplo. Esta "não informação" fica como o ponto negativo da documentação dela.
De qualquer forma, apenas precisamos apresentar lista de endereços ao usuário e deixar com que ele escolha o correto.
Projeto de exemplo - Android
Nesta parte dois o conteúdo do artigo tende a ser bem mais enxuto que a parte um. Isso, pois apenas estaremos finalizando a lógica de busca de endereço com a API do ViaCEP, a parte "verbosa" já foi apresentada no artigo um da série.
Ao final deste artigo vamos ter uma nova Activity com o layout similar ao apresentado a abaixo:
Assim podemos prosseguir com as adições e atualizações ao projeto.
Acrescentando classes de negócio no domínio do problema
Vamos precisar adicionar algumas novas classes ao domínio do problema. Classes de negócio referentes as seguintes necessidades:
Adaptador e gerenciador de itens de uma lista;
Busca remota da lista de CEPs de acordo com os dados de endereço fornecidos em formulário.
Para o primeiro item, vale informar que estaremos trabalhando com um ListView, pois a implementação é mais simples que a de um RecyclerView, além de atender bem aos nossos requisitos neste projeto de exemplo.
Vamos recapitular o resultado de uma consulta por CEPs a API do ViaCEP:
Em nosso domínio do problema o usuário terá de entrar com os dados de: rua, cidade e estado. Sabendo disso e tendo em mente que sempre é retornada um array JSON, mesmo que vazio. Podemos assim definir um layout personalizado para os itens da lista, itens que serão, na verdade, os endereços retornados.
Note que não precisamos apresentar os dados de cidade e estado no layout de item. Isso, pois o usuário já terá de fornece-los.
Quanto a rua, vamos sim manter no layout dos itens, pois pode ser que o nome da rua não tenha sido fornecido corretamente e a API tenha retornado um resultado com os nomes possíveis ao nome informado.
Segundo alguns testes realizados, o campo complemento do JSON retornado nunca teve algum valor. Logo, podemos optar pelos seguintes dados no layout de item: cep, rua e bairro.
Assim temos um layout com a seguinte estrutura:
O XML deste logo abaixo, address_item.xml:
android:background="#00684c"
android:paddingStart="5dp">
android:id="@+id/tv_zip_code"
android:id="@+id/tv_street"
android:layout_below="@+id/tv_zip_code"
android:id="@+id/tv_neighbor"
android:layout_below="@+id/tv_street"
Assim podemos partir para a construção do adapter que será utilizado pelo ListView do layout da Activity ZipCodeSearchActivity.
Vamos nomear o adapter de AddressAdapter. Segue apresentação das partes deste adapter.
Primeiro o código comum a adapters de lista, inicialização de atributos e métodos obrigatórios:
public class AddressAdapter extends BaseAdapter {
public AddressAdapter(Context context, List<Address> addresses){
return addresses.get( i );
Simples, certo? Caso ainda não conheça o BaseAdapter, logo depois de terminar este artigo, parte dois, acesse o artigo, também com vídeo, que tem aqui no Blog sobre esta classe: Utilizando BaseAdapter Para Personalização Completa da ListView.
E assim o código restante de AddressAdapter, mais precisamente, o código de getView() e a classe interna que nos permite a aplicação do padrão ViewHolder.
if( view == null ){
view = inflater.inflate(R.layout.address_item, null);
holder.setViews( view );
holder.setData( addresses.get( i ) );
TextView tvZipCode;
TextView tvNeighbor;
void setViews( View view ){
tvZipCode = (TextView) view.findViewById(R.id.tv_zip_code);
tvStreet = (TextView) view.findViewById(R.id.tv_street);
tvNeighbor = (TextView) view.findViewById(R.id.tv_neighbor);
void setData( Address address ){
tvZipCode.setText( "CEP: "+address.getCep() );
tvZipCode.setTag( address.getCep() );
tvStreet.setText( "Rua: "+address.getLogradouro() );
tvNeighbor.setText( "Bairro: "+address.getBairro() );
Veja em ViewHolder que, para amenizar a codificação em getView(), adicionamos alguns métodos de preenchimento a ela.
Assim terminamos com a classe que responde a primeira necessidade apontada logo no início desta seção.
Nossa segunda tarefa é construir a classe que permitirá a conexão com o servidor da API ViaCEP e também o processamento da resposta desta API. Isso para que seja possível a colocação dos itens no ListView da Activity ZipCodeSearchActivity.
Assim, segue código de ZipCodeRequest (o nome autocomentado, equivalente ao que é requisitado):
public class ZipCodeRequest extends AsyncTask<Void, Void, Void> {
private WeakReference<ZipCodeSearchActivity> activity;
public ZipCodeRequest( ZipCodeSearchActivity activity ){
activity.get().lockFields( true );
String jsonString = JsonRequest.request( activity.get().getUriRequest() );
activity.get().getAddresses().clear(); /* PARA REAPROVEITAR O OBJETO DE LISTA */
for( int i = 0; i < jsonArray.length(); i++ ){
activity.get().getAddresses().add( gson.fromJson(jsonObject.toString(), Address.class) );
protected void onPostExecute(Void data) {
activity.get().lockFields( false );
activity.get().updateListView();
Primeiro, o código é muito similar ao da classe AddressRequest, apresentada na parte um da série. Segundo, o WeakReference continua sendo utilizado, isso, pois como AddressRequest, ZipCodeRequest também tem um ciclo de vida diferente do da Activity que acionará ele, a ZipCodeSearchActivity.
E terceiro. Bom, para o terceiro vamos recapitular o código Gson sendo utilizado:
Note que como desta vez temos como retorno um array JSON, precisamos trabalhar primeiro o array como um simples array JSON para então utilizarmos verdadeiramente o Gson para realizar o parser.
A library Gson tem uma maneira de trabalhar também com lista de dados, assim o uso do JSONArray seria dispensável. Porém, em nosso caso, o código é simples o suficiente para não optarmos pela versão de lista desta library.
Seguindo o conteúdo, mais precisamente o conteúdo da Activity ZipCodeSearchActivity, você entenderá a importância da invocação activity.get().getAddresses().clear().
Já lhe adianto: este clear() vai nos permitir aliviar o peso na memória do device evitando que tenhamos de criar um novo objeto de lista. O clear() faz parte da lógica que vai nos ajudar a ter este ganho em reutilização de recursos.
Com isso terminamos com as novas classes de domínio do problema. Vamos prosseguir, pois ainda falta a nova Activity, ZipCodeSearchActivity, e a atualização de algumas classes criadas na parte um da série.
Só um minuto. ZipCodeSearchActivity não faz parte do domínio do problema, digo, classes de negócio?
Sim, faz parte, alias é a classe que mais tem lógica de negócio, mas como se trata de uma Activity, um componente Android, que tem muito mais responsabilidades do que somente lógica de negócio do sistema, preferi coloca-la em uma seção separada.
Construindo a ZipCodeSearchActivity, Activity de busca por CEP
Nosso primeiro passo aqui é definir o layout da Activity. Esta terá de permitir o usuário entrar com os dados: rua, cidade e estado. E, obviamente, permitir o clique em um botão para invocar a busca.
Também será necessária a estrutura para apresentação dos itens, isso no mesmo layout que terá o formulário de busca.
Vamos reaproveitar parte do layout da SignUpActivity, mais precisamente, parte do formulário. Assim teremos um layout com a seguinte arquitetura:
Uma correção na figura anterior: no lugar de Button, na verdade é TextView. Mas você pode utilizar, sem problema algum, um Button.
A figura acima facilita o entendimento do XML que será apresentado a seguir, o XML do arquivo de layout activity_zip_code_search.xml:
tools:context="br.com.thiengo.marketplaceapp.ZipCodeSearchActivity">
android:id="@+id/sub_title_1"
android:background="#005740"
android:text="Busca CEP"
android:textColor="#f9f9f9"
android:id="@+id/ll_fieldset_1"
android:layout_below="@+id/sub_title_1"
android:id="@+id/til_city"
android:hint="*Cidade"
android:id="@+id/sp_state"
android:id="@+id/ll_fieldset_2"
android:layout_below="@+id/ll_fieldset_1"
android:id="@+id/til_street"
android:hint="*Rua"
android:onClick="searchAddress"
android:id="@+id/sub_title_2"
android:layout_below="@+id/ll_fieldset_2"
android:text="Resultado (endereços)"
android:id="@+id/lv_address"
android:layout_below="@+id/sub_title_2"
Com a estrutura de layout anterior, teremos como resultado uma visualização como a figura a seguir:
Assim podemos prosseguir com o código de ZipCodeSearchActivity.
Como com outras classes grandes apresentadas nesta série, também vamos em partes.
Primeiro a inicialização dos atributos:
public class ZipCodeSearchActivity extends AppCompatActivity
private Spinner spStates;
private ListView lvAddress;
setContentView(R.layout.activity_zip_code_search);
lvAddress = (ListView) findViewById(R.id.lv_address);
AddressAdapter adapter = new AddressAdapter( this, addresses );
lvAddress.setAdapter( adapter );
lvAddress.setOnItemClickListener(this);
spStates = (Spinner) findViewById(R.id.sp_state);
spStates.setAdapter( ArrayAdapter.createFromResource(this, R.array.states, android.R.layout.simple_spinner_item) );
util = new Util(this,
R.id.et_street,
R.id.et_city,
R.id.sp_state);
Como em SignUpActivity, também vamos trabalhar com o travamento / destravamento de Views via classe utilitária Util.
Veja que colocamos a implementação de OnItemClickListener diretamente em ZipCodeSearchActivity, isso para evitar a "embolação" de código devido ao uso de instância anônima caso essa estratégia não fosse adotada.
Agora o trecho com mais lógica de negócio:
private String getState(){
String state = (String) spStates.getSelectedItem();
String[] parts = state.split("\\(");
parts = parts[ parts.length - 1 ].split("\\)");
state = parts[0];
private String getCity(){
return ((EditText) findViewById(R.id.et_city)).getText().toString();
private String getStreet(){
return ((EditText) findViewById(R.id.et_street)).getText().toString();
public String getUriRequest(){
String uri = getState()+"/";
uri += getCity()+"/";
uri += getStreet()+"/";
uri += "json/";
return "https://viacep.com.br/ws/"+uri;
public void searchAddress( View view ){
new ZipCodeRequest( this ).execute();
((AddressAdapter) lvAddress.getAdapter()).notifyDataSetChanged();
public void lockFields( boolean isToLock ){
util.lockFields( isToLock );
String[] zipCodeArray = addresses.get( i ).getCep().split("-");
String zipCode = zipCodeArray[0] + zipCodeArray[1];
intent.putExtra( Address.ZIP_CODE_KEY, zipCode );
O método getUriRequest() utiliza alguns métodos que encapsulam a complexidade de acesso aos dados de algumas Views especificas. Este método é muito similar ao da classe SignUpActivity, aqui retornando a url de busca de CEPs.
O método getAddresses() permite que o código de ZipCodeRequest realize o clear() na lista addresses. Com isso reaproveitamos o objeto de lista, diminuindo a possiblidade de vazamento de memória e a posterior exceção OutOfMemoryException.
Note o método getState(). Como os itens de nosso array de estados tem a seguinte estrutura: Nome Estado (Sigla). Temos de criar uma lógica somente para pegar a sigla desta estrutura, pois a API do ViaCEP está esperando a sigla e não o nome completo do estado.
O método updateListView() permite o fácil acesso, do código em ZipCodeRequest, ao algoritmo de atualização de itens na ListView, para que o usuário tenha o reflexo da atualização na tela dele.
No onItemClickListener() obtemos o CEP da posição escolhida e o passamos para um Intent que permitirá o acesso a ele na SignUpActivity.
Com isso vamos poder atualizar o campo etZipCode desta Activity. Note que removemos o "-" para facilitar a atribuição do CEP ao correto campo em SignUpActivity.
O método searchAddress() é o que responde ao clique no TextView de rótulo "Buscar" no layout da Activity atual. Nele ativamos o ZipCodeRequest.
O código em ZipCodeSearchActivity ainda não está funcional devido a algumas atualizações que devemos realizar nas classes Address e SignUpActivity.
Mas por aqui já terminamos com o código dela, ou seja, o usuário preenche o formulário para busca do CEP. A busca é apresentada no ListView logo abaixo do formulário.
E assim, encontrado o CEP correto, quando o usuário clica no item enviamos o CEP para a Activity que contém o formulário de endereço para cadastro de conta.
Somente um detalhe, a atualização do AndroidManifest.xml:
package="br.com.thiengo.marketplaceapp">
<activity android:name=".ZipCodeSearchActivity"/>
Atualizando o domínio do problema
A seguir as atualizações nas classes Address e SignUpActivity.
Vamos iniciar com a classe Address:
public static final int RESQUEST_ZIP_CODE_CODE = 556; /* NÚMERO ALEATÓRIO */
public static final String ZIP_CODE_KEY = "zip_code_key";
Apenas adicionamos as entidades que permitem a utilização do CEP de ZipCodeSearchActivity em SignUpActivity.
Uma constante para chamada e verificação, respectivamente para uso em: startActivityForResult() e onActivityResult().
A chave ZIP_CODE_KEY é para acesso ao CEP no Intent recebido em onActivityResult().
E assim o código da Activity:
if( requestCode == Address.RESQUEST_ZIP_CODE_CODE
etZipCode.setText( data.getStringExtra( Address.ZIP_CODE_KEY ) );
public void searchZipCode(View view){
Intent intent = new Intent(this, ZipCodeSearchActivity.class);
startActivityForResult( intent, Address.RESQUEST_ZIP_CODE_CODE );
Simples. A chamada a Activity de busca de CEP, via searchZipCode() que está vinculado ao onClick do TextView de rótulo "Esqueci o CEP" do layout de SignUpActivity.
E a atribuição do valor retornado, em onActivityResult(), ao campo de CEP, etZipCode, do layout de SignUpActivity.
Execute o aplicativo em algum device ou emulador, logo em seguida clique em "Ainda não tem conta? Cadastre-se":
Na tela seguinte clique em "Esqueci o CEP":
Entre com os dados de sua região, digo, os dados: cidade, estado e rua. Logo depois clique em Buscar:
Terá o resultado sendo apresentado de maneira similar ao da figura a seguir:
Encontre a opção que se enquadra em sua região e clique nela. Assim, o formulário de endereço em SignUpActivity terá o pré-preenchimento realizado, como a seguir:
Terminamos com esta série sobre API de endereços.
Existem várias outras disponíveis, com isso fica como principal valor da série a lógica de negócio utilizada no projeto de exemplo, para você reaproveita-la com qualquer outra API.
Antes de seguir para a conclusão, não esqueça de se inscrever na 📫 lista de e-mails do Blog para receber os novos conteúdos Android exclusivos do Blog.
Vídeo com implementação passo a passo do código - Parte dois
No vídeo abaixo é apresentada a implementação passo a passo do projeto, parte dois:
Para acessar a versão Android do Projeto, completa (incluindo parte um), entre no GitHub a seguir: https://github.com/viniciusthiengo/marketplace-app.
Com o final da série de API de endereço temos que a parte crítica da implementação está ligada a lógica de negócio para gerenciar corretamente os dados retornados pela API e a posterior apresentação deles.
Ou seja, não somente para pré-preenchimento de endereço, mas para qualquer outro tipo de dado, a lógica que você utilizará em seu aplicativo é que fará a diferença tanto na qualidade do algoritmo dele quanto na qualidade percebida pelo usuário.
No aplicativo do Blog Thiengo [Calopsita], assim que você entra na área de envio de contato seu endereço de email já está presente no campo de email. Isso conforta o usuário em saber que somente precisará preencher o campo assunto e a mensagem em si.
Mas lhe digo que para a apresentação desse email, o código que obtém ele e então envia este por "n" entidades até colocar o endereço no campo correto é a parte menos trivial de todo o algoritmo.
Porque somente pegar o email é de um entidade nativa Android é algo simples. Ou seja, novamente a lógica de negócio é a parte "burocrática".
Com essa série de dois artigos, com vídeos, conseguimos assim entender que formulários não devem ser colocados no ar sem o estudo correto, digo, a otimização dos campos que devem ou não estar presentes e a melhoria do preenchimento daqueles que devem estar.
Qualquer dúvida ou sugestão, é só deixar seu comentário logo abaixo.
Por fim, não esqueça de se inscrever na 📩 lista de e-mails, respondo às suas dúvidas também por lá, além de lhe enviar conteúdos exclusivos de desenvolvimento Android.
ViaCEP - página de apresentação da API
Página GitHub Postmon API
Estratégia de Anúncios com In Loco Media no AndroidAndroid
GCMNetworkManager Para Execução de Tarefas no Background AndroidAndroid
android - formulário - weakreference - gson - viacep
Olá Thiengo. Obrigado por compartilhar seus conhecimentos. Olha que estranho: A Activity SignUpActivity retorna os dados perfeitamente.
Em outra Activity( B ) que tenho em meu app, estou usando Retrofit para buscar dados no servidor. Quando após ter navegado pela Activity "B" e retorno a Activity SignUpActivity para buscar um endereço, os dados não retornam! Só retornam se eu não usar o Retrofit na Activity "B". Parece que há um conflito entre ( HttpURLConnection ou AsyncTask ) e o Retrifit. Vc já teve esse problema?
Ainda não tive esse problema. Certificou-se de que ao menos há a conexão remota em SignUpActivity logo depois de utilizar a atividade com Retrofit?
Pode ser que na SignUpActivity você esteja esperando algum dado que é retornado da invocação realizada pelo Retrofit, e essa invocação é assíncrona, ou seja, a SignUpActivity somente deveria ser chamada depois do retorno do Retrofit. Verifique isso também.