image


В предыдущих статьях мы создавали аккаунт разработчика для использования Huawei Mobile Services и подготавливали проект к их использованию. И использовали аналитику от Huawei вместо аналога от Google. В этой статье мы будем встраивать определение геолокации от Huawei.


Вот полный список статей из цикла:


  1. Создаём аккаунт разработчика, подключаем зависимости, подготавливаем код к внедрению. тык
  2. Встраиваем Huawei Analytics. тык
  3. Используем геолокацию от Huawei. < вы тут
  4. Huawei maps. Используем вместо Google maps для AppGallery.

Как должен выглядеть код в уже готовом проекте


Исходить будем, опять таки, из того, что у вас геолокация для гугла сделана примерно так:


1) Для проверки разрешения пользователя на доступ к его местоположению использована библиотека RxPermissions примерно так:


class PermissionsHelper {

    private var rxPermissions: RxPermissions? = null

    /**
     * Вызываем в Activity#onCreate
     */
    fun attach(activity: FragmentActivity) {
        rxPermissions = RxPermissions(activity)
    }

    /**
     * Вызываем в Activity#onDestroy
     */
    fun detach() {
        rxPermissions = null
    }

    fun requestPermission(vararg permissionName: String): Single<Boolean> {
        return rxPermissions?.request(*permissionName)
            ?.firstOrError()
            ?: Single.error(
                IllegalStateException("PermissionHelper is not attached to Activity")
            )
    }
}

2) Создан свой класс для местоположения:


data class Location(
    val latitude: Double,
    val longitude: Double
) {
    companion object {
        val DEFAULT_LOCATION = Location(59.927752, 30.346944)
    }
}

3) Создана абстракция над поставщиком местоположения:


interface FusedLocationClient {

    fun checkPermissions(): Single<Boolean>

    fun getLastLocation(): Single<Location>

    fun requestLastLocation(): Single<Location>
}

4) И используется она примерно так:


class LocationGateway(
    private val fusedLocationClient: FusedLocationClient
) {

    fun requestLastLocation(): Single<Location> {
        return fusedLocationClient.checkPermissions()
            .flatMap { granted ->
                if (granted) {
                    fusedLocationClient.getLastLocation()
                        .onErrorResumeNext(fusedLocationClient.requestLastLocation())
                } else {
                    Single.just(Location.DEFAULT_LOCATION) // или ошибку кидаем какую-то
                }
            }
    }
}

Используем разные реализации определения геолокации


Если вышеописанное верно для вашего случая, то как и в случае с аналитикой нам понадобятся 2 разные реализации FusedLocationClientFusedLocationClientImpl:


1) В папке src/huawei/kotlin/com/example:


class FusedLocationClientImpl(
    private val permissionsHelper: PermissionsHelper,
    context: Context
) : FusedLocationClient {

    private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)

    override fun checkPermissions(): Single<Boolean> {
        val permissions = mutableListOf(Manifest.permission.ACCESS_FINE_LOCATION)
        // Add this permission too after API=28 if you want to receive location in background
        // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
        //     permissions += Manifest.permission.ACCESS_BACKGROUND_LOCATION
        // }
        return permissionsHelper.requestPermission(*permissions.toTypedArray())
    }

    override fun getLastLocation(): Single<Location> {
        return Single.create { singleEmitter ->
            fusedLocationClient.lastLocation
                .addOnFailureListener {
                    if (singleEmitter.isDisposed) return@addOnFailureListener

                    singleEmitter.onError(it)
                }
                .addOnSuccessListener { newLocation ->
                    if (singleEmitter.isDisposed) return@addOnSuccessListener

                    if (newLocation == null) {
                        singleEmitter.onError(UnknownLocationException())
                    } else {
                        singleEmitter.onSuccess(
                            Location(
                                newLocation.latitude,
                                newLocation.longitude
                            )
                        )
                    }
                }
        }
    }

    override fun requestLastLocation(): Single<Location> {
        return Single.create { singleEmitter ->

            val locationRequest = LocationRequest.create()
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
                .setInterval(5000)
                .setSmallestDisplacement(5.5F)
                .setNumUpdates(1)

            val callback = object : LocationCallback() {
                override fun onLocationResult(result: LocationResult) {
                    if (singleEmitter.isDisposed) return

                    singleEmitter.onSuccess(
                        Location(
                            result.lastLocation.latitude,
                            result.lastLocation.longitude
                        )
                    )
                }
            }

            fusedLocationClient.requestLocationUpdates(locationRequest, callback, null)

            singleEmitter.setCancellable {
                fusedLocationClient.removeLocationUpdates(callback)
            }
        }
    }
}

