Hey! Меня зовут Дмитрий Лёвочкин, я Flutter разработчик в Friflex и автор блога Дневник Flutter разработчика. Мы в Friflex занимаемся разработкой мобильных приложений, и одна из наших ключевых отраслей – ритейл. Сложно представить мобильное приложение крупного ритейлера без карты внутри. В этой статье я расскажу, какие преимущества есть у Яндекс Карт и как быстро интегрировать их в ваше приложение.
Статья состоит из двух частей:
-
Теоретическая. Плюсы и минусы использования Яндекс Карт.
-
Практическая. Напишем приложение, в котором отобразим текущее местоположение пользователя. В случае ошибки будем выводить Москву как город по умолчанию.
Если вы занимаетесь написанием приложений на Flutter, то, скорее всего, знаете, что для интеграции карт в ваше приложение есть несколько основных пакетов:
В контексте ситуации, сложившейся в нашей стране, у Яндекс карт есть ряд преимуществ:
-
Если вы планируете публиковать приложение в AppGallery, вам не нужно писать разделение для GMS (Google Mobile Services) и HMS (Huawei Mobile Services) сервисов. Яндекс Карты без проблем работают с Huawei. Если вы будете использовать Google Карты, вам потребуется разделить сервисы, так как ваше приложение не может одновременно поддерживать HMS и GMS сервисы (согласно политике Google Play).
-
В случае возникновения проблем с работой сервисов Google в РФ, ваше приложение будет стабильно работать с картами Яндекса.
-
По регионам России информация точнее, чем у Google Карт.
-
Более знакомый интерфейс для русскоязычного пользователя.
-
Не возникнет проблем с оплатой сервиса, если вы решите перейти на платный тариф.
Вы можете разделить сервисы, использовать Google Карты для публикации приложения в App Store и Google Play, а Яндекс Карты для публикации в AppGallery. Но это добавит проблем при сборке. Нужно будет каждый раз менять карты.
Минусы при работе с Яндекс Картами:
-
Значительно увеличивает размер APK. Для нативной разработки есть две версии пакета – full и lite. Lite решает эту проблему, но во Flutter она не работает. Issue здесь: https://github.com/Unact/yandex_mapkit/issues/194
-
Всегда работает только с одним языком. Из-за нативных ограничений после запуска приложения язык нельзя изменить.
-
Документация не адаптирована под Flutter и сейчас она только для нативной разработки: https://yandex.com/dev/maps/mapkit/doc/intro/concepts/about.html
К счастью, во Flutter библиотеке хорошо описан процесс подключения, а на github можно найти множество примеров использования.
В Telegram есть чат разработчиков по yandex_mapkit, там можно получить ответы на вопросы.
С теоретической частью закончили, теперь давайте перейдем к практике.
Интеграция Яндекс Карт во Flutter
Задача – интегрировать Яндекс Карты, отобразить текущее местоположение пользователя.
Для подключения карт нам потребуется пакет yandex_mapkit
Для определения местоположения будем использовать geolocator (имеет Flutter Favorite).
Вы можете использовать любой другой сервис, но нужно иметь в виду, что geolocator использует сервисы Google на Android, и качество геолокации может быть значительно ниже для Huawei. Чтобы избежать такой проблемы, для HMS лучше отдельно использовать их плагин huawei_location
Добавляем оба плагина в pubspec.yaml файл нашего Flutter Android проекта:
yandex_mapkit: ^3.2.0 geolocator: ^9.0.2
Местоположение определяется по текущей широте и долготе.
У геолокатора и у Яндекс Карт есть свой класс для широты и долготы. Но у одного одни модели, а у другого – другие. Нам нужна своя модель без завязки на реализации сторонних сервисов.
Для этого создадим класс AppLatLong со значениями типа double. Также создадим класс MoscowLocation с координатами Москвы (будем выводить Москву, если не получится определить текущее местоположение):
class AppLatLong { final double lat; final double long; const AppLatLong({ required this.lat, required this.long, }); } class MoscowLocation extends AppLatLong { const MoscowLocation({ super.lat = 55.7522200, super.long = 37.6155600, }); }
Создаем новый файл app_location.dart, в котором создаем абстрактный класс AppLocation с тремя методами:
abstract class AppLocation { Future<AppLatLong> getCurrentLocation(); Future<bool> requestPermission(); Future<bool> checkPermission(); }
Далее создаем сервис LocationService, в котором реализовываем интерфейс AppLocation. Этот сервис будет отвечать за получение текущего местоположения пользователя. Создадим переменную defLocation с дефолтными координатами.
Расписываем его методы:
class LocationService implements AppLocation { final defLocation = const MoscowLocation(); }
У geolocator есть свои методы, которые мы здесь используем:
-
getCurrentPosition() – для определения текущей геопозиции (широта и долгота);
-
requestPermission() – для запроса на разрешение использования сервиса местоположения;
-
checkPermission() проверяет, разрешил ли пользователь доступ к геопозиции устройства.
В методе getCurrentLocation() в случае успешного определения местоположения, мы вернем текущие широту и долготу. В случае ошибки вернем широту и долготу Москвы. Координаты нужны, чтобы дальше можно было отобразить их на карте.
@override Future<AppLatLong> getCurrentLocation() async { return Geolocator.getCurrentPosition().then((value) { return AppLatLong(lat: value.latitude, long: value.longitude); }).catchError( (_) => defLocation, ); }
В методе requestPermission() мы делаем повторный запрос на доступ к сервису геопозиции. Если пользователь разрешил доступ к геопозиции, вернем true. В случае ошибки вернем false.
@override Future<bool> requestPermission() { return Geolocator.requestPermission() .then((value) => value == LocationPermission.always || value == LocationPermission.whileInUse) .catchError((_) => false); }
Метод checkPermission() практически копирует предыдущий метод и возвращает булевое значение в зависимости от того, предоставил ли пользователь доступ к определению геопозиции.
@override Future<bool> checkPermission() { return Geolocator.checkPermission() .then((value) => value == LocationPermission.always || value == LocationPermission.whileInUse) .catchError((_) => false); }
Сервис для определения геопозиции готов! Создаем новый файл map_screen.dart и StatefulWidget в нем – MapScreen:
class MapScreen extends StatefulWidget { const MapScreen({Key? key}) : super(key: key); @override State<MapScreen> createState() => _MapScreenState(); } class _MapScreenState extends State<MapScreen> { @override Widget build(BuildContext context) { return Scaffold(); }
Подключаем пакет yandex_mapkit:
Вначале нам нужно получить MapKit mobile SDK key. Для этого идем https://developer.tech.yandex.com/ , выбираем MapKit mobile SDK key и заполняем простую форму (бесплатный тариф). После успешного заполнения копируем наш ключ:
Для подключения Android:
Идем в android > app > build.gradle и добавляем:
implementation 'com.yandex.android:maps.mobile:4.2.2-full'
Идем в android > app > src > main > AndroidManifest.xml и добавляем два разрешения:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Идем в файл android > app > src > main > kotlin или java у вас > MainActivity.kt и добавляем код (берем в readme https://pub.dev/packages/yandex_mapkit) в зависимости от того java у вас или kotlin. Добавляем наш ключ, который получили выше, в 11 строку. Не пугаемся подчеркиваний и ошибок, просто закрываем файл:
Не забудьте добавить первую строку с названием.
Для подключения iOS:
Идем в iOS> Runner > AppDelegate.swift и добавляем вначале импорт:
import YandexMapsMobile
Затем:
YMKMapKit.setApiKey("YOUR_API_KEY")
Не забудьте добавить ваш ApiKey, который получили ранее.
В iOS > podfile добавляем
platform :ios, '12.0':
platform :ios, ‘12.0’: В iOS > Runner > Info.plist обязательно нужно добавить:
<key>NSLocationWhenInUseUsageDescription</key> <string>Доступ к геолокации пользователя необходим для отображения текущей геопозиции</string>
Это требование Apple, им нужно описание. Необходимо добавить в описание, для чего запрашиваем геопозицию. Иначе можно не пройти проверку в App Store.
yandex_mapkit для Android и iOS подключили.
Создаем новый файл map_screen.dart и StatefulWidget в нем – MapScreen:
class MapScreen extends StatefulWidget { const MapScreen({Key? key}) : super(key: key); @override State<MapScreen> createState() => _MapScreenState(); } class _MapScreenState extends State<MapScreen> { final mapControllerCompleter = Completer<YandexMapController>(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Текущее местоположение'), ), body: YandexMap( onMapCreated: (controller) { mapControllerCompleter.complete(controller); }, ), ); } }
Добавляем контроллер через Completer, который будем заполнять дальше, в onMapCreated
Используем методы из LocationService:
Future<void> _initPermission() async { if (!await LocationService().checkPermission()) { await LocationService().requestPermission(); } await _fetchCurrentLocation(); } Future<void> _fetchCurrentLocation() async { AppLatLong location; const defLocation = MoscowLocation(); try { location = await LocationService().getCurrentLocation(); } catch (_) { location = defLocation; } _moveToCurrentLocation(location); }
Метод _initPermission() проверяет, предоставил ли пользователь разрешения на определение геопозиции. Если не предоставил, то делаем запрос на разрешение доступа к геопозиции. После этого вызываем метод _fetchCurrentLocation() для установки координат.
Метод _fetchCurrentLocation() получает необходимые координаты для метода _moveToCurrentPosition(). В случае ошибки, или если он не сможет определить текущее местоположение, вернет координаты Москвы.
Напишем метод _moveToCurrentPosition(). Это основной метод, который и будет показывать местоположение пользователя на карте:
Future<void> _moveToCurrentLocation( AppLatLong appLatLong, ) async { (await mapControllerCompleter.future).moveCamera( animation: const MapAnimation(type: MapAnimationType.linear, duration: 1), CameraUpdate.newCameraPosition( CameraPosition( target: Point( latitude: appLatLong.lat, longitude: appLatLong.long, ), zoom: 12, ), ), ); }
Метод принимает координаты высоты и широты, которые мы получили выше.
Ждем получения mapControllerCompleter и далее, используя методы .moveCamera() и .newCameraPosition(), по полученным координатам анимированно переносим фокус на текущее местоположение.
Параметр zoom: 12 задает отдаление ближе/дальше, так можно отобразить более точные координаты местоположения.
Осталось только добавить метод _initPermission().ignore() для запроса разрешений и установления координат в initState() MapScreen, и реализация готова.
.ignore() нужен здесь для безопасной обработки и игнорирования Future метода _initPermission()
@override void initState() { super.initState(); _initPermission().ignore(); }
Запускаем приложение:
Hidden text
Отлично, задача выполнена! Мы получили текущее местоположение пользователя:)
Мы интегрировали Яндекс Карты в приложение и научились определять текущее местоположение пользователя. В следующей части статьи добавим маркер для более точных координат текущего местоположения и покажем необходимые объекты рядом, например, магазины.
Если у вас остались вопросы по интеграции Яндекс Карт во Flutter, оставляйте их в комментариях!
Код проекта доступен по ссылке.
ссылка на оригинал статьи https://habr.com/ru/articles/715940/
Добавить комментарий