Как интегрировать Яндекс Карты в приложение на Flutter

от автора

Hey! Меня зовут Дмитрий Лёвочкин, я Flutter разработчик в Friflex и автор блога Дневник Flutter разработчика. Мы в Friflex занимаемся разработкой мобильных приложений, и одна из наших ключевых отраслей – ритейл. Сложно представить мобильное приложение крупного ритейлера без карты внутри. В этой статье я расскажу, какие преимущества есть у Яндекс Карт и как быстро интегрировать их в ваше приложение.

Статья состоит из двух частей:

  1. Теоретическая. Плюсы и минусы использования Яндекс Карт.

  2. Практическая. Напишем приложение, в котором отобразим текущее местоположение пользователя. В случае ошибки будем выводить Москву как город по умолчанию. 

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

В контексте ситуации, сложившейся в нашей стране, у Яндекс карт есть ряд преимуществ:

  1. Если вы планируете публиковать приложение в AppGallery, вам не нужно писать разделение для GMS (Google Mobile Services) и HMS (Huawei Mobile Services) сервисов. Яндекс Карты без проблем работают с Huawei. Если вы будете использовать Google Карты, вам потребуется разделить сервисы, так как ваше приложение не может одновременно поддерживать HMS и GMS сервисы (согласно политике Google Play).

  2. В случае возникновения проблем с работой сервисов Google в РФ, ваше приложение будет стабильно работать с картами Яндекса.

  3. По регионам России информация точнее, чем у Google Карт.

  4. Более знакомый интерфейс для русскоязычного пользователя.

  5. Не возникнет проблем с оплатой сервиса, если вы решите перейти на платный тариф.

Вы можете разделить сервисы, использовать Google Карты для публикации приложения в App Store и Google Play, а Яндекс Карты для публикации в AppGallery. Но это добавит проблем при сборке. Нужно будет каждый раз менять карты.

Минусы при работе с Яндекс Картами:

  1. Значительно увеличивает размер APK. Для нативной разработки есть две версии пакета – full и lite. Lite решает эту проблему, но во Flutter она не работает. Issue здесь: https://github.com/Unact/yandex_mapkit/issues/194

  2. Всегда работает только с одним языком. Из-за нативных ограничений после запуска приложения язык нельзя изменить.

  3. Документация не адаптирована под 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *