В тот день я опоздал везде. Ожидая звенящего ключами работника, я размышлял, насколько глупо попался. Забегался, оставил машину на полчаса вместо максимальных бесплатных 20 минут — ровно на 21-й минуте и попался. Не повезло, полосатый фургончик парковщиков стоял недалеко, и они моментально среагировали. Ловили меня и до этого, по разным причинам: забывал, истекал оплаченный срок, а иногда и просто не мог найти свою машину в лабиринте улиц.
“Для всего должно быть приложение” — подумал я и начал копаться в апп сторе. После вороха сомнительных результатов у меня поубавилось уверенности, и я решил уточнить: “для всего должно быть приложение на андроид”. После чего нашел свой хуавей и полез в недра плей стора. Оттуда на меня высыпалось еще больше мусора, и я, утопая в корявых поделках, плюнул. Либо я ищу как-то не так, либо не существует удобного и понятного трекера парковки. Вывод простой: если у нас чего-то нет, давайте сделаем это сами.
На фоне у меня уже пилился долгострой, отнимавший почти все свободное время. Я решил взять паузу и в промежутке собрать другой проектик. Прикинув желаемый функционал, мне представился срок в месяц-полтора, и, забегая вперед, скажу, что в принципе вышло даже быстрее. По итогу получилось компактное и чистое приложение на обе платформы, очень удобное. Сейчас сам им пользуюсь и предлагаю попробовать вам.
Идея
Итак, чего же я хотел бы от трекера парковки? Чего мне всегда не хватает, когда я попадаю на штраф? Подумав, я сформулировал следующие вводные:
- Простое. В идеале однокнопочное. Минимум отвлекающих функций: никаких профилей, истории парковок, поддержки нескольких авто одновременно и тд.
- С уведомлениями. Я бы не забывал про машину, если бы приложение мне напоминало: братан, у тебя там парковка между прочим, и она вот-вот истечет.
- Учитывать время ходьбы. Это простейшая мысль, которую я нигде не видел воплощенной. Мне не надо знать, что парковка истечет через 5 минут, если я в получасе ходьбы от нее. Мне нужно напомнить так, чтобы я успел дойти до машины за пару минут до конца и уехать.
- Маршрут и навигация к парковке. Навигация, естественно, через гугл или эппл карты, а вот маршрут можно и в своем приложении показывать. Это поможет, если ты оставил машину в незнакомом районе, таймер тикает, а ты бегаешь кругами по одинаковым улицам.
В качестве технологической базы я выбрал Flutter, так как уже работаю с ним какое-то время и считаю неплохим. Для маршрутов решил использовать HERE API, тоже уже не в первый раз, а для карты — Mapbox. Набросал себе несколько вариантов интерфейса, выбрал самый минималистичный и включился в работу.
Разработка
В принципе, про сам процесс разработки рассказать можно не особо много. Серверной части тут нет, простейший клиент, состоящий по сути из одной большой вьюшки в разных состояниях. На первой странице ты либо выбираешь пресет — 1, 2, 4, 6 часов — либо слайдером настраиваешь свой срок парковки. Попробовав это вживую, я понял, что слайдер достаточно груб, и добавил ему кнопки “+” и “-“. Забавным и довольно неочевидным нюансом тут стало то, что, пока ты выбираешь — время идет. Так что обновлять эту вьюшку нужно в реальном времени. Выбрав срок, нажимаем на единственную кнопку внизу, и данные по локации и времени сохраняются, а парковка стартует. Важно заметить, что я принципиально храню все данные на борту, ничего ни на какой сервер не передается. В дальнейшем это, кстати, создаст мне трудности, но про это позже.
Вторая страница — это, собственно, таймер парковки. Я перепробовал тут кучу вариантов: начинал с дико перегруженных инфой прототипов, после чего итеративно избавлялся и упрощал до предела. Мне очень хотелось, чтобы статус парковки изображался визуально, а не сухими цифрами, поэтому главным элементом этой страницы наряду с картой стала кольцевая диаграмма. Она показывает истекшее время, время ходьбы и оставшееся время. Если ты находишься не рядом с машиной и время ходьбы ненулевое — появляется кнопка запуска навигации, а карта показывает примерный маршрут до парковки. Вот, собственно, и все.
Не смотря на визуальную простоту, этот экран изрядно потрепал мне нервы. Надо понимать, что пока парковка активна, происходит одновременно ряд процессов:
- время идет, и истекает таймер
- ты перемещаешься, и время пути меняется
Все это происходит в реальном времени и должно моментально отображаться на интерфейсе. Диаграмма должна плавно перестраиваться, карта двигаться и перерисовывать маршрут, при риске опоздать или истечении парковки соответствующее предупреждение тоже должно выскакивать. Более того: при закрытии и открытии приложения нужно это все корректно восстановить. И наконец страшное: весь функционал должен уметь работать в фоновом режиме, ведь большинство своей жизни приложение проведет именно на фоне. Такие дела.
Не буду описывать реализацию в деталях, скажу лишь, что флаттеровская работа со стейтами очень достойная. Там, где в чистом андроиде получилась бы перегруженная объектно-ориентированная конструкция, у меня вышло просто и элегантно. Нормальные понятные стейты, толковая привязка вьюшек к моделям — в задачах подобных этой флаттер сияет. Впрочем, не обошлось и без типичных косяков: например, у местной библиотеки работы с картами отсутствует анимированный переход на новые координаты. Пришлось лезть в ее код, вытаскивать какие-то приватные методы и дописывать свой костыль. Не исключено, что этот функционал скоро допишут, но в этом весь флаттер — при всей его привлекательности будьте готовы к таким вот открытиям.
С остальным вроде вышло все гладко. Собственно, остального там было и немного: пара диалогов, да интро-страница, для которой моя девушка (она художница) набросала приятные анимации. Наконец, я перевел все приложение на русский, включая карту. А вот дальше началась боль: уведомления.
Уведомления
Для меня возможность присылать умные уведомления была ключевой для приложения — без нее нечего и городить. Однако, когда я начал работать над этой функцией, я понял, что сам себе наступил на ногу. Проблем на самом деле было две. Первая — флаттер, который не умеет из коробки работать с низкоуровневыми апи девайсов: нужно писать прокладку в виде плагина, которая содержит два нативных кода и абстракцию на дарте. Вторая проблема — моя принципиальная позиция, что все должно выполняться на борту. Итак, суммируем, что надо. Надо, чтобы приложение на фоне:
- Постоянно мониторило твою геолокацию,
- Проверяло, сколько минут ходьбы до парковки
- Сверялось с истекающим таймером
- Если есть риск не успеть вернуться — стреляло уведомление
Чувствуете масштаб трагедии? Понятно, что не существует технической возможности это делать, если приложение совсем убито. Но допустим, оно просто свернуто, а экран выключен. Маршрут и оценку времени ходьбы мне дает апи HERE, значит ли это, что приложение будет спамить реквестами на фоне? Сколько кода вообще будет при этом прогоняться? Как у нас с памятью и расходом энергии?
Я сел думать. Начал с малого: технически у флаттера с недавнего времени есть возможность выполнять код в фоновом режиме, она называется isolate. Если все правильно написать, то можно построить логику специфическим образом, чтобы не вся она выполнялось на фоне, а какой-то определенный функционал. Ведь в свернутом режиме нам не нужно рисовать интерфейсы или обновлять стейты — нас интересует голая логика вычисления времени, а эта логика довольно компактная и дешевая. При этом запускать ее должна не смена геопозиции, как было у меня в первоначальном варианте, а некий внутренний таймер. Потому что уведомление должно прилететь, даже если ты не двигаешься, а сидишь на месте.
Ну хорошо, мы написали легкий код, который умеет прогоняться на фоне и управляется таймером. Как нам его тригерить в рамках нативных приложений и передавать туда данные геолокации? Оказалось, по-разному.
Логика андроида и айфона в вопросах выполнения кода на бекграунде кардинально разная. Для айфона оно отсутствует как таковое: фоновый код может вызвать только системное событие определенного рода, например проигрывание потокового аудио или — к счастью для меня — обновление локации. Для этого достаточно запросить пермишен и в нативной части флатеровского плагина подписаться на событие. Оттуда через специфический канал связи (MethodChannel) мы вызываем коллбэк в дарте, передаем ему геоданные и запускаем фоном нужную нам логику. Красота.
У андроида это работает по-другому. Технически у него есть возможность выполнять код фоном без привязки к системным событиям — для этого надо описать сервис. Он висит отдельно от приложения либо совсем на фоне, либо на форграунде (там он дает от себе знать прикрепленным сообщением в шторке). Казалось бы, проблема решена: в нативной части плагина описываем фоновый сервис, который трекит локацию и дергает дартовский код. Однако есть нюанс. С андроида версии 8.0 поменялось отношение к таким сервисам: теперь, если приложение свернуто в данный момент, его фоновые сервисы адски режутся осью в целях экономии энергии. И, если твой код осуществляет, например, бекап данных, то тебе в принципе все равно, в какой именно момент ось пустит его работать — главное, чтобы сработал. А вот в моем случае нам необходимо отслеживать изменение локации практически в реальном времени, и тайминг здесь критичен. Поэтому единственный выход, это пускать сервис в форграунд. Описываем небольшое прикрепленное в шторке уведомление, подписываемся на локацию, через MethodChannel передаем ее в дарт. Вроде все работает.
Кстати, возвращаясь к вопросам памяти, расхода энергии и количества реквестов — код прогоняется при смещении локации в 50 метров, либо по таймеру. Причем сам он очень легкий, а реквесты проходят не через меня, а идут напрямую на апи HERE. Я сам не получаю никаких данных и нигде у себя не сохраняю. Вообще подобная реализация очень хорошо описана тут, это блог одного из инженеров флаттера — там и код прилагается. Его пример несколько про другое, но общий подход понятен, так что рекомендую почитать.
Результат
Вот, собственно, и все. За месяц свободного времени у меня получилось приложение, отлично решающее конкретную задачу. Надеюсь, эта статья будет кому-то интересна, а приложение спасет от штрафов и стресса. Поглядеть и поругать можно тут: Android, iOS.
Всех благодарю за внимание.
Комментарии (34)
apple01
20.08.2019 22:40Спасибо, мне понравилась идея и реализация. Мне нужно аналогичное приложение которое мониторит позиции меня и автобуса с GPS тракингом и сообщает когда пора выходить из дома. Причем оно должно издавать периодические звуковые сигналы, частота которых увеличивается если мне нужно ускориться чтобы успеть на остановку. В установках нужно добавить интервалы безопасности чтобы прибыть на остановку на пару минут раньше. Мечтаю о таком уже несколько лет :) На самом деле это частный случай общей задачи.
KEugene
21.08.2019 05:28Может, это зависит от региона, но подобный функционал у гугл мапс: выйти через 15 минут, чтобы подойти к остановке к приезду автобуса маршрута такого-то.
apple01
21.08.2019 17:46Протестировал гугл мапс. У меня по крайней мере он опирается на расписание а не на трекинг к сожалению.
MotoDruG
21.08.2019 11:56им нельзя слепо верить(( иногда трекер подвисает, уже бывало так, смотришь на карту, маршрутка там, по прикидкам будет через 5 минут, ждёшь, через 5 минут опять смотришь, маршрутка уже за 5 остановок уехала, делаешь круглые глаза «как так? она же мимо не проезжала». Только потом начал подмечать, что некоторые маршруты практически лайф-вьюв, а некоторые если пару минут стоят на одном месте — 100% зависли.
dchabanenko Автор
21.08.2019 12:16Насколько я знаю, они используют GTFS (General Transit Feed Specification), и не все компании предоставляют реалтаймовый фид. Для многих это тупо статичная табличка время-станция, вот гуглы и пытаются прикинуть, а реальное время конкретного автобуса другое — он уже уехал или не приехал
apple01
21.08.2019 17:49В моем случае есть real time и автобусная компания предоставляет простую текстовую страницу для мобильного телефона с обратным отсчетом времени для выбранной остановки.
Слепо верить конечно нельзя, например автобус может вообще не появиться на трекинге, но это не значит что он обязательно не приедет. Но если появился и отсчет идет, то будет обязательно.
KEugene
21.08.2019 05:34На всякий случай хотел бы заметить, что постоянный пересмотр маршрута будет довольно энергоемким. Может, его перепрокладывать если телефон сменил позицию в радиусе, например, 10-50 м от предыдущего положения?
dchabanenko Автор
21.08.2019 12:19А я так и делаю — маршрут пересматривается при смещении в 50 метров. Там у меня есть хитрости в том, как код написан и запускается, потому что нужно таймер учитывать. Но реквесты на маршрут стреляют только при крупном смещении
slava_k
24.08.2019 09:33Возможно имеет смысл для экстренных ситуаций с пропаданием связи/gps сделать шагомер по встроенному гироскопу. В фоновом процессе всегда начинать счет шагов и среднюю скорость с пройденным растоянием, и в случае длительной неработоспособности связи/gps примерно вычислять время на возврат.
Также можно сделать простое серверное API для сбора данных треков и при длительном использовании приложения сравнивать предсказания времени и фактического результата для треков с общей зоной, при расхождениях в будущем вносить корректировки по времени. Это может быть актуально при перемещении через сложные пешеходные маршруты.
artforteam2018
21.08.2019 12:21Исходный код бы… Писал приложение на flutter и очень застрял на реализации фоновой работы для обоих платформ. Получилось даже, но потом все же перешли на сервер. Пример такого кода только один стоящий можно найти и он указан в статье.
mOlind
21.08.2019 15:03Все хорошо. Но как быть если интернет пропал? Сразу говорить пользователю чтобы шел к машине? Или вы зашли в здание и GPS уплыл в соседний район. У вас ломается предсказание, сколько идти обратно и не ясно в какую сторону. Может начать думать, что машина рядом, а может наоборот.
dchabanenko Автор
21.08.2019 15:15Угу, я думал об этом. Сейчас, если нет связи, работает просто таймер — все равно придет предупреждение, правда без учета ходьбы. И при каждом смещении апп попробует получить маршрут.
Было бы идеально считать маршрут в офлайне на борту, но это совсем другая лига...)mOlind
21.08.2019 15:30Я бы считал сколько времени человек идет от последнего нормального локейшена/построеного маршрута и закладывал столько же времени на обратную дорогу.
Офлайн навигация не так сложна, как может показаться. Но пользователю надо будет предварительно качать навигационные данные для своего города.
nikolayv81
21.08.2019 17:39В соседний район, это ладно, а вот в аэропорт за 20 км за пару секунд легко, и крупным игрокам (Яндекс, гугл) в голову не приходит, что превысить скорость света проблематично...
dchabanenko Автор
21.08.2019 15:10Всем, у кого покрашилось на андроиде 9 — уже починил и выкатил. Извините)
Там теперь оказывается нужен отдельный FOREGROUND_SERVICE пермишен, а я не спросил
spiceginger
21.08.2019 15:23Хорошее приложение, отправил знакомым ирландцам так как сам не вожу.
В ответ получил фидбэк, что они оплачивают парковку с приложения, оно присылает уведомление о том что время заканчивается. Да оно не говорит сколько идти, но говорят возиться с 2мя приложениями — морока.dchabanenko Автор
21.08.2019 15:32Спасибо. Я знаю это приложение, с ним как раз и попадал, что присылают за 5 минут, а я черт знает где)
Плюс есть же куча бесплатных на короткое время парковок. Там главное не опоздать в бесплатное окно
Bedal
21.08.2019 15:58Всё неплохо, вот только, когда ты ушёл от машины, то с высокой вероятностью будешь в помещении. GPS-координаты будут неадекватными. Ладно, если просто пропадут — а то ведь часто именно показывают невесть куда.
___
Паrдон, не заметил, что mOlind уже о том же написал.dchabanenko Автор
21.08.2019 16:10По идее апп построен так, что он старается вывести доступ к локации в высокий приоритет, а значит точность должна быть хорошей. Так же как в гугл картах например. Плюс девайс определяет местоположение по вайфаям и блютусам
Но вообще конечно надо теститьBedal
21.08.2019 16:59боюсь, здесь это не очень получится, большинство читающих — москвичи, но в Москве можно просто заплатить (и доплатить) за парковку с телефона. Там другая проблема:
главный подвох — заплатить за парковку с другим условным номером за углом, а за эту получить штраф
Я — не москвич, но тоже не помогу, наша провинция — настолько провинция, что подобные вопросы вообще не возникают.
Но вообще Вы поймали интересную задачку — при внешней простоте много всего нужно учитывать (те же вайфаи, предполагаемую скорость перемещения в разных местах...).
Mingun
21.08.2019 17:59Вы зачем так статью назвали? Уже который раз, листая ленту, ловлю себя на мысли, зачем и КАК он пакует людей и кому это надо отслеживать...
psinetron
21.08.2019 18:09А вот такой вопрос — наверняка при построении маршрута используется какой-нибудь google Routes. А у них большие ограничения на бесплатное использование. Каким сервисом в итоге маршрут строите?
SemenPV
Бутылка царской водки в багажнике как бэкап?
Assimilator
Болгарка быстрее
EGregor_IV
Цепь проще клещами перекусить. Быстрее и бесшумнее.
Yaris
За это можно получить штраф ещё "круглее", чем за собственно просроченную парковку. В моей части Европы такое очень быстро эскалируется до "запрета на движение автомобиля" (не знаю, как это по-русски называется) — когда машине просто нельзя появляться на дороге иначе, чем на эвакуаторе, а не то конфискация.
dchabanenko Автор
У нас тупо в суд можешь загреметь за порчу имущества, если срежешь.
Есть ловкачи, которые режут и прячут — потом пусть тебе докажут, что цепи вообще были. А ты говоришь, что пришел и ничего на машине нет.
Но это конечно все глупости, я до кусачек в багажнике пока не дошел — пытаюсь не нарушать)
stantum
Скорее всего у полиции/парковщиков ведется журнал, кому и когда нацепили. И он будет считаться более достоверным источником, чем заявление о «ничего не знаю».
Assimilator
Если уже таскаешь болгарку/клещи в багажнике, то наверное нужно ставить фейковый номерной знак поверх своего. Парковщики один фиг пробивать не будут, только запишут.
shifttstas
Т.е вариант не нарушать — вообще не рассматривается?
pilniy
не рассматривается. вы всегда выполняете все, что придумал какой-то хрен с горы?
iig
Поставить запаску, колесо с цепью в багажник, дома неспешно решить вопрос с цепью?