Google Maps Android API e Location Service API

Este artigo apresenta a criação de uma aplicação LBS, usando as APIs mais modernas oferecidas pelo ambiente de desenvolvimento Android.

Fique por dentro

A construção de aplicativos Android que contenham mapas e/ou que sejam sensíveis a localização aumentou consideravelmente nos últimos anos. Sua importância para o sucesso de uma ideia também evoluiu. Talvez por esse motivo a IDE (Integrated Development Environment) Android Studio ofereça várias melhorias que tornam o processo de construção de uma aplicação LBS (Location Based System) simplificado em relação ao Android ADT (Android Developer Tools). Além disso, a própria API sofreu algumas modificações ao longo do tempo, tornando processos assíncronos e fornecendo uma economia na chamada de métodos para realizar as mesmas tarefas de outrora. O objetivo deste artigo é deixar o leitor ciente das novas features da API e também apto a trabalhar com aplicações LBS no Android Studio.

O uso de mapas nas aplicações Android sempre forneceu um aspecto mais profissional ao produto final. Isso se deve em parte à qualidade da própria aplicação Google Maps e de sua imensa adoção entre usuários de todas as plataformas. Também podemos atribuir isso à qualidade que uma informação de localização tem em aplicações LBS. Mostrar um endereço em um campo de texto, como um TextView, e mostrar o mesmo endereço como um POI (Point Of Interest) em um mapa é uma diferença gritante de qualidade da informação.

Porém, apesar de seu imenso apelo, o processo de desenvolvimento de uma aplicação LBS não era tão simples quando existia somente a IDE Eclipse e seu plugin ADT. Talvez o leitor não tenha passado por essa fase, mas para gerar as keystores (ler NOTA 1) necessárias era preciso abrir o terminal de comandos (MS-DOS no Windows ou Shell Linux/Mac) e digitar uma extensa linha de comando.

Nota 1: Keystores - Toda aplicação Android precisa ser digitalmente assinada antes de poder ser executada em um smartphone. Para isso, o ambiente de desenvolvimento usa uma keystore para gerar a versão final da aplicação. Esse arquivo é o apk (application package).

Depois, a necessidade de criar um projeto no Google Developer Console também não ajudou muito, isso porque era necessário sair da IDE, abrir o browser, lembrar de cabeça o site ou realizar uma busca rápida no Google e, finalmente, seguir um processo como receita de bolo para conseguir gerar as chaves necessárias ao projeto no lado do Android.

No Android Studio não é preciso usar o console para gerar a keystore e o passo a passo realizado no browser foi simplificado. Além dessa mudança drástica e benéfica no processo de criação de uma aplicação LBS, houve a evolução natural das APIs, tanto no caso do Maps quanto no caso da Location Service API. Essa última permite que não só um mapa seja mostrado ao usuário, mas também que seja possível ler sua última posição geográfica e acompanhar possíveis deslocamentos. Isso sem falar na quantidade imensa de outras features disponíveis aos desenvolvedores.

Sendo assim, o objetivo primordial deste artigo é a criação de uma aplicação LBS inteira, usando as APIs mais modernas oferecidas pelo ambiente de desenvolvimento Android. Além disso, será usada a IDE Android Studio e as ferramentas disponíveis para esse tipo de aplicação.

Criando uma aplicação com mapa

Talvez um dos melhores benefícios para nós programadores seja a facilidade que o Android Studio trouxe para criação de aplicativos que fazem uso da Google Maps for Android. Existem duas maneiras de criá-los. O primeiro passo em ambas as maneiras é ir no menu File > New > New Project.

Depois da primeira tela do assistente, onde são definidos os valores para o nome do projeto, company domain e localização do projeto, o usuário escolherá as plataformas e exigências relacionadas ao SDK. Escolherá, por exemplo, se uma aplicação rodará ou não em uma TV, em um dispositivo wear ou em outras plataformas.

Depois desse processo, o assistente apresentado ao usuário permite adicionar uma Activity à aplicação. A IDE Android Studio já fornece alguns templates prontos para que o desenvolvedor possa utilizar. Observe a Figura 1.

Figura 1. Assistente para adição de uma Activity

Se o desenvolvedor já tem certeza que sua aplicação possuirá uma tela com mapa, a opção Google Maps Activity já pode ser selecionada. Porém, aqui vamos focar em quais ações o leitor deve realizar caso ele já tenha criado a aplicação e precise criar uma tela que faça uso do Google Maps.

