В данной статье мы рассмотрим, каким образом можно создать консольное приложение, позволяющее получить погодные данные для выбранной локации и отобразить их в консоли, используя язык программирования dart и библиотеку  weather_pack. Также научимся использовать сервис геокодирования местоположений и рассмотрим удобные классы, позволяющие конвертировать значения в нужные единицы измерения. Пакет weather_pack был разработан, чтобы упростить доступ к онлайн сервису https://openweathermap.org/ (далее, OWM), а также предоставить удобные методы по работе с полученными данными. Следующее руководство будет придерживаться официальных рекомендаций по использованию (readme.md проекта), но с некоторыми бонусами. Приступим! Предисловие Подготовительный этап Получаем первые погодные данные Архитектура Пишем код Компиляция в .exe Послесловие Данный материал был создан, чтобы решить ряд следующих задач: познакомиться с платформой habr в роли автора контента рассказать, как создать консольное приложение с помощью языка программирования dart рассмотреть возможности использования пакета weather_pack Библиотека  weather_pack не зависит от фреймворка flutter, и её можно использовать, например, в консольных приложениях dart. Именно так, создав консольное приложение, мы и протестируем данный пакет. Для начала, создадим наше dart-приложение. Перейдите в необходимую папку и запустите команду: Флаг --template=console создает простенькое консольное приложение-шаблон. Теперь необходимо добавить пакет в качестве зависимости в наш файл pubspec.yaml. Воспользуемся командой: Или же, добавим вручную: После запускаем команду dart pub get. После всех манипуляций мы имеем такую структуру папок. Подготовительный этап завершён, мы можем начинать пользоваться пакетом.  На данный момент в версии 0.0.2 есть два основных класса погоды: WeatherCurrent и WeatherOneCall. Вторая модель сложнее и включает в себя WeatherCurrent: WeatherCurrent - характеристики текущей погоды List<WeatherHourly> - список погодных моделей, различающихся на час. Включает в себя порядка 48 объектов, содержащих различные параметры. List<WeatherMinutely> - список моделей, характеризующихся минутной разницей и в качестве поля имеющие только precipitation - количество осадков в данное время. List<WeatherDaily> - список из 7 объектов, включающий обширный объем параметров, характеризующий каждый последующий день List<WeatherAlert> - погодные предупреждения. Поля: дата начала и дата окончания события, само событие и его описание. Ещё есть имя отправителя и теги. Два основных типа моделей означает, что это разные запросы на сервер. И запрос для получения WeatherOneCall дороже (было так: 1000/день и 30000/месяц). На данный момент, я не могу найти эту информацию для количества запросов именно для One Call API 2.5 (ранее это api называлось "one call api 2.0" и не включало в себя 5-ти дневной исторической погоды. Более того, по вышеуказанной ссылке раньше мы попадали именно на версию 2.0, на api которой и основан данный пакет). А сейчас появился тариф "one call api 3.0", который предлагает 1000 бесплатных запросов в день, но при превышении лимита - плOти. И вообще, прежде чем его использовать, необходимо подписаться на этот тариф, заполнив нижеуказанную форму и ... "Continue with Stripe" как бы намекает, что будет весело. Короче, бизнес и маркетинг, ничего личного. Поэтому, наши базовые запросы будут выглядеть так: Хочу обратить ваше внимание, что сервис OWM рекомендует использовать поиск погоды именно по координатам. Ранее это было возможно, используя город, однако: Please use Geocoder API if you need automatic convert city names and zip-codes to geo coordinates and the other way around. Please note that built-in geocoder has been deprecated. Although it is still available for use, bug fixing and updates are no longer available for this functionality. Поэтому, в пакете имеется класс GeocodingService, который предоставляет два метода: получение списка местоположений по координатам (getLocationByCoordinates()) и по предполагаемому названию местоположения (getLocationByCityName()). Самое время углубиться в архитектуру нашего мини-приложения. Как таковая, она будет отсутствовать. Входная точка приложения это файл по пути bin\main.dart. Код в нём следующий: Весь код логики будет (и должен) содержаться в файле по пути lib/weather_in_console.dart . Всё сделаем приватным через _ и определим одну единственную публичную функцию getWeather(). Наше приложение будет работать согласно блок-схеме:  Будем поэтапно, согласно вышеуказанной схеме, писать код. Все действия происходят в файле lib/weather_in_console.dart. Для начала определяем нашу единственную публичную функцию: В локальной области видимости функции getWeather() будут происходить все основные манипуляции и вызовы других, непубличных функций. Условимся называть именно эту область локальной. Для начала, необходимо проверить api-ключ на актуальность. Моделируем вот этот участок: В глобальной области видимости определяем приватную функцию: Для проверки ключа используем пакетный метод OWMApiTest.isCorrectApiKey(). Если ключ проходит проверку, продолжаем выполнение программы, иначе, прерываем выполнение командой exit(1). Из документации: Although you can use any number for an exit code, by convention, the codes in the table below have the following meanings: 0 - Success, 1 - Warnings, 2 - Errors Теперь вызовем эту функцию в локальной области: Небольшое отступление. Хочу обратить ваше внимание на то, что хранить секретные ключи и прочие приватные данные в коде я не рекомендую! В данном случае вы можете указать ваш api-ключ, полученный в сервисе OWM (зарегистрируйтесь на сайте openweathermap.org и получите ключ в личном кабинете на вкладке API keys. Это бесплатно), вместо null, однако, я рекомендую сделать иначе и воспользоваться const String.fromEnvironment(). Если вы запускаете приложение в терминале, то воспользуйтесь спец. флагом: Или же, чтобы использовать удобную вкладку Run, в Android Studio вы можете сделать так: Есть неплохая статья на эту тему здесь. Далее, реализация вот этого участка схемы: будет выглядеть так: Да, чтобы принимать пользовательские вводы я решил использовать дополнительный пакет, облегчающий работу с командной строкой. Пакет называется interact: ^2.1.1. Позже, он нам ещё пригодится для реализации более сложных вещей. После того, как пользователем было указано желаемое местоположение, необходимо это место преобразовать в координаты. На помощь приходит встроенный метод GeocodingService.getLocationByCityName() из нашего пакета weather_pack. В глобальной области видимости определяем приватную переменную сервиса геокодирования, в конструкторе которого необходимо указать api-ключ: и используем этот сервис, чтобы получить список мест, похожих на желаемое: Методы Spinner и Select из пакета interact Используем Spinner, чтобы сделать более дружелюбным наш интерфейс и также показать, что сейчас осуществляется запрос. Как только запрос выполнен, завершаем Spinner командой spinner.done(). Далее, возвращаем null, если похожих мест не найдено. Иначе, предлагаем пользователю выбрать наиболее похожее место из списка найденных мест. Кстати, метод getLocationByCityName() принимает необязательный параметр limit, чтобы указать максимальное количество найденных мест. По умолчанию указан максимум - не более 5 мест (в соответствии с api OWM). В локальной функции мы сделаем трюк - повторим весь процесс, если сервис геокодинга возвращает пустой список (т.е. null): Что ж, после того, как желаемое место было найдено и выбрано, предложим пользователю выбрать тип получаемых погодных данных. В блок схеме: В коде: В локальной функции: В функции _getWeather() мы получаем погоду и отображаем результаты в консоли. Мы также помним, что тариф WeatherService.currentWeatherByLocation() дешевле, поэтому, если мы выбираем _TypeWeather.current, то делаем именно дешевый запрос (хотя, как мы и помним, модель WeatherOneCall включает в себя WeatherCurrent). Конструктор WeatherService принимает необязательный параметр language, чтобы предоставить некоторые погодные данные на выбранном языке, например, WeatherCurrent.weatherDescription, а также PlaceGeocode.localNames - map, в которой содержатся названия местоположения на определенном языке. На данный момент api предоставляет 47 языков. Функции, такие как _printDaily() отображают погодные данные в консоли. В пакете weather_pack есть удобные методы конвертации и отображения погодных данных. Посмотрим, как они работают на примере _printCurrent(): Весь список доступных методов можно увидеть здесь. Выбираем единицы измерения давления, температуры и скорости. У каждого перечисления есть два метода конвертации: value() и valueToString(). Если сказать коротко, то метод valueToString() нужен для корректного отображения данных в интерфейсе (чтобы точно контролировать количество знаков после запятой), а метод value() — для точных вычислений. Каждый из этих методов имеет позиционный параметр int precision, чтобы указать количество значимых знаков после запятой. Этот код из "readme.md" хорошо описывает суть данных методов: Ещё примечательный факт, что направление ветра из api приходит в градусах. С помощью метода SideOfTheWorld.fromDegrees() можно перевести градусы в сторону света. Кажется, остался последний момент в нашей блок-схеме: После преобразований наша локальная функция будет выглядеть так: После отображения данных в консоли, мы предложим пользователю узнать погоду из другого местоположения. Если он откажется, мы завершим наше приложение. Кажется, блок-схема подошла к Концу. А почему бы и нет? Это достаточно просто сделать следующей командой: И мы получим .exe файл, который можно передавать друзьям и знакомым. С оговоркой: The exe subcommand has some known limitations: No cross-compilation support (issue 28617) The compiler can create machine code only for the operating system on which you’re compiling. To create executables for macOS, Windows, and Linux, you need to run the compiler three times. You can also use a continuous integration (CI) provider that supports all three operating systems. Подробнее об имеющихся возможностях компиляции, а также о полезных флагах (есть крутой флаг управления оптимизацией) смотрите в официальной документации здесь. Результат работы в гифке: Есть небольшое разочарование в том, что в windows терминалах (и в cmd и в powershell) я не смог увидеть красивых эмодзи, которые использовал в коде. Однако, через новомодный terminal проблема решена. Также, хочу обратить внимание, что мы не можем использовать кириллицу; соответственно, русскими буковками написать нужный нам город, например Омск, не получится. Приходится писать вот так: Omsk. Всё дело в данной проблеме, которая до сих пор не имеет очевидного решения. Ремарка к кодовой базе: Допущения в области проверки на null (например, city.latitude ?? 0.0 или current.temp!) существуют только потому, что автор не ставит перед собой задачи корректно их обрабатывать. Полностью отсутствует обработка ошибок, однако, в weather_pack существует класс OwmApiException, призванный помочь с некоторыми ошибками api. Но, например, TimeoutException - это дело рук пользующегося пакетом. Отсутствующая адекватная архитектура (мы не можем назвать адекватной архитектурой напихивание разнопарных функций в один файл) приложения говорит только о том, что весь код лежит в папке example (и это правда). Ссылка на репозиторий данного проекта. Спасибо за прочтение данной статьи. Буду рад комментариям.  Пользуясь случаем, поздравляю хабровчан с Новым годом! 🎊  Моя литера Т стремится к равнозначности линий