Play Services для Kotlin: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Play Services для Kotlin: полное руководство с примерами

Google Play Services — это мощный набор сервисов и API от Google, предоставляющий разработчикам Android доступ к ключевым функциям платформы. Сюда входят: геолокация, карты (Maps), авторизация (Auth), push-уведомления (Firebase Cloud Messaging), аналитика, платежи, работа с камерой и AR, синхронизация игр (Play Games) и многое другое. Библиотека Play Services является обязательной для большинства современных Android-приложений, так как она берёт на себя рутинные задачи по взаимодействию с системными службами и обеспечивает единый, стабильный интерфейс, независимо от версии Android на устройстве.

Зачем нужна эта библиотека? Во-первых, она абстрагирует сложность работы с системными API. Вместо того чтобы писать код для каждого производителя устройств (Samsung, Xiaomi, Huawei), вы используете единый API от Google, который гарантирует совместимость. Во-вторых, Play Services обновляются автоматически через Google Play Store, что позволяет вам использовать новейшие функции без необходимости обновлять само приложение. В-третьих, библиотека оптимизирует энергопотребление и производительность: например, для геолокации используется аппаратное ускорение и пакетная обработка запросов.

Для языка Kotlin, который является официальным языком разработки Android, Play Services предоставляет полностью совместимые Kotlin-расширения (KTX). Это значит, что вы можете писать более лаконичный и безопасный код, используя корутины, лямбды и extension-функции. В этой статье мы рассмотрим основные модули Play Services, их установку, ключевые методы и напишем полноценное приложение, демонстрирующее работу с геолокацией, картами и авторизацией.

Установка

Play Services — это не отдельная библиотека, а набор модулей. Вы подключаете только те, которые нужны вашему приложению. Установка производится через систему сборки Gradle (аналог pip для Python, но для Android).

1. Откройте файл build.gradle.kts (уровень проекта) и убедитесь, что добавлен репозиторий Google:

// build.gradle.kts (Project)
allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

2. В файле build.gradle.kts (уровень модуля — app) добавьте зависимости. Пример для модулей: Maps, Location, Auth и Base (обязательный):

// build.gradle.kts (Module: app)
dependencies {
    // Базовая библиотека Play Services (обязательна)
    implementation("com.google.android.gms:play-services-base:18.5.0")
    
    // Google Maps
    implementation("com.google.android.gms:play-services-maps:19.0.0")
    
    // Геолокация
    implementation("com.google.android.gms:play-services-location:21.3.0")
    
    // Авторизация (Google Sign-In)
    implementation("com.google.android.gms:play-services-auth:21.2.0")
    
    // Kotlin-расширения (KTX) для корутин
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
}

3. Синхронизируйте проект (Sync Now). После этого все API станут доступны в коде.

Важно: Версии библиотек постоянно обновляются. Рекомендуется проверять актуальные версии на официальной странице релизов.

Быстрый старт: минимальный пример

Создадим простое приложение, которое получает текущие координаты устройства и выводит их на экран. Для этого нам понадобится модуль play-services-location.

1. Добавьте разрешения в AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

2. Код Activity (Kotlin):

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices

class MainActivity : AppCompatActivity() {

    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private lateinit var locationText: TextView

    // Регистрируем обработчик запроса разрешений
    private val requestPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        val fineGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: false
        val coarseGranted = permissions[Manifest.permission.ACCESS_COARSE_LOCATION] ?: false
        if (fineGranted || coarseGranted) {
            getLastLocation()
        } else {
            Toast.makeText(this, "Разрешение не получено", Toast.LENGTH_SHORT).show()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        locationText = findViewById(R.id.location_text)
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

        // Проверяем и запрашиваем разрешения
        when {
            ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
                PackageManager.PERMISSION_GRANTED -> {
                getLastLocation()
            }
            else -> {
                requestPermissionLauncher.launch(
                    arrayOf(
                        Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.ACCESS_COARSE_LOCATION
                    )
                )
            }
        }
    }

    @SuppressLint("MissingPermission")
    private fun getLastLocation() {
        fusedLocationClient.lastLocation.addOnSuccessListener { location ->
            if (location != null) {
                locationText.text = "Широта: ${location.latitude}\nДолгота: ${location.longitude}"
            } else {
                locationText.text = "Местоположение недоступно"
            }
        }.addOnFailureListener { e ->
            locationText.text = "Ошибка: ${e.message}"
        }
    }
}

Этот минимальный пример показывает, как запросить разрешение, получить клиент геолокации и выполнить асинхронный запрос на получение последнего известного местоположения.

Основные методы, функции и классы

Рассмотрим 8 ключевых классов/методов из разных модулей Play Services. Для каждого приведена сигнатура (упрощённая), описание и пример.

1. FusedLocationProviderClient.lastLocation

Сигнатура: suspend fun lastLocation: Task<Location?> (через Task API) или suspend fun awaitLastLocation(): Location? (через KTX)

