Primeiros passos com o SDK do Places para Android (Kotlin)

1. Antes de começar

Este codelab ensina como integrar o SDK do Places para Android ao seu app e usar cada um dos recursos dele.

App de demonstração do Places

Pré-requisitos

  • Conhecimento básico de desenvolvimento Android e Kotlin

O que você vai aprender

  • Como instalar o SDK do Places para Android com extensões Kotlin.
  • Como carregar detalhes de um lugar específico.
  • Como adicionar um widget do Place Autocomplete ao app.
  • Como carregar o local atual com base no lugar informado pelo dispositivo no momento.

Pré-requisitos

Para concluir este codelab, você vai precisar das seguintes contas, serviços e ferramentas:

2. Começar a configuração

Para a etapa abaixo, ative a API Places e o SDK do Maps para Android.

Configurar a Plataforma Google Maps

Caso você ainda não tenha uma conta do Google Cloud Platform e um projeto com faturamento ativado, veja como criá-los no guia da Plataforma Google Maps.

  1. No Console do Cloud, clique no menu suspenso do projeto e selecione o projeto que você quer usar neste codelab.

  1. Ative as APIs e os SDKs da Plataforma Google Maps necessários para este codelab no Google Cloud Marketplace. Para fazer isso, siga as etapas descritas neste vídeo ou nesta documentação.
  2. Gere uma chave de API na página Credenciais do Console do Cloud. Siga as etapas indicadas neste vídeo ou nesta documentação. Todas as solicitações à Plataforma Google Maps exigem uma chave de API.

3. Início rápido

Para começar o mais rápido possível e acompanhar este codelab, faça o download do código inicial. Se preferir, você pode ir direto para a solução, mas continue lendo se quiser desenvolver por conta própria.

  1. Clone o repositório se você tiver o git instalado.
git clone https://github.com/googlemaps/codelab-places-101-android-kotlin.git

Se preferir, clique neste botão para fazer o download do código-fonte.

  1. Depois de fazer o download do código, abra o projeto no diretório /starter do Android Studio. Esse projeto inclui a estrutura de arquivos básica necessária para concluir o codelab. Tudo o que você precisa para trabalhar está no diretório /starter.

Se você quiser ver o código completo da solução em execução, veja o código concluído no diretório /solution.

4. Adicionar sua chave de API ao projeto

Nesta seção, descrevemos como armazenar sua chave de API para que ela possa ser referenciada com segurança pelo seu app. Não faça a verificação dela no sistema de controle de versões. Recomendamos armazená-la no arquivo secrets.properties, que será colocado na sua cópia local do diretório raiz do projeto. Para saber mais sobre o arquivo secrets.properties, consulte Arquivos de propriedades do Gradle.

Para otimizar essa tarefa, recomendamos usar o plug-in Secrets Gradle para Android.

Para instalar esse plug-in no seu projeto do Google Maps, faça o seguinte:

  1. No Android Studio, abra o arquivo de nível superior build.gradle.kts ou build.gradle e adicione o seguinte código ao elemento dependencies em buildscript.

Se você estiver usando build.gradle.kts, adicione:

buildscript {
    dependencies {
        classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
    }
}

Se você estiver usando build.gradle, adicione:

buildscript {
    dependencies {
        classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
    }
}
  1. Abra o arquivo build.gradle.kts ou build.gradle no nível do módulo e adicione o seguinte código ao elemento plugins.

Se você estiver usando build.gradle.kts, adicione:

plugins {
    // ...
    id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
}

Se você estiver usando build.gradle, adicione:

plugins {
    // ...
    id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
  1. No arquivo build.gradle.kts ou build.gradle no nível do módulo, defina targetSdk e compileSdk como 34.
  2. Salve o arquivo e sincronize seu projeto com o Gradle.
  3. Abra o arquivo secrets.properties no diretório de nível superior e adicione o código a seguir. Substitua YOUR_API_KEY pela sua chave de API. Armazene sua chave nesse arquivo porque secrets.properties não é verificado em um sistema de controle de versões.
PLACES_API_KEY=YOUR_API_KEY
  1. Salve o arquivo.
  2. Crie o arquivo local.defaults.properties no seu diretório de nível superior, na mesma pasta que o arquivo secrets.properties, e depois adicione o seguinte código.
PLACES_API_KEY=DEFAULT_API_KEY

O objetivo desse arquivo é oferecer um local de backup para a chave da API se o arquivo secrets.properties não for encontrado, para que os builds não apresentem falha. Isso pode acontecer se você clonar o app de um sistema de controle de versões que omite secrets.properties e ainda não tiver criado um arquivo secrets.properties localmente para fornecer sua chave de API.

  1. Salve o arquivo.
  2. No Android Studio, abra o arquivo build.gradle.kts ou build.gradle no nível do módulo e edite a propriedade secrets. Se a propriedade secrets não existir, adicione-a.

Edite as propriedades do plug-in para definir propertiesFileName como secrets.properties, defaultPropertiesFileName como local.defaults.properties e outras propriedades.

secrets {
    // Optionally specify a different file name containing your secrets.
    // The plugin defaults to "local.properties"
    propertiesFileName = "secrets.properties"

    // A properties file containing default secret values. This file can be
    // checked in version control.
    defaultPropertiesFileName = "local.defaults.properties"
}

5. Instalar o SDK do Places para Android

Nesta seção, você adiciona o SDK do Places para Android às dependências do seu app.

  1. Agora que sua chave de API pode ser acessada no app, adicione a dependência do SDK do Places para Android ao arquivo build.gradle do seu app.

Modifique o arquivo build.gradle no nível do app para adicionar a dependência do SDK do Places para Android:

build.gradle no nível do app

dependencies {
   // Dependency to include Places SDK for Android
   implementation 'com.google.android.libraries.places:places:3.4.0'
}
  1. Execute o app.

Você vai ver um app com uma tela vazia. Continue a preencher a tela com três demonstrações.

6. Instalar o Android KTX do Places

Para apps Kotlin que usam um ou mais SDKs do Android da Plataforma Google Maps, as bibliotecas de extensão Kotlin (KTX) permitem aproveitar recursos da linguagem Kotlin, como corrotinas, propriedades/funções de extensão e muito mais. Cada SDK do Google Maps tem uma biblioteca KTX correspondente, como mostrado abaixo:

Diagrama KTX da Plataforma Google Maps

Nesta tarefa, utilize a biblioteca Places do Android KTX para usar recursos da linguagem específica do Kotlin no seu app.

Adicionar a dependência do Android KTX do Places

Para aproveitar os recursos específicos do Kotlin, inclua a biblioteca KTX correspondente para esse SDK no arquivo build.gradle no nível do app.

build.gradle

dependencies {
    // ...

    // Places SDK for Android KTX Library
    implementation 'com.google.maps.android:places-ktx:3.1.1'
}

7. Inicializar o cliente do Places

Inicializar o SDK do Places para o escopo do aplicativo

No arquivo DemoApplication.kt da pasta app/src/main/java/com/google/codelabs/maps/placesdemo, inicialize o SDK do Places para Android. Cole as linhas abaixo no fim da função onCreate:

        // Initialize the SDK with the Google Maps Platform API key
        Places.initialize(this, BuildConfig.PLACES_API_KEY)

Quando você cria o app, o plug-in de secrets do Gradle para Android (em inglês) disponibiliza a chave de API no seu arquivo secrets.properties como BuildConfig.PLACES_API_KEY.

Adicionar o arquivo de aplicativo ao manifesto

Como você estendeu o Application com DemoApplication, é necessário atualizar o manifesto. Adicione a propriedade android:name ao elemento application no arquivo AndroidManifest.xml, localizado em app/src/main:

    <application
        android:name=".DemoApplication"
        ...
    </application>

Esse código aponta o manifesto do aplicativo para a classe DemoApplication na pasta src/main/java/com/google/codelabs/maps/placesdemo/.

8. Buscar Place Details

Criar a tela "Detalhes"

Um layout activity_details.xml com um LinearLayout vazio está disponível na pasta app/src/main/res/layout/. Preencha o layout linear adicionando o seguinte código entre os colchetes <LinearLayout>.

    <com.google.android.material.textfield.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/details_input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/details_input_hint"
            android:text="@string/details_input_default" />

    </com.google.android.material.textfield.TextInputLayout>

    <Button
        android:id="@+id/details_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/button_details" />

    <TextView
        android:id="@+id/details_response_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        android:textIsSelectable="true" />

Esse código adiciona um campo de entrada de texto em que o usuário pode inserir qualquer ID de local ou usar o padrão fornecido, um botão para iniciar a solicitação do Place Details e um TextView para exibir as informações da resposta. As strings associadas são definidas para você no arquivo src/main/res/values/strings.xml.

Criar a atividade "Detalhes"

  1. Crie um arquivo DetailsActivity.kt na pasta src/main/java/com/google/codelabs/maps/placesdemo/ e associe-o ao layout que você acabou de criar. Cole este código no arquivo:
@ExperimentalCoroutinesApi
class DetailsActivity : AppCompatActivity() {
    private lateinit var placesClient: PlacesClient
    private lateinit var detailsButton: Button
    private lateinit var detailsInput: TextInputEditText
    private lateinit var responseView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_details)

        // Set up view objects
        detailsInput = findViewById(R.id.details_input)
        detailsButton = findViewById(R.id.details_button)
        responseView = findViewById(R.id.details_response_content)

        val apiKey = BuildConfig.PLACES_API_KEY

        // Log an error if apiKey is not set.
        if (apiKey.isEmpty() || apiKey == "DEFAULT_API_KEY") {
            Log.e(TAG, "No api key")
            finish()
            return
        }
    }
}
  1. Crie um cliente do Places para usar com essa atividade. Cole esse código após o código para verificar a chave de API na função onCreate.
        // Retrieve a PlacesClient (previously initialized - see DemoApplication)
        placesClient = Places.createClient(this)
  1. Depois que o cliente do Places estiver configurado, anexe um listener de cliques ao botão. Cole este código após a criação do cliente do Places na função onCreate.
        // Upon button click, fetch and display the Place Details
        detailsButton.setOnClickListener { button ->
            button.isEnabled = false
            val placeId = detailsInput.text.toString()
            val placeFields = listOf(
                Place.Field.NAME,
                Place.Field.ID,
                Place.Field.LAT_LNG,
                Place.Field.ADDRESS
            )
            lifecycleScope.launch {
                try {
                    val response = placesClient.awaitFetchPlace(placeId, placeFields)
                    responseView.text = response.prettyPrint()
                } catch (e: Exception) {
                    e.printStackTrace()
                    responseView.text = e.message
                }
                button.isEnabled = true
            }
        }

