Pierwsze kroki z pakietem Places SDK na Androida (Kotlin)

1. Zanim zaczniesz

W tym laboratorium dowiesz się, jak zintegrować pakiet SDK Miejsc na Androida z aplikacją i korzystać z poszczególnych funkcji tego pakietu.

Aplikacja demonstracyjna Miejsc

Wymagania wstępne

  • Podstawowa wiedza o Kotlinie i programowaniu na Androida

Czego się nauczysz

  • Jak zainstalować pakiet SDK Miejsc na Androida z rozszerzeniami Kotlin.
  • Jak wczytać szczegóły konkretnego miejsca.
  • Jak dodać do aplikacji widżet autouzupełniania miejsc.
  • Jak wczytać bieżące miejsce na podstawie aktualnej lokalizacji urządzenia.

Czego potrzebujesz

Aby ukończyć to ćwiczenie, potrzebujesz tych kont, usług i narzędzi:

2. Konfiguracja

W kroku włączania poniżej włącz interfejs Places APIpakiet SDK Map Google na Androida.

Konfigurowanie Google Maps Platform

Jeśli nie masz jeszcze konta Google Cloud Platform i projektu z włączonymi płatnościami, zapoznaj się z przewodnikiem Pierwsze kroki z Google Maps Platform, aby utworzyć konto rozliczeniowe i projekt.

  1. W konsoli Google Cloud kliknij menu projektu i wybierz projekt, którego chcesz użyć w tym samouczku.

  1. Włącz interfejsy API i pakiety SDK Google Maps Platform wymagane w tym samouczku w Google Cloud Marketplace. Aby to zrobić, wykonaj czynności opisane w tym filmie lub tej dokumentacji.
  2. Wygeneruj klucz interfejsu API na stronie Dane logowania w konsoli Cloud. Możesz wykonać czynności opisane w tym filmie lub tej dokumentacji. Wszystkie żądania wysyłane do Google Maps Platform wymagają klucza interfejsu API.

3. Szybki start

Aby jak najszybciej rozpocząć pracę, pobierz kod początkowy, który pomoże Ci wykonać to ćwiczenie z programowania. Możesz przejść od razu do rozwiązania, ale jeśli chcesz wykonać wszystkie czynności, aby samodzielnie je utworzyć, czytaj dalej.

  1. Sklonuj repozytorium, jeśli masz zainstalowany program git.
git clone https://github.com/googlemaps/codelab-places-101-android-kotlin.git

Możesz też kliknąć ten przycisk, aby pobrać kod źródłowy.

  1. Po pobraniu kodu otwórz projekt znajdujący się w katalogu /starter w Android Studio. Ten projekt zawiera podstawową strukturę plików, która będzie potrzebna do ukończenia ćwiczenia. Wszystko, czego potrzebujesz do pracy, znajduje się w katalogu /starter.

Jeśli chcesz zobaczyć działający pełny kod rozwiązania, możesz wyświetlić gotowy kod w katalogu /solution.

4. Dodawanie klucza interfejsu API do projektu

W tej sekcji opisujemy, jak przechowywać klucz interfejsu API, aby aplikacja mogła się do niego bezpiecznie odwoływać. Nie należy umieszczać klucza interfejsu API w systemie kontroli wersji, dlatego zalecamy przechowywanie go w pliku secrets.properties, który zostanie umieszczony w lokalnej kopii katalogu głównego projektu. Więcej informacji o pliku secrets.properties znajdziesz w artykule Pliki właściwości Gradle.

Aby uprościć to zadanie, zalecamy użycie wtyczki Gradle obiektów tajnych na Androida.

Aby zainstalować wtyczkę Gradle obiektów tajnych na Androida w projekcie Google Maps:

  1. W Android Studio otwórz plik najwyższego poziomu build.gradle.kts lub build.gradle i dodaj ten kod do elementu dependencies w sekcji buildscript.

Jeśli używasz build.gradle.kts, dodaj:

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

Jeśli używasz build.gradle, dodaj:

buildscript {
    dependencies {
        classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
    }
}
  1. Otwórz plik build.gradle.kts lub build.gradle na poziomie modułu i dodaj ten kod do elementu plugins.

Jeśli używasz build.gradle.kts, dodaj:

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

Jeśli używasz build.gradle, dodaj:

plugins {
    // ...
    id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
  1. W pliku build.gradle.kts lub build.gradle na poziomie modułu sprawdź, czy wartości targetSdkcompileSdk są ustawione na 34.
  2. Zapisz plik i zsynchronizuj projekt z Gradle.
  3. Otwórz plik secrets.properties w katalogu najwyższego poziomu i dodaj ten kod: Zastąp YOUR_API_KEY swoim kluczem interfejsu API. Przechowuj klucz w tym pliku, ponieważ secrets.properties jest wykluczony z systemu kontroli wersji.
PLACES_API_KEY=YOUR_API_KEY
  1. Zapisz plik.
  2. Utwórz plik local.defaults.properties w katalogu najwyższego poziomu, tym samym, w którym znajduje się plik secrets.properties, a następnie dodaj ten kod.
PLACES_API_KEY=DEFAULT_API_KEY

Ten plik służy jako lokalizacja kopii zapasowej klucza interfejsu API, jeśli plik secrets.properties nie zostanie znaleziony, aby kompilacje nie kończyły się niepowodzeniem. Może się to zdarzyć, jeśli sklonujesz aplikację z systemu kontroli wersji, który pomija plik secrets.properties, a nie utworzysz jeszcze lokalnie pliku secrets.properties, aby podać klucz interfejsu API.

  1. Zapisz plik.
  2. W Android Studio otwórz plik build.gradle.kts lub build.gradle na poziomie modułu i zmień właściwość secrets. Jeśli właściwość secrets nie istnieje, dodaj ją.

Edytuj właściwości wtyczki, aby ustawić propertiesFileName na secrets.properties, defaultPropertiesFileName na local.defaults.properties i inne właściwości.

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. Instalowanie pakietu SDK Miejsc na Androida

W tej sekcji dodasz pakiet SDK Miejsc na Androida do zależności aplikacji.

  1. Teraz, gdy klucz interfejsu API jest dostępny w aplikacji, dodaj zależność pakietu Places SDK na Androida do pliku build.gradle aplikacji.

Zmodyfikuj plik build.gradle na poziomie aplikacji, aby dodać zależność pakietu SDK Miejsc na Androida:

build.gradle na poziomie aplikacji

dependencies {
   // Dependency to include Places SDK for Android
   implementation 'com.google.android.libraries.places:places:3.4.0'
}
  1. Uruchom aplikację.

Powinna być widoczna aplikacja z pustym ekranem. Wypełnij ten ekran 3 wersjami demonstracyjnymi.

6. Instalowanie biblioteki Places Android KTX

W przypadku aplikacji w Kotlinie, które korzystają z co najmniej jednego pakietu SDK Google Maps Platform na Androida, biblioteki rozszerzeń Kotlin (KTX) umożliwiają korzystanie z funkcji języka Kotlin, takich jak korutyny, właściwości i funkcje rozszerzeń itp. Każdy pakiet SDK Map Google ma odpowiednią bibliotekę KTX, jak pokazano poniżej:

Diagram KTX Google Maps Platform

W tym zadaniu użyjesz biblioteki Places Android KTX, aby w aplikacji korzystać z funkcji języka Kotlin.

Dodawanie zależności Places Android KTX

Aby korzystać z funkcji specyficznych dla języka Kotlin, w pliku build.gradle na poziomie aplikacji uwzględnij odpowiednią bibliotekę KTX dla tego pakietu SDK.

build.gradle

dependencies {
    // ...

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

7. Inicjowanie klienta Places

Inicjowanie pakietu SDK Miejsc w zakresie aplikacji

W pliku DemoApplication.kt w folderze app/src/main/java/com/google/codelabs/maps/placesdemo zainicjuj pakiet SDK Miejsc na Androida. Na końcu funkcji onCreate wklej te wiersze:

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

Gdy tworzysz aplikację, wtyczka Gradle obiektów tajnych na Androida udostępnia klucz interfejsu API w pliku secrets.properties jako BuildConfig.PLACES_API_KEY.

Dodawanie pliku aplikacji do manifestu

Ponieważ masz rozszerzenie ApplicationDemoApplication, musisz zaktualizować plik manifestu. Dodaj właściwość android:name do elementu application w pliku AndroidManifest.xml znajdującym się w app/src/main:

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

Ten kod wskazuje plik manifestu aplikacji na klasę DemoApplication w folderze src/main/java/com/google/codelabs/maps/placesdemo/.

8. Pobieranie szczegółów miejsca

Tworzenie ekranu szczegółów

Układ activity_details.xml z pustym LinearLayout jest dostępny w folderze app/src/main/res/layout/. Wypełnij układ liniowy, dodając ten kod między nawiasami <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" />

Ten kod dodaje pole wprowadzania tekstu, w którym użytkownik może wpisać dowolny identyfikator miejsca lub użyć podanej wartości domyślnej, przycisk do zainicjowania żądania szczegółów miejsca oraz element TextView do wyświetlania informacji z odpowiedzi. Powiązane ciągi znaków są zdefiniowane w pliku src/main/res/values/strings.xml.

Tworzenie aktywności Szczegóły

  1. Utwórz plik DetailsActivity.kt w folderze src/main/java/com/google/codelabs/maps/placesdemo/ i powiąż go z utworzonym przed chwilą układem. Wklej do pliku ten kod:
@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. Utwórz klienta Miejsc do użycia w tej aktywności. Wklej ten kod po kodzie, który sprawdza klucz interfejsu API w funkcji onCreate.
        // Retrieve a PlacesClient (previously initialized - see DemoApplication)
        placesClient = Places.createClient(this)
  1. Po skonfigurowaniu klienta Places dołącz do przycisku odbiornik kliknięć. Wklej ten kod po utworzeniu klienta Places w funkcji 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
            }
        }