2) В папке src/google/kotlin/com/example:


class FusedLocationClientImpl(
    private val permissionsHelper: PermissionsHelper,
    context: Context
) : FusedLocationClient {

    private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)

    override fun checkPermissions(): Single<Boolean> {
        val permissions = mutableListOf(Manifest.permission.ACCESS_FINE_LOCATION)
        // Add this permission too after API=28 if you want to receive location in background
        // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
        //     permissions += Manifest.permission.ACCESS_BACKGROUND_LOCATION
        // }
        return permissionsHelper.requestPermission(*permissions.toTypedArray())
    }

    @SuppressLint("MissingPermission")
    override fun getLastLocation(): Single<Location> {
        return Single.create { singleEmitter ->
            fusedLocationClient.lastLocation
                .addOnFailureListener {
                    if (singleEmitter.isDisposed) return@addOnFailureListener

                    singleEmitter.onError(it)
                }
                .addOnSuccessListener { newLocation ->
                    if (singleEmitter.isDisposed) return@addOnSuccessListener

                    if (newLocation == null) {
                        singleEmitter.onError(UnknownLocationException())
                    } else {
                        singleEmitter.onSuccess(
                            Location(
                                newLocation.latitude,
                                newLocation.longitude
                            )
                        )
                    }
                }
        }
    }

    @SuppressLint("MissingPermission")
    override fun requestLastLocation(): Single<Location> {
        return Single.create { singleEmitter ->

            val locationRequest = LocationRequest.create()
                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
                .setInterval(5000)
                .setSmallestDisplacement(5.5F)
                .setNumUpdates(1)

            val callback = object : LocationCallback() {
                override fun onLocationResult(result: LocationResult) {
                    if (singleEmitter.isDisposed) return

                    singleEmitter.onSuccess(
                        Location(
                            result.lastLocation.latitude,
                            result.lastLocation.longitude
                        )
                    )
                }
            }

            fusedLocationClient.requestLocationUpdates(locationRequest, callback, null)

            singleEmitter.setCancellable {
                fusedLocationClient.removeLocationUpdates(callback)
            }
        }
    }
}

В итоге реализации отличаются только импортами)


Аналогично с аналитикой, в DI биндим для типа FusedLocationClient экземпляр FusedLocationClientImpl. Для разных сборок будет взята та или иная реализация.
Ну и не забываем, конечно, зависимости в скрипте сборки прописать:


dependencies {
  huaweiImplementation 'com.huawei.agconnect:agconnect-core:1.3.1.300'
  huaweiImplementation 'com.huawei.hms:location:5.0.0.301'

  googleImplementation 'com.google.android.gms:play-services-location:17.0.0'
}

И не забудьте добавить разрешение на доступ к местоположению в фоне если в приложении планируется такой вариант использования сервиса! Если такое разрешение уже есть в файле AndroidManifest.xml — то можете этот пункт пропустить. Если нет — добавьте его:


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example">

    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>

Подводные камни


Надо иметь в виду, что геолокация от Huawei будет работать при следующих условиях:


  1. У вас установлены Huawei Mobile Services на девайсе.
  2. Им выданы нужные разрешения.

Дальше — встраиваем карты


С геолокацией мы разобрались, в следующей статье покажем как встроить карты от Huawei в приложение, которое уже использует аналог от Google.


Весь код, который есть в этом цикле статей вы можете посмотреть в репозитории на GitHub. Вот ссылка: https://github.com/MobileUpLLC/huawei_and_google_services.