Esse código recupera o ID de lugar que foi inserido no campo de entrada, define quais campos vão ser solicitados, cria um FetchPlaceRequest, inicia a tarefa e detecta um sucesso ou uma falha. Quando a solicitação é bem-sucedida, a função preenche a TextView com os detalhes solicitados.

Adicionar a atividade "Detalhes" ao manifesto

Adicione um elemento <activity> para o DetailsActivity como filho do elemento <application> no arquivo AndroidManifest.xml, localizado em app/src/main:

        <activity android:name=".DetailsActivity" android:label="@string/details_demo_title" />

Adicionar a atividade "Detalhes" ao menu de demonstração

Um módulo Demo vazio é fornecido para listar as demonstrações disponíveis na tela inicial. Agora que você criou uma atividade do Place Details, adicione-a ao arquivo Demo.kt na pasta src/main/java/com/google/codelabs/maps/placesdemo/ com este código:

    DETAILS_FRAGMENT_DEMO(
        R.string.details_demo_title,
        R.string.details_demo_description,
        DetailsActivity::class.java
    ),

As strings associadas são definidas no arquivo src/main/res/values/strings.xml.

Inspecione o MainActivity.kt e observe que ele cria um ListView preenchido pela iteração do conteúdo do módulo Demo. Quando o usuário toca em um item da lista, o listener de cliques abre a atividade associada.

Executar o app

  1. Execute o app. Desta vez, você vai ver um item na lista com a demonstração do Place Details.
  2. Toque no texto do Place Details. Você vai ter acesso à visualização criada com um botão e um campo de entrada.
  3. Toque no botão "VER DETALHES". Se você usou o ID de lugar padrão, vai ver o nome, o endereço e as coordenadas do mapa, como mostrado na Figura 1.

Atividade do Place Details com resposta

Figura 1. Atividade do Place Details com resposta exibida.

9. Adicionar Place Autocomplete

Criar uma tela de preenchimento automático

Um layout activity_autocomplete.xml com um LinearLayout vazio foi fornecido na pasta app/src/main/res/layout/. Preencha o layout linear adicionando este código entre os colchetes <LinearLayout>.

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/autocomplete_fragment"
        android:background="@android:color/white"
        android:name="com.google.android.libraries.places.widget.AutocompleteSupportFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/autocomplete_response_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        android:textIsSelectable="true" />

Esse código adiciona um widget AutocompleteSupportFragment e um TextView para exibir as informações da resposta. As strings associadas são definidas no arquivo src/main/res/values/strings.xml.

Criar uma atividade de preenchimento automático

  1. Crie um arquivo AutocompleteActivity.kt na pasta src/main/java/com/google/codelabs/maps/placesdemo/ e defina-o com este código:
@ExperimentalCoroutinesApi
class AutocompleteActivity : AppCompatActivity() {
    private lateinit var responseView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_autocomplete)

        // Set up view objects
        responseView = findViewById(R.id.autocomplete_response_content)
        val autocompleteFragment =
            supportFragmentManager.findFragmentById(R.id.autocomplete_fragment)
                    as AutocompleteSupportFragment
    }
}

Esse código associa a atividade às visualizações e ao AutocompleteSupportFramgent que você definiu no arquivo de layout.

  1. Em seguida, defina o que acontece quando o usuário seleciona uma das previsões apresentadas pelo Place Autocomplete. Adicione este código ao fim da função onCreate:
        val placeFields: List<Place.Field> =
            listOf(Place.Field.NAME, Place.Field.ID, Place.Field.ADDRESS, Place.Field.LAT_LNG)
        autocompleteFragment.setPlaceFields(placeFields)

        // Listen to place selection events
        lifecycleScope.launchWhenCreated {
            autocompleteFragment.placeSelectionEvents().collect { event ->
                when (event) {
                    is PlaceSelectionSuccess -> {
                        val place = event.place
                        responseView.text = prettyPrintAutocompleteWidget(place, false)
                    }

                    is PlaceSelectionError -> Toast.makeText(
                        this@AutocompleteActivity,
                        "Failed to get place '${event.status.statusMessage}'",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
        }

Esse código define os campos que são solicitados para o lugar e detecta um evento de seleção de lugar, além de sucesso ou falha. Quando a solicitação é bem-sucedida, a função preenche a TextView com os detalhes do lugar. O Place Autocomplete retorna um objeto do Place. Não é necessário fazer outra solicitação do Place Details ao usar o widget do Place Autocomplete.

Adicionar a atividade de preenchimento automático ao manifesto

Adicione um elemento <activity> para o AutocompleteActivity como filho do elemento <application> no arquivo AndroidManifest.xml, localizado em app/src/main:

        <activity android:name=".AutocompleteActivity" android:label="@string/autocomplete_fragment_demo_title" />

Adicionar a atividade de preenchimento automático ao menu de demonstração

Assim como antes, adicione a demonstração do Place Autocomplete à tela inicial anexando-a à lista no módulo Demo. Agora que você criou uma atividade do Place Autocomplete, adicione-a ao arquivo Demo.kt na pasta src/main/java/com/google/codelabs/maps/placesdemo/. Cole este código imediatamente após o item DETAILS_FRAGMENT_DEMO:

    AUTOCOMPLETE_FRAGMENT_DEMO(
        R.string.autocomplete_fragment_demo_title,
        R.string.autocomplete_fragment_demo_description,
        AutocompleteActivity::class.java
    ),

As strings associadas são definidas no arquivo src/main/res/values/strings.xml.

Executar o app

  1. Execute o app. Desta vez, você vai ver dois itens na lista da tela inicial.
  2. Toque na linha do Place Autocomplete. Uma entrada do Place Autocomplete é exibida como mostrado na Figura 2.
  3. Comece a digitar o nome de um lugar. Pode ser o nome de um estabelecimento, um endereço ou uma região geográfica. As previsões são apresentadas enquanto você digita.
  4. Selecione uma das previsões. As previsões vão desaparecer, e o TextView vai exibir os detalhes do lugar selecionado, como mostrado na Figura 3.

Atividade de preenchimento automático depois que o usuário toca no campo de entrada

Figura 2. Atividade de preenchimento automático depois que o usuário toca no campo de entrada.

Atividade de preenchimento automático depois que o usuário digita e seleciona &quot;Niagara Falls&quot;

Figura 3. Atividade de preenchimento automático que exibe o Place Details depois que o usuário digita e seleciona "Niagara Falls".

10. Acessar o local atual do dispositivo

Criar uma tela do local atual

Um layout activity_current.xml com um LinearLayout vazio foi fornecido na pasta app/src/main/res/layout/. Preencha o layout linear adicionando o seguinte código entre os colchetes <LinearLayout>.

    <Button
        android:id="@+id/current_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/current_button" />

    <TextView
        android:id="@+id/current_response_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        android:scrollbars = "vertical"
        android:textIsSelectable="true" />

Criar uma atividade do local atual

  1. Crie um arquivo CurrentPlaceActivity.kt na pasta src/main/java/com/google/codelabs/maps/placesdemo/ e defina-o com este código:
@ExperimentalCoroutinesApi
class CurrentPlaceActivity : AppCompatActivity(), OnMapReadyCallback {
    private lateinit var placesClient: PlacesClient
    private lateinit var currentButton: Button
    private lateinit var responseView: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_current)

        // Retrieve a PlacesClient (previously initialized - see DemoApplication)
        placesClient = Places.createClient(this)

        // Set view objects
        currentButton = findViewById(R.id.current_button)
        responseView = findViewById(R.id.current_response_content)

        // Set listener for initiating Current Place
        currentButton.setOnClickListener {
            checkPermissionThenFindCurrentPlace()
        }
    }
}

Esse código associa a atividade às visualizações que você definiu no arquivo de layout. Ele também adiciona um listener de clique ao botão para chamar a função checkPermissionThenFindCurrentPlace quando o botão é clicado.

  1. Defina checkPermissionThenFindCurrentPlace() para verificar a permissão de localização exata e solicite a permissão, se ela ainda não tiver sido concedida. Cole este código após a função onCreate.
    /**
     * Checks that the user has granted permission for fine or coarse location.
     * If granted, finds current Place.
     * If not yet granted, launches the permission request.
     * See https://developer.android.com/training/permissions/requesting
     */
    private fun checkPermissionThenFindCurrentPlace() {
        when {
            (ContextCompat.checkSelfPermission(
                this,
                ACCESS_FINE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(
                this,
                ACCESS_COARSE_LOCATION
            ) == PackageManager.PERMISSION_GRANTED) -> {
                // You can use the API that requires the permission.
                findCurrentPlace()
            }

            shouldShowRequestPermissionRationale(ACCESS_FINE_LOCATION)
            -> {
                Log.d(TAG, "Showing permission rationale dialog")
                // TODO: In an educational UI, explain to the user why your app requires this
                // permission for a specific feature to behave as expected. In this UI,
                // include a "cancel" or "no thanks" button that allows the user to
                // continue using your app without granting the permission.
            }

            else -> {
                // Ask for both the ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions.
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf(
                        ACCESS_FINE_LOCATION,
                        ACCESS_COARSE_LOCATION
                    ),
                    PERMISSION_REQUEST_CODE
                )
            }
        }
    }

    companion object {
        private const val TAG = "CurrentPlaceActivity"
        private const val PERMISSION_REQUEST_CODE = 9
    }
  1. Quando a ramificação else da função checkPermissionThenFindCurrentPlace chama requestPermissions, o app apresenta uma caixa de diálogo de solicitação de permissão ao usuário. Quando o usuário executa um dispositivo com um SO anterior ao Android 12, ele só pode conceder permissões de localização precisas. Quando o usuário usa um dispositivo com o Android 12 ou mais recente, ele pode informar uma localização aproximada, em vez de um lugar exato, como mostrado na Figura 4.

Solicitar permissão do usuário em um dispositivo com o Android 12 ou mais recente

Figura 4. Solicitar permissão do usuário em um dispositivo com o Android 12 ou mais recente permite conceder uma localização aproximada ou precisa.

Depois que o usuário responde à caixa de diálogo de permissões, o sistema invoca a implementação do app de onRequestPermissionsResult. O sistema transmite a resposta do usuário à caixa de diálogo de permissão, além do código de solicitação definido. Substitua onRequestPermissionResult para processar o código de solicitação das permissões de localização relacionadas a essa atividade do local atual colando o seguinte código abaixo de checkPermissionThenFindCurrentPlace.

    @SuppressLint("MissingPermission")
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>, grantResults: IntArray
    ) {
        if (requestCode != PERMISSION_REQUEST_CODE) {
            super.onRequestPermissionsResult(
                requestCode,
                permissions,
                grantResults
            )
            return
        } else if (
            permissions.toList().zip(grantResults.toList())
                .firstOrNull { (permission, grantResult) ->
                    grantResult == PackageManager.PERMISSION_GRANTED && (permission == ACCESS_FINE_LOCATION || permission == ACCESS_COARSE_LOCATION)
                } != null
        )
        // At least one location permission has been granted, so proceed with Find Current Place
        findCurrentPlace()
    }
  1. Quando a permissão for concedida, a função findCurrentPlace vai ser executada. Defina a função com esse código após a função onRequestPermissionsResult.
    /**
     * Fetches a list of [PlaceLikelihood] instances that represent the Places the user is
     * most likely to be at currently.
     */
    @RequiresPermission(anyOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
    private fun findCurrentPlace() {
        // Use fields to define the data types to return.
        val placeFields: List<Place.Field> =
            listOf(Place.Field.NAME, Place.Field.ID, Place.Field.ADDRESS, Place.Field.LAT_LNG)

        // Call findCurrentPlace and handle the response (first check that the user has granted permission).
        if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) ==
            PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, ACCESS_COARSE_LOCATION) ==
            PackageManager.PERMISSION_GRANTED
        ) {
            // Retrieve likely places based on the device's current location
            currentButton.isEnabled = false
            lifecycleScope.launch {
                val response = placesClient.awaitFindCurrentPlace(placeFields)

                responseView.text = response.prettyPrint()

                // Enable scrolling on the long list of likely places
                val movementMethod = ScrollingMovementMethod()
                responseView.movementMethod = movementMethod
            }
        } else {
            Log.d(TAG, "LOCATION permission not granted")
            checkPermissionThenFindCurrentPlace()
        }
    }