Ten kod pobiera identyfikator miejsca wpisany w polu wejściowym, określa pola, o które należy poprosić w przypadku miejsca, tworzy FetchPlaceRequest, inicjuje zadanie i nasłuchuje, czy zakończy się ono sukcesem, czy nie. Jeśli żądanie zostanie wykonane, funkcja wypełni element TextView żądanymi szczegółami.

Dodawanie aktywności Szczegóły do pliku manifestu

Dodaj element <activity> dla DetailsActivity jako element podrzędny elementu <application> w pliku AndroidManifest.xml znajdującym się w app/src/main:

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

Dodawanie aktywności Szczegóły do menu wersji demonstracyjnej

Pusty moduł Demo służy do wyświetlania listy dostępnych wersji demonstracyjnych na ekranie głównym. Po utworzeniu działania Place Details dodaj je do pliku Demo.kt w folderze src/main/java/com/google/codelabs/maps/placesdemo/ za pomocą tego kodu:

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

Powiązane ciągi znaków są zdefiniowane w pliku src/main/res/values/strings.xml.

Sprawdź MainActivity.kt i zobacz, że tworzy on element ListView, który jest wypełniany przez iterację zawartości modułu Demo. Jeśli użytkownik kliknie element na liście, detektor kliknięć otworzy powiązaną aktywność.

Uruchamianie aplikacji

  1. Uruchom aplikację. Tym razem na liście powinien pojawić się jeden element przedstawiający wersję demonstracyjną szczegółów miejsca.
  2. Kliknij tekst Szczegóły miejsca. Powinien wyświetlić się utworzony widok z polem wprowadzania i przyciskiem.
  3. Kliknij przycisk „ZOBACZ SZCZEGÓŁY”. Jeśli używasz domyślnego identyfikatora miejsca, powinny się wyświetlić nazwa miejsca, adres i współrzędne mapy, jak pokazano na ilustracji 1.

Aktywność Szczegóły miejsca z odpowiedzią

Rysunek 1. Aktywność Szczegóły miejsca z wyświetloną odpowiedzią.

9. Dodawanie autouzupełniania miejsc

Tworzenie ekranu autouzupełniania

Układ activity_autocomplete.xml z pustym LinearLayout znajduje się w folderze app/src/main/res/layout/. Wypełnij układ liniowy, dodając ten kod między nawiasami <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" />

Ten kod dodaje widżet AutocompleteSupportFragment i element TextView do wyświetlania informacji z odpowiedzi. Powiązane ciągi znaków są zdefiniowane w pliku src/main/res/values/strings.xml.

Tworzenie aktywności autouzupełniania

  1. Utwórz plik AutocompleteActivity.kt w folderze src/main/java/com/google/codelabs/maps/placesdemo/ i zdefiniuj go za pomocą tego kodu:
@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
    }
}

Ten kod wiąże aktywność z widokami i AutocompleteSupportFramgent zdefiniowanymi w pliku układu.

  1. Następnie określ, co się stanie, gdy użytkownik wybierze jedną z prognoz wyświetlanych przez autouzupełnianie miejsc. Dodaj ten kod na końcu funkcji 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()
                }
            }
        }

Ten kod określa, o które pola należy poprosić w przypadku miejsca, nasłuchuje zdarzenia wyboru miejsca oraz nasłuchuje powodzenia lub niepowodzenia. Jeśli prośba zostanie rozpatrzona pozytywnie, funkcja wypełni widok TextView szczegółami miejsca. Pamiętaj, że autouzupełnianie miejsc zwraca obiekt Place. Podczas korzystania z widżetu autouzupełniania miejsc nie musisz wysyłać osobnego żądania szczegółów miejsca.

Dodawanie aktywności autouzupełniania do pliku manifestu

Dodaj element <activity> dla AutocompleteActivity jako element podrzędny elementu <application> w pliku AndroidManifest.xml znajdującym się w app/src/main:

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

Dodawanie działania Autouzupełnianie do menu demonstracyjnego

Tak jak wcześniej dodaj wersję demonstracyjną autouzupełniania miejsc do ekranu głównego, dołączając ją do listy w module Demo. Po utworzeniu aktywności autouzupełniania miejsca dodaj ją do pliku Demo.kt w folderze src/main/java/com/google/codelabs/maps/placesdemo/. Wklej ten kod bezpośrednio po elemencie DETAILS_FRAGMENT_DEMO:

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

Powiązane ciągi znaków są zdefiniowane w pliku src/main/res/values/strings.xml.

Uruchamianie aplikacji

  1. Uruchom aplikację. Tym razem na liście na ekranie głównym powinny być widoczne 2 elementy.
  2. Kliknij wiersz Autouzupełnianie miejsc. Powinno pojawić się okienko wprowadzania autouzupełniania miejsc, jak pokazano na rysunku 2.
  3. Zacznij wpisywać nazwę miejsca. Może to być nazwa placówki, adres lub region geograficzny. Podpowiedzi powinny pojawiać się podczas pisania.
  4. Wybierz jedną z prognoz. Prognozy powinny zniknąć, a w obszarze TextView powinny się teraz wyświetlać szczegóły wybranego miejsca, jak pokazano na rysunku 3.

Aktywność autouzupełniania po kliknięciu pola wprowadzania przez użytkownika

Rysunek 2. Automatyczne uzupełnianie po kliknięciu pola wprowadzania przez użytkownika.

Automatyczne uzupełnianie aktywności po wpisaniu i wybraniu przez użytkownika hasła „Niagara Falls”

Rysunek 3. Aktywność autouzupełniania wyświetlająca szczegóły miejsca po wpisaniu i wybraniu przez użytkownika hasła „Niagara Falls”.

10. Pobieranie Aktualnego miejsca urządzenia

Tworzenie ekranu bieżącego miejsca

W folderze app/src/main/res/layout/ znajduje się układ activity_current.xml z pustym LinearLayout. Wypełnij układ liniowy, dodając ten kod między nawiasami <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" />

Tworzenie aktywności Aktualne miejsce

  1. Utwórz plik CurrentPlaceActivity.kt w folderze src/main/java/com/google/codelabs/maps/placesdemo/ i zdefiniuj go za pomocą tego kodu:
@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()
        }
    }
}

Ten kod wiąże aktywność z widokami zdefiniowanymi w pliku układu. Dodaje też do przycisku detektor kliknięć, który po kliknięciu przycisku wywołuje funkcję checkPermissionThenFindCurrentPlace.

  1. Zdefiniuj checkPermissionThenFindCurrentPlace(), aby sprawdzić, czy aplikacja ma uprawnienia do dokładnej lokalizacji, i poproś o nie, jeśli nie zostały jeszcze przyznane. Wklej ten kod po funkcji 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. Gdy gałąź else funkcji checkPermissionThenFindCurrentPlace wywoła funkcję requestPermissions, aplikacja wyświetli użytkownikowi okno z prośbą o uprawnienia. Jeśli użytkownik korzysta z urządzenia z systemem operacyjnym starszym niż Android 12, może przyznać tylko uprawnienia do dokładnej lokalizacji. Jeśli użytkownik korzysta z urządzenia z Androidem 12 lub nowszym, będzie miał możliwość podania przybliżonej (ogólnej) lokalizacji zamiast dokładnej, jak pokazano na rysunku 4.

Wyświetlanie prośby o zgodę użytkownika na urządzeniu z Androidem 12 lub nowszym

Rysunek 4. Gdy użytkownik poprosi o uprawnienia na urządzeniu z Androidem 12 lub nowszym, będzie mógł przyznać dostęp do dokładnej lub przybliżonej lokalizacji.

Gdy użytkownik odpowie na okno uprawnień systemowych, system wywoła implementację onRequestPermissionsResult w Twojej aplikacji. System przekazuje odpowiedź użytkownika na okno dialogowe z prośbą o uprawnienia oraz zdefiniowany przez Ciebie kod żądania. Zastąp onRequestPermissionResult, aby obsłużyć kod żądania uprawnień do lokalizacji związanych z tą aktywnością bieżącego miejsca, wklejając poniższy kod pod 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. Gdy to zrobisz, funkcja findCurrentPlace zostanie uruchomiona. Zdefiniuj funkcję za pomocą tego kodu po funkcji 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()
        }
    }

Ten kod określa pola, o które należy poprosić w przypadku prawdopodobnych miejsc, tworzy FindCurrentPlaceRequest, inicjuje zadanie i wypełnia element TextView żądanymi szczegółami.

Dodaj aktywność Bieżące miejsce do pliku manifestu

Dodaj element <activity> dla CurrentPlaceActivity jako element podrzędny elementu <application> w pliku AndroidManifest.xml znajdującym się w app/src/main:

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

Dodaj aktywność Bieżące miejsce do menu wersji demonstracyjnej

Podobnie jak wcześniej dodaj wersję demonstracyjną bieżącego miejsca do ekranu głównego, dołączając ją do listy w module Demo. Po utworzeniu aktywności Bieżące miejsce dodaj ją do pliku Demo.kt w folderze src/main/java/com/google/codelabs/maps/placesdemo/. Wklej ten kod bezpośrednio po elemencie AUTOCOMPLETE_FRAGMENT_DEMO:

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

Powiązane ciągi znaków są zdefiniowane w pliku src/main/res/values/strings.xml.

Uruchamianie aplikacji

  1. Uruchom aplikację. Tym razem na liście na ekranie głównym powinny być widoczne 3 elementy.
  2. Kliknij wiersz Aktualne miejsce. Na ekranie powinien pojawić się przycisk.
  3. Naciśnij przycisk. Jeśli wcześniej nie przyznano tej aplikacji uprawnień do lokalizacji, powinna pojawić się prośba o uprawnienia.
  4. Zezwól aplikacji na dostęp do lokalizacji urządzenia.
  5. Ponownie kliknij przycisk. Tym razem powinna się pojawić lista maksymalnie 20 miejsc w pobliżu i ich prawdopodobieństwa, jak pokazano na rysunku 5.

