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();
}
Запускаем приложение:
Скрытый текст
Отлично, задача выполнена! Мы получили текущее местоположение пользователя:)
Мы интегрировали Яндекс Карты в приложение и научились определять текущее местоположение пользователя. В следующей части статьи добавим маркер для более точных координат текущего местоположения и покажем необходимые объекты рядом, например, магазины.
Если у вас остались вопросы по интеграции Яндекс Карт во Flutter, оставляйте их в комментариях!
Код проекта доступен по ссылке.
P.S. Мы ведем дружелюбный канал про Flutter в Telegram. Присоединяйтесь!