Esse código define quais campos vão ser solicitados para os possíveis lugares, cria um FindCurrentPlaceRequest, inicia a tarefa e preenche o TextView com os detalhes solicitados.

Adicionar a atividade do local atual ao manifesto

Adicione um elemento <activity> para o CurrentPlaceActivity como filho do elemento <application> no arquivo AndroidManifest.xml, localizado em app/src/main:

        <activity android:name=".CurrentPlaceActivity" android:label="@string/current_demo_title" />

Adicionar a atividade do local atual ao menu de demonstração

Assim como antes, adicione a demonstração do local atual à tela inicial anexando-a à lista no módulo Demo. Agora que você criou uma atividade do local atual, adicione-a ao arquivo Demo.kt na pasta src/main/java/com/google/codelabs/maps/placesdemo/. Cole este código imediatamente após o item AUTOCOMPLETE_FRAGMENT_DEMO:

    CURRENT_FRAGMENT_DEMO(
        R.string.current_demo_title,
        R.string.current_demo_description,
        CurrentPlaceActivity::class.java
    ),

As strings associadas são definidas no arquivo src/main/res/values/strings.xml.

Executar o app

  1. Execute o app. Desta vez, você vai ver três itens na lista da tela inicial.
  2. Toque na linha do local atual. Você vai ver um botão na tela.
  3. Toque no botão. Se você nunca concedeu permissão de localização a este app, uma solicitação de permissão vai ser exibida.
  4. Conceda permissão ao app para acessar a localização do dispositivo.
  5. Toque no botão novamente. Desta vez, uma lista de até 20 lugares por perto e as probabilidades deles serão exibidas, como mostrado na Figura 5.

