Инструкция: как создать приложение для просмотра погоды на Flutter
Привет! В этой статье я познакомлю вас с кроссплатформенной разработкой приложений на Flutter.
Я покажу, как работать с сетью, расскажу, что такое BLoC, и на примере получения геолокации покажу, как работать с нативной частью приложения. Я подразумеваю, что у вас уже есть какие-то навыки программирования и не буду объяснять базовые вещи.
Для тех кто не хочет читать много текста есть ссылка на репозиторий GitHub.
0. Intro
Для начала давайте установим Flutter. Как это сделать – вы можете узнать об этом на официальном сайте Flutter.
1. Getting started
Инициализируем стартовый шаблон приложения с помощью CLI Flutter. Для этого выполним в терминале:
Если все прошло успешно вы увидите примерно такое сообщение:
flutter doctor – утилита для проверки, что Flutter установлен правильно
Проверим, что всё работает нормально:
- Открываем проект в Android Studio. Обратите внимание, что для работы с Flutter в Android Studio нужно установить плагин flutter.
- Запускаем эмулятор или подключаем реальное устройство.
- Запускаем проект Run → Debug.
2. Устанавливаем необходимые зависимости
Как и любой современный фреймворк Flutter имеет свой менеджер пакетов – Pub. Для того чтобы установить необходимые зависимости давайте добавим их в файл pubspec.yaml в конец секции dependecies:
После чего нам необходимо их установить. Для этого выполним в терминале команду:
3. Сервис для работы с API
Информацию о погоде мы будем получать с сервиса OpenWeatherMap. Для начала вам необходимо зарегистрироваться, чтобы получить ключ для работы с API на почту. Давайте опишем сервис для работы с API:
У нас будут два метода fetchCurrentWeather – метод получения текущей погоды и fetchHourlyWeather – метод получения погоды по часам.
Опишем модель данных о погоде:
В Dart асинхронные запросы всегда возвращаются в виде Future. Если говорить совсем коротко, то это данные которые будут доступны «в будущем». То есть, мы выполнили запрос, но, для того, чтобы клиент получил ответ от сервера должно пройти какое-то время, и чтобы интерфейс пользователя не блокировался мы должны обработать этот ответ специальным образом, таким как BLoC.
5. BLoC
Существует множество разных паттернов отделения бизнес-логики от UI компонента: MVC, MVP, MVVM и так далее. Во Flutter чаще всего принято использовать BLoC, что расшифровывается как Business logic component. Суть этих «блоков», чтобы сделать event-driven архитектуру приложения. Для начала опишем список состояний компонента с погодой:
Мы описали 4 состояния:
- WeatherInitial – когда не происходит ничего
- WeatherLoadInProgress – когда мы загружаем данные о погоде
- WeatherLoadSuccess – когда данные загружены успешно
- WeatherLoadFailure – когда произошла какая-то ошибка
Далее нам необходимо описать события которые будет обрабатывать BLoC:
Сейчас нам достаточно одного события – WeatherRequested. Это событие для того, чтобы BLoC понимал что необходимо запрашивать данные о погоде по названию города.
Опишем сам BLoC:
Для реализации BLoC мы выбрали библиотеки bloc и flutter_bloc. Как видим наш «блок» это просто класс который наследуется от дженерика Bloc из библиотеки Bloc. «Блок» принимает два типа:
- WeatherEvent – события которые он будет обрабатывать
- WeatherState – состояния в которых он может быть
Теперь нам необходимо реализовать метод mapEventToState для обработки событий в «блоке»:
И добавим в конструктор блока стандартное состояние:
Метод add() добавляет событие в блок и это событие обрабатывается методом mapEventToState.
Вот так выглядит наш полный «блок»:
6. Интерфейс
Сделаем карточку погоды. Карточка должна отображать температуру, иконку и произвольный заголовок.
Нам также понадобится компонент для отображения погоды по часам:
Сделаем компонент для главной страницы, который будет в себя включать виджет с текущей погодой и горизонтальный скролл с погодой по часам:
7. Bloc + UI = <3
Давайте соединим наш BLoC и UI. Входная точка в приложение в Dart/Flutter – функция main в файле lib/main.dart. Отсюда будет происходить запуск нашего приложения.
Есть несколько способов работы с BLoC, мы будем использовать BlocProvider. Разберем несколько строк кода:
Scaffold – компонент для стандартного интерфейса MaterialDesign: Шапка/Тело/Навигация и FloatingButton.
create: (context) => WeatherBloc('Berlin') – здесь мы создаем наш объект «блока» и передаем туда значение по умолчанию.
Под капотом BlocBuilder это обычный StatefulWidget. Он имеет колбек builder, который вызывается каждый раз когда внутри WeatherBloc вызывается метод mapEventToState и возвращает новое значение, тем самым заставляя компонент перерисовываться. Внутри builder у нас есть проверка состояния компонента, которая будет рисовать компонент только если данные получены успешно:
8. SearchDelegate
У Flutter есть метод showSearch который открывает окно поиска по гайдлайнам MaterialDesign.
В шапке Scaffold мы добавили иконку поиска по клику на которую вызывается окно поиска:
Открытие окна происходит с помощью метода showSearch, который принимает в себя текущий контекст приложения и класс SearchDelegate который будет обрабатывать результат поиска. Давайте напишем свой SearchDelegate:
Здесь у нас четыре метода:
- buildActions – возвращает компонент, который отображается справа строки поиска. У нас это кнопка удаление текущего ввода.
- buildLeading – возвращает компонент, который отображается слева шапки (например, стрелка назад).
- buildSuggestions – обрабатывает то, что вводит пользователь.
- buildResults – вызывается, когда был вызван метод showResults.
Мы также описали колбек который будет вызываться когда пользователь выбрал какой-то город или нажал поиск на клавиатуре:
В lib/main.dart мы описали его так:
Мы получаем результат ввода пользователя и кидаем событие WeatherRequested для WeatherBloc после чего происходит ререндер всего экрана.
Посмотрим на результат:
9. Определение текущей геолокации пользователя
Для того, чтобы определить геолокацию пользователя мы используем библиотеку geolocator.
Для того, чтобы она заработала нам необходимо добавить специальные разрешения в нативные части приложения под iOS и Android. О том как это сделать описано в документации библиотеки geolocator.
Для начала добавим новое событие в файл lib/events/WeatherEvent.dart:
Отредактируем файл lib/bloc/WeatherBloc.dart.
Создадим метод _newWeatherRequested:
Также сделаем новый обработчик события для определения текущей локации:
Здесь мы проверяем права на локацию, если прав нет, то просто запрашиваем.
И обновим mapEventToState:
И весь наш новый lib/bloc/WeatherBloc.dart:
Изменим главный экран чтобы добавить кнопку запроса локации:
И проверяем что получилось:
Спасибо всем кто дочитал до конца. Буду рад ответить на вопросы и готов выслушать любую критику.
Плюсанул за Flutter сразу)
Комментарий недоступен
Код без коментов это круто!
Интересно, на vc.ru этого не хватает, но почему без какого-то конвейерного скрипта чтобы собрать на github ci или Jenkins? Интересно было бы запустить этот проект в github codespaces и в облаке потестить в процессе собранное приложение на каком-то стенде например с Selenoid Android :)
Ну в случае флаттера лучше всё ж использовать https://codemagic.io/
Задумка хорошя, спасибо! Стоило бы добавить во все приведенные куски кода include, которые им нужны (причем в некоторых кусках они есть, что сбивает). Не каждый новичок сообразит и полезет в полную версию кода на гитхабе, да и не удобно это.
Спасибо, учту на будущее!