Sendo assim, escolha a opção Empty Activity e clique no botão Next. Na última tela do assistente o leitor pode simplesmente clicar na opção Finish, deixando o nome padrão para o arquivo Java referente à Activity e para o arquivo XML referente ao layout da tela.

Depois disso o leitor visualizará o Android Studio com toda a estrutura de arquivos e pastas necessárias para o funcionamento de uma aplicação Android. Como dito antes, vamos supor que, depois da criação de algumas telas a mais, o desenvolvedor foi requisitado para inserir uma tela com um mapa centralizado na última posição conhecida do usuário.

Sendo assim, será necessário criar uma Activity para comportar essa feature. Na IDE esse processo é simples, basta ir na pasta java, clicar com o botão direito do mouse e navegar pelo seguinte menu: New > Google > Google Maps Activity (Figura 2). Perceba que até existe uma opção de Activity um pouco acima do menu Google, mas não é a que desejamos nesse momento. Ao escolher essa opção de menu, o desenvolvedor será apresentado ao assistente presente na Figura 3.

Figura 2. Menu para criação de uma Google Maps Activity
Figura 3. Assistente para criação de uma Google Maps Activity

Se tudo estiver certo, o desenvolvedor será direcionado para o arquivo google_maps_api.xml. Esse arquivo é o ponto que reflete o quanto a IDE Android Studio facilitou a nossa vida. Observe a Listagem 1.

1. <resources> 2. <!-- 3. TODO: Before you run your application, you need a Google Maps API key. 4. 5. To get one, follow this link, follow the directions and press "Create" at the end: 6. 7. https://console.developers.google.com/flows/enableapi?apiid=maps_android_backend& keyType=CLIENT_SIDE_ANDROID&r=FB:64:FB:34:A2:62:3C:24:A4:0A:5F:13:68:7C:70: 45:E6:E4:43:35%3Bartigos.devmedia.com.artigomapas 8. 9. You can also add your credentials to an existing key, using this line: 10. 11. FB:64:FB:34:A2:62:3C:24:A4:0A:5F:13:68:7C:70:45:E6:E4:43:35;artigos.devmedia.com.artigomapas 12. Alternatively, follow the directions here: 13. https://developers.google.com/maps/documentation/android/start#get-key 14. 15. Once you have your key (it starts with "AIza"), replace the "google_maps_key" string in this file. 16. --> 17. <string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">YOUR_KEY_HERE</string> 18. </resources>
Listagem 1. Conteúdo do arquivo google_maps_api.xml

Perceba que na linha 9, na frase que começa com “you can also add your”, a IDE já descobriu o SHA-1 (ler Nota 2) da keystore de testes usada pelo Android Studio. No Eclipse com o plugin ADT era necessário usar a ferramenta keytool, que vem na instalação do JDK (Java Development Kit), com alguns parâmetros para descobrir esse dado. Isso tudo era feito no terminal de comandos.

Nota 2: O SHA-1 é uma das funções de dispersão SHA mais utilizadas no mercado atualmente. O SHA (secure hash algorithm), na sua versão original, produz um valor de dispersão de 160 bits conhecido como resumo da mensagem. Esse valor é necessário porque o site de desenvolvedores do Google o exige para criar uma nova aplicação e associá-la à aplicação Android.

Na linha 7 o leitor se deparará com um link. Basta copiar e colar no seu browser preferido. É importante ressaltar que é necessário estar conectado com a sua conta Google. Qualquer uma delas serve, Gmail, YouTube, entre outras. A página carregada deve ser semelhante à ilustrada na Figura 4.

Figura 4. Tela inicial do Google Developer Console

Neste momento o leitor tem duas opções: Usar um projeto já existente ou Criar um projeto.

É importante ressaltar que o projeto criado no Google Developer Console não tem ligação com o projeto criado para o Android, a não ser pela geração das chaves. Isso faz com que o nome não necessite ser obrigatoriamente o mesmo, apesar de ser uma boa prática.

No caso de sucesso, a próxima página visualizada será semelhante à mostrada na Figura 5. Nesta página temos um feedback de que o projeto foi criado e a Google Maps Android API já foi ativada nele. O próximo passo é acessar a página de credenciais para criar as chaves necessárias no projeto Android. Para isso, clique no botão Acessar Credenciais.

Figura 5. API ativada e link para acesso às credenciais