Apresentação de possíveis correspondências com locais atuais para o local informado pelo dispositivo

Figura 5. Apresentação de possíveis correspondências com locais atuais para o local informado pelo dispositivo.

11. Mostrar o lugar atual em um mapa

Adicionar a dependência do mapa

No arquivo build.gradle no nível do módulo, adicione a dependência do Google Play Services no SDK do Maps para Android.

app/build.gradle

dependencies {
    // ...
    implementation 'com.google.android.gms:play-services-maps:18.2.0'
}

Atualizar o manifesto do Android para incluir mapas

Adicione os seguintes elementos meta-data ao elemento application.

Eles incorporam a versão do Google Play Services com que o app foi compilado e especificam sua chave de API.

AndroidManifest.xml

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="${MAPS_API_KEY}" />

Adicione a chave de API a secrets.properties

Abra o arquivo secrets.properties no diretório de nível superior e adicione o código a seguir. Substitua YOUR_API_KEY pela sua chave de API.

MAPS_API_KEY=YOUR_API_KEY

Abra o arquivo local.defaults.properties no diretório de nível superior, na mesma pasta que o arquivo secrets.properties, e adicione o seguinte código.

MAPS_API_KEY=DEFAULT_API_KEY

Verificar a chave de API

Em onCreate(), o app vai verificar a chave de API do Maps e inicializar o fragmento de suporte do Maps. getMapAsync() é usado para registrar o callback do mapa.

Adicione o código a seguir para fazer isso.

CurrentPlaceActivity.kt

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_current)

        val apiKey = BuildConfig.MAPS_API_KEY

        // Log an error if apiKey is not set.
        if (apiKey.isEmpty() || apiKey == "DEFAULT_API_KEY") {
            Log.e("Places test", "No api key")
            finish()
            return
        }

        // Retrieve a PlacesClient (previously initialized - see DemoApplication)
        placesClient = Places.createClient(this)
        (supportFragmentManager
            .findFragmentById(R.id.map) as SupportMapFragment?)?.getMapAsync(this)

        // ...
    }