wyświetlanie prawdopodobnych dopasowań do Aktualnego miejsca na podstawie zgłoszonej lokalizacji urządzenia;

Rysunek 5. Wyświetlanie prawdopodobnych dopasowań bieżącego miejsca do zgłoszonej lokalizacji urządzenia.

11. Wyświetlanie bieżącego miejsca na mapie

Dodaj zależność Mapy

W pliku build.gradle na poziomie modułu dodaj zależność Usług Google Play dla pakietu Maps SDK na Androida.

app/build.gradle

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

Zaktualizuj plik manifestu Androida, aby uwzględnić mapy

Dodaj te elementy meta-data w elemencie application.

Zawierają one wersję Usług Google Play, z którą skompilowano aplikację, i określają klucz interfejsu 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}" />

Dodaj klucz interfejsu API do secrets.properties

Otwórz plik secrets.properties w katalogu najwyższego poziomu i dodaj ten kod: Zastąp YOUR_API_KEY swoim kluczem interfejsu API.

MAPS_API_KEY=YOUR_API_KEY

Otwórz plik local.defaults.properties w katalogu najwyższego poziomu, czyli w tym samym folderze co plik secrets.properties, a następnie dodaj ten kod.

MAPS_API_KEY=DEFAULT_API_KEY

Sprawdź klucz interfejsu API

onCreate() aplikacja sprawdzi klucz interfejsu API Map Google i zainicjuje fragment obsługi map. getMapAsync() służy do rejestrowania wywołania zwrotnego z mapy.

Aby to zrobić, dodaj ten kod.

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)

        // ...
    }

Tworzenie układu mapy

  1. W folderze app/src/main/res/layout/ utwórz plik układu fragment_map.xml i wypełnij go tym kodem.

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" />

Określa element SupportMapFragment, który będzie działać jako kontener mapy i zapewni dostęp do obiektu GoogleMap.

  1. W układzie activity_current.xml dostępnym w folderze app/src/main/res/layout/ dodaj ten kod na dole układu liniowego.

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"/>

Dodany znak TextView odwołuje się do nowego zasobu w postaci ciągu znaków, który należy utworzyć.

  1. W pliku app/src/main/res/values/strings.xml dodaj ten zasób ciągu znaków.

res/values/strings.xml

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

Ponieważ do mapy dodano dodatkowe widoki, element TextView wyświetlający listę miejsc musi mieć ustawioną wysokość, aby te widoki pozostały widoczne.

  1. Dodaj atrybut maxHeight do elementu TextView o identyfikatorze current_response_content.

res/layout/activity_current.xml

android:maxHeight="200dp"

Wdróż funkcję OnMapReadyCallback.

Zaimplementuj interfejs OnMapReadyCallback, dodając go do deklaracji klasy, i zastąp metodę onMapReady(), aby skonfigurować mapę, gdy obiekt GoogleMap będzie dostępny:

CurrentPlaceActivity.kt

class CurrentPlaceActivity : AppCompatActivity(), OnMapReadyCallback {

Na końcu klasy dodaj ten kod:

CurrentPlaceActivity.kt

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

Aby działać prawidłowo, wywołanie zwrotne wymaga pewnych zmiennych klasy. Bezpośrednio po nagłówku klasy dodaj ten kod:

CurrentPlaceActivity.kt

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

Dodaj ten kod do obiektu towarzyszącego klasy:

CurrentPlaceActivity.kt

private const val DEFAULT_ZOOM = 15f

Uruchamianie aplikacji