Na página seguinte (mostrada na Figura 6), encontram-se algumas informações interessantes para este artigo. Logo na parte superior temos um campo de texto para edição do nome da chave. Se o leitor desejar, pode manter o nome padrão. Logo abaixo o console apresenta um parágrafo que explica de forma sucinta para que serve essa chave e o SHA-1: “Dispositivos Android enviam solicitações de API diretamente para o Google. O Google verifica se cada solicitação vem de um app para Android que corresponde a um nome de pacote e a uma impressão digital de assinatura SHA-1 que você fornece. Verifique o nome do pacote no arquivo AndroidManifest.xml. Use o comando a seguir para receber a impressão digital”.

Abaixo da explicação temos um comando que nos revelará a impressão digital para certificação SHA-1 da keystore. Não precisamos fazer isso porque a IDE que estamos utilizando já nos poupou desse serviço. Perceba também que logo abaixo, nos campos de nome do pacote e SHA-1, já existem valores pré-configurados. Isso acontece porque, no link que copiamos do arquivo xml gerado no Android Studio, os parâmetros passados por query string já informavam esses valores.

Figura 6. Página para criação de credencial no Google Developer Console

Finalmente, basta clicar no botão Criar. O resultado esperado aqui é o surgimento de um pop-up com o título “Chave da API”. Copie a chave que está dentro da caixa de texto e retorne para o Android Studio. Na Figura 7 ilustramos o pop-up mencionado.

Figura 7. Pop-up com a chave de API gerada

Feito isso, retorne à Listagem 1 e cole a chave no final do arquivo google_maps_api.xml, onde existe um recurso com a marcação YOUR_KEY_HERE. Sua aplicação já está pronta para mostrar um mapa 2D/3D de qualquer localização geográfica.

Só é preciso fazer mais uma alteração no projeto para rodar a aplicação e vermos a tela com o mapa. No arquivo AndroidManifest.xml encontramos as principais configurações da aplicação Android. Dentro da tag raiz manifest temos a tag application. Dentro dessa última, por sua vez, temos tags activity que identificam as telas que compõem nossa criação. O conteúdo do arquivo de manifesto deve estar semelhante ao apresentado na Listagem 2.

… <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key" /> <activity android:name=".MapsActivity" android:label="@string/title_activity_maps"></activity> </application> ...
Listagem 2. AndroidManifest.xml padrão criado pela IDE

A primeira activity refere-se à MainActivity, que foi criada automaticamente no momento da criação do próprio projeto. Nessa tag, perceba que temos um intent-filter e, dentro do mesmo, uma action e uma category. Esse filtro serve para identificar essa tela como a principal da aplicação, ou seja, quando a mesma é aberta, a tela que será vista antes de qualquer outra será a MainActivity.

Na tag com a propriedade android:name=".MapsActivity", perceba que não existe esse filtro. Sendo assim, basta dar um ctrl+x e ctrl+v, removendo o filtro da MainActivity para a MapsActivity. Observe na Listagem 3 como esse XML deve ficar após a edição.

... <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> </activity> <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key" /> <activity android:name=".MapsActivity" android:label="@string/title_activity_maps"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> ...
Listagem 3. AndroidManifest.xml após a edição

Depois dessa alteração o leitor pode executar a aplicação. O resultado é apresentado na Figura 8.

Figura 8. Aplicação em execução

Análise do código gerado

Neste momento vamos entender o que a IDE criou e o que o código quer nos dizer. A classe MapsActivity foi criada com o intuito de nos mostrar o mapa. Perceba que logo no método onCreate, que faz parte do ciclo de vida de uma Activity, o conteúdo visual da mesma está sendo setado para o recurso R.layout.activity_maps (com o método setContentView) como mostra o trecho de código da Listagem 4.

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_maps); SupportMapFragment mapFragment = (SupportMapFragment)getSupportFragmentManager() .findFragmentById(R.id.map); mapFragment.getMapAsync(this); }
Listagem 4. Método onCreate

Sendo assim, comecemos a análise nesse arquivo xml, que deverá ser muito semelhante ao apresentado na Listagem 5.

<fragment xmlns:android="http://schemas.android.com/apk/res/android" xmlns:map="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/map" android:name="com.google.android.gms.maps.SupportMapFragment" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="artigos.devmedia.com.artigomapas.MapsActivity" />
Listagem 5. Arquivo activity_maps.xml