Criar o layout do mapa

  1. Na pasta app/src/main/res/layout/, crie o arquivo de layout fragment_map.xml e preencha-o com o seguinte código.

res/layout/fragment_map.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    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="wrap_content"
    tools:context="com.google.codelabs.maps.placesdemo.CurrentPlaceActivity" />

Isso define um SupportMapFragment para atuar como um contêiner do mapa e dar acesso ao objeto GoogleMap.

  1. No layout activity_current.xml disponível na pasta app/src/main/res/layout/, adicione o seguinte código à parte de baixo do layout linear.

res/layout/activity_current.xml

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="16dp"
        android:paddingBottom="16dp"
        android:text="@string/showing_most_likely_place"
        style="@style/TextAppearance.AppCompat.Title"/>

    <include layout="@layout/fragment_map"/>

O TextView adicionado faz referência a um novo recurso de string que precisa ser criado.

  1. Em app/src/main/res/values/strings.xml, adicione o seguinte recurso de string.

res/values/strings.xml

<string name="showing_most_likely_place">Showing most likely place</string>

Como outras visualizações foram adicionadas ao mapa, a TextView que mostra a lista de lugares precisa ter a altura definida para que essas visualizações permaneçam visíveis.

  1. Adicione o atributo maxHeight ao TextView com ID current_response_content

res/layout/activity_current.xml

android:maxHeight="200dp"

Implementar o OnMapReadyCallback

Implemente a interface OnMapReadyCallback adicionando-a à declaração de classe e substitua o método onMapReady() para configurar o mapa quando o objeto GoogleMap estiver disponível:

CurrentPlaceActivity.kt

class CurrentPlaceActivity : AppCompatActivity(), OnMapReadyCallback {

No final da classe, adicione o seguinte código:

CurrentPlaceActivity.kt

    override fun onMapReady(map: GoogleMap) {
        this.map = map
        lastKnownLocation?.let { location ->
            map.moveCamera(
                CameraUpdateFactory.newLatLngZoom(
                    location,
                    DEFAULT_ZOOM
                )
            )
        }
    }

O callback exige algumas variáveis de classe para funcionar corretamente. Imediatamente após o cabeçalho da classe, adicione o seguinte:

CurrentPlaceActivity.kt

private var map: GoogleMap? = null
private var lastKnownLocation: LatLng? = null

Adicione o seguinte código ao objeto complementar da classe:

CurrentPlaceActivity.kt

private const val DEFAULT_ZOOM = 15f

Executar o app

  1. Execute o app.
  2. Toque na linha do local atual. Você vai ver um botão na tela.
  3. Toque no botão. Se você nunca concedeu permissão de localização a este app, uma solicitação de permissão vai ser exibida.
  4. Conceda permissão ao app para acessar a localização do dispositivo.
  5. Toque no botão novamente. O mapa vai aparecer.

Atividade do local atual mostrando o mapa

Figura 6. Atividade do local atual mostrando o mapa.

Atualizar o mapa com um lugar

No final da classe, adicione o seguinte código:

CurrentPlaceActivity.kt

private data class LikelyPlace(
    val name: String,
    val address: String,
    val attribution: List<String>,
    val latLng: LatLng
)

private fun PlaceLikelihood.toLikelyPlace(): LikelyPlace? {
    val name = this.place.name
    val address = this.place.address
    val latLng = this.place.latLng
    val attributions = this.place.attributions ?: emptyList()

    return if (name != null && address != null && latLng != null) {
        LikelyPlace(name, address, attributions, latLng)
    } else {
        null
    }
}

São usados para armazenar e formatar os dados do lugar.

No início da classe, adicione o seguinte código para criar uma variável usada para armazenar os dados do lugar retornado.

CurrentPlaceActivity.kt

private val likelyPlaces = mutableListOf<LikelyPlace>()

Nesta etapa , o código será alterado para que o usuário receba uma lista de lugares e escolha um para mostrar no mapa. Todos os dados do Google Maps são mostrados em uma lista na tela.

Na função findCurrentPlace, no bloco lifecycleScope.launch antes desta linha de código

CurrentPlaceActivity.kt

responseView.text = response.prettyPrint()

Adicione o seguinte código:

CurrentPlaceActivity.kt

                likelyPlaces.clear()

                likelyPlaces.addAll(
                    response.placeLikelihoods.take(M_MAX_ENTRIES).mapNotNull { placeLikelihood ->
                        placeLikelihood.toLikelyPlace()
                    }
                )