  1. Uruchom aplikację.
  2. Kliknij wiersz Aktualne miejsce. Na ekranie powinien pojawić się przycisk.
  3. Naciśnij przycisk. Jeśli wcześniej nie przyznano tej aplikacji uprawnień do lokalizacji, powinna pojawić się prośba o uprawnienia.
  4. Zezwól aplikacji na dostęp do lokalizacji urządzenia.
  5. Ponownie kliknij przycisk. Wyświetli się mapa.

Aktywność w sekcji Bieżące miejsce z wyświetloną mapą

Rysunek 6. Aktywność w sekcji Bieżące miejsce z wyświetloną mapą.

Aktualizowanie mapy o miejsce

Na końcu klasy dodaj ten kod:

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łużą one do przechowywania i formatowania danych miejsca.

Na początku klasy dodaj ten kod, aby utworzyć zmienną służącą do przechowywania zwróconych danych o miejscu.

CurrentPlaceActivity.kt

private val likelyPlaces = mutableListOf<LikelyPlace>()

W tym kroku zmienimy kod, aby użytkownikowi wyświetlała się lista miejsc, z której będzie mógł wybrać jedno do wyświetlenia na mapie. Wszystkie dane o miejscach są wyświetlane na ekranie w postaci listy.

W funkcji findCurrentPlace w bloku lifecycleScope.launch przed tym wierszem kodu

CurrentPlaceActivity.kt

responseView.text = response.prettyPrint()

dodaj ten kod:

CurrentPlaceActivity.kt

                likelyPlaces.clear()

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

                openPlacesDialog()

Ten kod wymaga stałej wartości określającej maksymalną liczbę miejsc do wyświetlania.

W obiekcie towarzyszącym dodaj kod tej stałej.

CurrentPlaceActivity.kt

private const val M_MAX_ENTRIES = 5

Dodaj ten kod, który tworzy okno umożliwiające użytkownikowi wybranie miejsca.

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()
    }

Zgodnie ze sprawdzonymi metodami dotyczącymi Androida okno odwołuje się do zasobu w postaci ciągu znaków, który należy dodać do strings.xml pliku zasobów znajdującego się w folderze app/src/main/res/values/.

Dodać te boty do pokoju „strings.xml”?

res/values/strings.xml

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

Funkcje te wywołują następnie funkcję setPlaceOnMap, która przesuwa kamerę i umieszcza znacznik w wybranej lokalizacji.

Dodaj ten kod:

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)
        )
    }

Zalecamy też zapisywanie i przywracanie stanu map.

Aby zapisać stan, zastąp funkcję onSaveInstanceState i dodaj ten kod:

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)
    }

Aby przywrócić jego stan, w onCreate dodaj ten kod po wywołaniu setContentView:

CurrentPlaceActivity.kt

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

Zapisywanie i przywracanie wymaga klucza, który jest stałą z obiektu towarzyszącego.

W bloku obiektu towarzyszącego dodaj te elementy:

CurrentPlaceActivity.kt

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

Uruchamianie aplikacji

  1. Uruchom aplikację.
  2. Kliknij wiersz Aktualne miejsce. Na ekranie powinien pojawić się przycisk.
  3. Naciśnij przycisk. Jeśli wcześniej nie przyznano tej aplikacji uprawnień do lokalizacji, powinna pojawić się prośba o uprawnienia.
  4. Zezwól aplikacji na dostęp do lokalizacji urządzenia.
  5. Ponownie kliknij przycisk.
  6. Wybierz miejsce, klikając je. Mapa zostanie powiększona i wyśrodkowana, a w wybranej lokalizacji pojawi się znacznik.

Mapa ze znacznikiem w wybranej lokalizacji

Rysunek 7. Mapa ze znacznikiem w wybranej lokalizacji.

12. Gratulacje

Udało Ci się utworzyć aplikację na Androida za pomocą pakietu SDK Miejsc na Androida.

Czego się dowiedziałeś

Co dalej?

  • Aby znaleźć więcej inspiracji, przejrzyj lub utwórz rozwidlenie android-places-demos repozytorium GitHub z przykładami i wersjami demonstracyjnymi.
  • Skorzystaj z większej liczby ćwiczeń z programowania w Kotlinie, aby tworzyć aplikacje na Androida za pomocą Google Maps Platform.
  • Pomóż nam tworzyć treści, które będą dla Ciebie najbardziej przydatne, i odpowiedz na to pytanie:

Jakie inne codelaby chcesz zobaczyć?

Wizualizacja danych na mapach Więcej informacji o dostosowywaniu stylu map Tworzenie interakcji 3D na mapach

Nie widzisz interesującego Cię laboratorium? Zgłoś problem tutaj