O fragmento irá ocupar toda a extensão da tela, tanto na horizontal quanto na vertical. Isso se explica pelo valor match_parent nos atributos layout_width e layout_height. O id é de suma importância, pois será usado para recuperar esse fragmento no código Java, como veremos na sequência. Por fim, outro ponto importante é o android:name, que define a classe que tratará do ciclo de vida e do conteúdo visual desse fragment. Na listagem percebemos que essa classe é da própria biblioteca do Maps para Android. Voltando ao trecho de código do onCreate, iremos focar nesta linha:

SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() .findFragmentById(R.id.map);

O método getSupportFragmentManager recupera uma instância de FragmentManager da biblioteca de compatibilidade, permitindo, assim, o uso do conceito de Fragment nas versões mais antigas do Android. Logo em seguida, é chamado o método findFragmentById. O id passado por parâmetro é o mesmo configurado no atributo id da Listagem 5. O casting explícito serve para definir o retorno como uma instância de classe SupportMapFragment, classe filha de Fragment.

As demais linhas da classe MapsActivity também merecem uma atenção especial. Observe a Listagem 6.

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback { private GoogleMap mMap; @Override protected void onCreate(Bundle savedInstanceState) { //... mapFragment.getMapAsync(this); } @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; // Add a marker in Sydney and move the camera LatLng sydney = new LatLng(-34, 151); mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney")); mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney)); } }
Listagem 6. Classe MapsActivity

Iniciemos o estudo na linha 2. A classe GoogleMap nos dá acesso a qualquer alteração desejada no mapa, como inserção de POIs, mudança do ponto central, mudança do nível de zoom e ângulo de visão etc. Na linha 6 chamamos o método getMapAsync da classe SupportMapFragment, que fará o processo de recuperar a instância de GoogleMap de forma assíncrona. Esse método espera receber uma implementação da interface OnMapReadyCallback, portanto passamos a palavra reservada this porque a própria classe MapsActivity implementa essa interface (veja a linha 1).

Como estamos utilizando a linguagem de programação Java, o uso de uma interface obriga que a classe forneça a implementação concreta de todos os métodos abstratos da mesma. Nesse caso, o único método que obrigatoriamente deve ser implementado é o onMapReady (linha 10). Perceba que o próprio método já recebe por parâmetro uma instância da classe GoogleMap.

Dentro do referido método, mais especificamente na linha 11, passamos o valor recebido por parâmetro para a variável global mMap, que é uma instância de GoogleMap. A sequência de linhas 14, 15 e 16 cria um POI no mapa. Como todo ponto geográfico, sua referência é através dos dados de latitude e longitude, logo foi utilizada uma classe chamada LatLng, fornecida pela API (veja a linha 14).

Na linha 15, utilizou-se do método addMarker da classe GoogleMap. O parâmetro que esse método exige é uma instância de MarkerOptions, que, por sua vez, possui métodos para definir a posição geográfica do marcador (método position) e o título que será mostrado quando o mesmo sofrer um evento de click por parte do usuário (método title). Por fim, na linha 16, o método moveCamera move a câmera que “filma” o mapa que está sendo mostrado ao usuário. A classe auxiliar CameraUpdateFactory fornece vários métodos para posicionamento da câmera. No código gerado pela IDE foi usado o newLatLng, no qual apenas uma nova posição é definida.

Com isso nossa aplicação já mostra um mapa, entendemos tudo o que está acontecendo e, principalmente, como e por que a IDE gerou todo esse código para facilitar nosso trabalho.

Mostrando a última posição conhecida

O próximo passo é colocar no ponto central do mapa a última posição que a API conseguiu ler. Para conseguir implementar essa feature, em nossa aplicação faremos uso da Location Service API, que é um dos serviços disponibilizados pela Google Play Services. A mesma disponibiliza vários serviços de sucesso do Google, por exemplo o Google Analytics, Google Cloud Messaging (serviço para comunicação servidor-cliente), Google Drive, Google Wallet, Google Play Game Services, Android Wear, Google Account Login, entre outros.

Os serviços rodam em background através de uma aplicação instalada no próprio smartphone. A API nos fornece uma classe que serve como ligação entre a nossa aplicação e esses serviços, chamada GoogleApiClient. Observe na Listagem 7 as mudanças que irão ocorrer na classe MapsActivity.

… public class MapsActivity extends FragmentActivity implements OnMapReadyCallback, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { … private GoogleApiClient mGoogleApiClient; ... @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; // Create an instance of GoogleAPIClient. if (mGoogleApiClient == null) { mGoogleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); mGoogleApiClient.connect(); } } @TargetApi(Build.VERSION_CODES.M) @Override public void onConnected(Bundle bundle) { ... } @Override public void onConnectionSuspended(int i) {} @Override public void onConnectionFailed(ConnectionResult connectionResult) {} }
Listagem 7. Mudanças na classe Maps Activity

Na listagem anterior, o método onMapReady adicionou um simples marker ao mapa independentemente da posição real do usuário. Agora, verifica-se se a variável global mGoogleApiClient está nula para que, caso afirmativo, sua instância seja criada. Esse teste lógico é extremamente importante porque é esse cliente que liga nossa aplicação ao Google Play Services.

No Builder da GoogleApiClient foi passado apenas um parâmetro, que é uma instância de Context. A palavra reservada this pode ser usada para referenciar a instância de Context porque o this refere-se à instância da classe corrente e, nesse caso, a Activity herda diretamente de Context. Em seguida temos três métodos auxiliares:

Depois disso, na linha 16, é chamado o método connect da classe GoogleApiClient. Se tudo ocorrer bem, o método onConnected será chamado. Veja como esse método deve ficar na Listagem 8.

@TargetApi(Build.VERSION_CODES.M) @Override public void onConnected(Bundle bundle) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1); return; } Location mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); LatLng eu = new LatLng(mLastLocation.getLatitude(), mLastLocation.getLongitude()); mMap.addMarker(new MarkerOptions().position(eu).title("Estou aqui")); mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(eu, 16)); }
Listagem 8. Método onConnected