                openPlacesDialog()

Esse código exige uma constante para o número máximo de lugares a serem mostrados.

No objeto complementar, adicione o código dessa constante.

CurrentPlaceActivity.kt

private const val M_MAX_ENTRIES = 5

Adicione o código a seguir, que cria a caixa de diálogo para o usuário selecionar um lugar.

CurrentPlaceActivity.kt

    /**
     * Displays a form allowing the user to select a place from a list of likely places.
     */
    private fun openPlacesDialog() {
        // Ask the user to choose the place where they are now.
        val listener =
            DialogInterface.OnClickListener { _, which -> // The "which" argument contains the position of the selected item.
                val likelyPlace = likelyPlaces[which]
                lastKnownLocation = likelyPlace.latLng

                val snippet = buildString {
                    append(likelyPlace.address)
                    if (likelyPlace.attribution.isNotEmpty()) {
                        append("\n")
                        append(likelyPlace.attribution.joinToString(", "))
                    }
                }

                val place = Place.builder().apply {
                    name = likelyPlace.name
                    latLng = likelyPlace.latLng
                }.build()

                map?.clear()

                setPlaceOnMap(place, snippet)
            }

        // Display the dialog.
        AlertDialog.Builder(this)
            .setTitle(R.string.pick_place)
            .setItems(likelyPlaces.map { it.name }.toTypedArray(), listener)
            .setOnDismissListener {
                currentButton.isEnabled = true
            }
            .show()
    }

Seguindo as práticas recomendadas do Android, a caixa de diálogo faz referência a um recurso de string que precisa ser adicionado ao arquivo de recursos strings.xml localizado na pasta app/src/main/res/values/.

Adicione o seguinte a strings.xml:

res/values/strings.xml

    <string name="pick_place">Choose a place</string>

Essas funções chamam a função setPlaceOnMap, que move a câmera e coloca um marcador no local selecionado.

Adicione o seguinte código:

CurrentPlaceActivity.kt

    private fun setPlaceOnMap(place: Place?, markerSnippet: String?) {
        val latLng = place?.latLng ?: defaultLocation
        map?.moveCamera(
            CameraUpdateFactory.newLatLngZoom(
                latLng,
                DEFAULT_ZOOM
            )
        )
        map?.addMarker(
            MarkerOptions()
                .position(latLng)
                .title(place?.name)
                .snippet(markerSnippet)
        )
    }

Também é recomendável salvar e restaurar o estado dos mapas.

Para salvar o estado, substitua a função onSaveInstanceState e adicione o seguinte código:

CurrentPlaceActivity.kt

    /**
     * Saves the state of the map when the activity is paused.
     */
    override fun onSaveInstanceState(outState: Bundle) {
        outState.putParcelable(KEY_LOCATION, lastKnownLocation)
        super.onSaveInstanceState(outState)
    }

Para restaurar o estado, em onCreate, adicione o seguinte código após a chamada para setContentView:

CurrentPlaceActivity.kt

        if (savedInstanceState != null) {
            lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION)
        }

Salvar e restaurar requer uma chave, que é uma constante do objeto complementar.

No bloco de objeto complementar, adicione o seguinte:

CurrentPlaceActivity.kt

        // Key for storing activity state.
        private const val KEY_LOCATION = "location"

Executar o app

  1. Execute o app.
  2. Toque na linha do local atual. Você vai ver um botão na tela.
  3. Toque no botão. Se você nunca concedeu permissão de localização a este app, uma solicitação de permissão vai ser exibida.
  4. Conceda permissão ao app para acessar a localização do dispositivo.
  5. Toque no botão novamente.
  6. Toque em um lugar para escolher. O mapa vai ser ampliado e centralizado com um marcador no local selecionado.

Mapa com marcador no local selecionado

Figura 7. Mapa com marcador no local selecionado.

12. Parabéns

Você criou um app Android com o SDK do Places para Android.

O que você aprendeu

Qual é a próxima etapa?

  • Explore ou crie uma bifurcação do repositório android-places-demos de amostras e demonstrações do GitHub para ter mais inspiração.
  • Aprenda com mais codelabs do Kotlin para criar apps Android com a Plataforma Google Maps.
  • Ajude-nos a criar conteúdo útil respondendo à seguinte pergunta:

Quais outros codelabs você quer ver?

Visualização de dados em mapas Como personalizar o estilo dos meus mapas Como criar interações 3D em mapas

O codelab que você quer ver não está listado? Solicite-o aqui.