Описание: Возвращает последнее известное местоположение устройства. Это самый быстрый способ получить координаты, но точность может быть низкой, если данные устарели.

// Использование с корутинами (play-services-location-ktx)
lifecycleScope.launch {
    val location = fusedLocationClient.awaitLastLocation()
    if (location != null) {
        println("Lat: ${location.latitude}, Lng: ${location.longitude}")
    }
}

2. FusedLocationProviderClient.requestLocationUpdates

Сигнатура: fun requestLocationUpdates(request: LocationRequest, callback: LocationCallback, looper: Looper?): Task<Void>

Описание: Запускает периодическое получение обновлений геолокации с заданными параметрами (интервал, приоритет). Используется для навигации или трекинга.

val locationRequest = LocationRequest.Builder(
    Priority.PRIORITY_HIGH_ACCURACY, 5000 // 5 секунд
).apply {
    setMinUpdateIntervalMillis(2000)
    setMaxUpdateDelayMillis(10000)
}.build()

val locationCallback = object : LocationCallback() {
    override fun onLocationResult(result: LocationResult) {
        for (location in result.locations) {
            println("Новое местоположение: ${location.latitude}, ${location.longitude}")
        }
    }
}

fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, mainLooper)

3. GoogleSignInClient.silentSignIn

Сигнатура: fun silentSignIn(): Task<GoogleSignInAccount>

Описание: Пытается выполнить "тихий" вход в аккаунт Google без показа UI. Если пользователь уже авторизован, возвращает аккаунт. Иначе — исключение.

val signInClient = GoogleSignIn.getClient(this, GoogleSignInOptions.DEFAULT_SIGN_IN)

lifecycleScope.launch {
    try {
        val account = signInClient.silentSignIn().await()
        println("Успешный вход: ${account.email}")
    } catch (e: ApiException) {
        println("Тихий вход не удался: ${e.statusCode}")
        // Показать UI входа
    }
}

4. GoogleSignInClient.signOut

Сигнатура: fun signOut(): Task<Void>

Описание: Выполняет выход из аккаунта Google. После вызова silentSignIn вернёт ошибку.

signInClient.signOut().addOnCompleteListener {
    println("Выход выполнен")
}

5. GoogleMap.addMarker

Сигнатура: fun addMarker(options: MarkerOptions): Marker

Описание: Добавляет маркер на карту Google Maps. Параметры: позиция, заголовок, иконка и т.д.

// Предполагаем, что mapFragment уже загружен
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync { googleMap ->
    val sydney = LatLng(-33.852, 151.211)
    googleMap.addMarker(
        MarkerOptions()
            .position(sydney)
            .title("Сидней")
            .snippet("Самый большой город Австралии")
    )
    googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 15f))
}

6. GoogleMap.setOnMarkerClickListener

Сигнатура: fun setOnMarkerClickListener(listener: GoogleMap.OnMarkerClickListener?)

Описание: Устанавливает слушатель кликов по маркерам. Возвращает true, если событие обработано.

googleMap.setOnMarkerClickListener { marker ->
    Toast.makeText(this, "Нажат маркер: ${marker.title}", Toast.LENGTH_SHORT).show()
    true // Событие обработано, стандартное поведение (инфо-окно) не показывается
}

7. PlacesClient.findCurrentPlace

Сигнатура: fun findCurrentPlace(request: FindCurrentPlaceRequest): Task<FindCurrentPlaceResponse>

Описание: Определяет текущее место (Place), где находится устройство, на основе данных геолокации и базы Google. Возвращает список вероятных мест.

val placesClient = Places.createClient(this)
val request = FindCurrentPlaceRequest.builder(
    listOf(Place.Field.NAME, Place.Field.ADDRESS, Place.Field.LAT_LNG)
).build()

if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) ==
    PackageManager.PERMISSION_GRANTED) {
    placesClient.findCurrentPlace(request).addOnSuccessListener { response ->
        for (placeLikelihood in response.placeLikelihoods) {
            println("Место: ${placeLikelihood.place.name}, Вероятность: ${placeLikelihood.likelihood}")
        }
    }
}

8. Task.await() (из KTX)

Сигнатура: suspend fun <T> Task<T>.await(): T

Описание: Kotlin-расширение, которое преобразует асинхронный Task в корутину. Позволяет писать последовательный код без колбэков.

// Без await (колбэки)
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
    // обработка
}

// С await (корутины)
lifecycleScope.launch {
    val location = fusedLocationClient.lastLocation.await()
    // обработка
}

Реальный практический пример (150+ строк)

Создадим приложение "Мой трекер", которое отображает текущее местоположение на карте, позволяет ставить метки и показывает список последних координат. Используем модули: Maps, Location, а также корутины.

Шаг 1. AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="ВАШ_API_КЛЮЧ_ДЛЯ_MAPS" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Шаг 2. activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <fragment
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match                    

Рекомендаций