O primeiro teste lógico, localizado logo no início do método, serve para garantir que o usuário deu permissões para uso do ACCESS_FINE_LOCATION, necessário para buscar a informação localizada com GPS (Global Position System).

Caso negativo, o método requestPermissions é usado, justamente para requisitar a permissão ao usuário. Ao executar a aplicação pela primeira vez o usuário receberá um pop-up semelhante ao apresentado na Figura 9.

Figura 9. Pop-up requisitando permissão

As permissões sofreram grandes mudanças a partir do Android 6.0 (Marshmallow). Devido a essas diferenças de versões do sistema operacional, no início do método é utilizada a annotation @TargetApi, indicado o direcionamento desse método para o Android 6.

Na chamada do método requestPermission, presente na linha 5, o primeiro parâmetro é um array de Strings com todas as permissões que serão requisitadas ao usuário, e o segundo é o request_code, que poderá ser avaliado no retorno da resposta do usuário. Caso o usuário ainda não tenha fornecido permissão para o ACESS_FINE_LOCATION, o return é chamado interrompendo o método precocemente já que as linhas subsequentes não farão sentido sem a permissão.

No código que segue (linhas 8 a 11), é recuperada a última posição do usuário conhecida pelo LocationServices. Através de sua classe interna e estática, FusedLocationApi, e de seu método getLastLocation, recebemos como retorno uma instância de Location. Perceba que o parâmetro do método é a instância de GoogleApiClient.

A classe Location contém diversas informações sobre uma localização geográfica, sendo que os dados mais importantes nesse momento para o nosso código são latitude e longitude, recuperados por meio dos métodos getLatitude e getLongitude. Ambos estão sendo passados na criação da instância de LatLng.

Essa instância é usada primeiramente para adicionar um novo marker no mapa, porém, diferente do que acontecia anteriormente, estamos indicando exatamente onde o usuário está. Em seguida a câmera do mapa é animada para uma nova latitude e longitude e, além disso, para um nível de zoom igual a 16. Esse valor pode variar de 1 a 21, sendo o maior valor para um nível maior de zoom in. Perceba que novamente usamos a classe CameraUpdateFactory e um de seus métodos auxiliares.

Agora execute a aplicação novamente. O resultado deve ser algo similar ao apresentado na Figura 10.

Figura 10. Aplicativo executando e mostrando a última posição conhecida do usuário

O uso de mapas em aplicações Android não é uma feature nova, sua utilização torna os aplicativos mais sensíveis à localização e ao contexto do usuário a um bom tempo. Na IDE Android Studio foi dada uma atenção especial à construção desse tipo de aplicativo, facilitando a vida do desenvolvedor e tornando o processo mais rápido e fácil.

Além disso, as inovações constantes na própria API contribuem para isso. Porém, às vezes, desenvolvedores menos assíduos da plataforma são surpreendidos por essas rápidas mudanças. Este artigo visou explicar como criar uma aplicação LBS, apresentando a forma de trabalhar com a Google Maps for Android e Location Services API na mais recente versão do Android Studio.

Confira também

Artigos relacionados