Инструкция: как создать приложение для просмотра погоды на Flutter
Привет! В этой статье я познакомлю вас с кроссплатформенной разработкой приложений на Flutter.
19 761просмотров
Я покажу, как работать с сетью, расскажу, что такое BLoC, и на примере получения геолокации покажу, как работать с нативной частью приложения. Я подразумеваю, что у вас уже есть какие-то навыки программирования и не буду объяснять базовые вещи.
Инициализируем стартовый шаблон приложения с помощью CLI Flutter. Для этого выполним в терминале:
$ flutter create WeatherApp
Если все прошло успешно вы увидите примерно такое сообщение:
All done!
[✓] Flutter: is fully installed. (Channel stable, 1.20.3, on Mac OS X 10.15.5 19F101, locale ru-RU)
[!] Android toolchain - develop for Android devices: is partially installed; more components are available. (Android SDK version 29.0.3)
[✓] Xcode - develop for iOS and macOS: is fully installed. (Xcode 11.7)
[✓] Android Studio: is fully installed. (version 4.0)
[!] VS Code: is partially installed; more components are available. (version 1.48.2)
[!] Connected device: is not available.
Run "flutter doctor" for information about installing additional components.
In order to run your application, type:
$ cd WeatherApp
$ flutter run
Your application code is in WeatherApp/lib/main.dart.
flutter doctor – утилита для проверки, что Flutter установлен правильно
Проверим, что всё работает нормально:
Открываем проект в Android Studio. Обратите внимание, что для работы с Flutter в Android Studio нужно установить плагин flutter.
Запускаем эмулятор или подключаем реальное устройство.
Запускаем проект Run → Debug.
2. Устанавливаем необходимые зависимости
Как и любой современный фреймворк Flutter имеет свой менеджер пакетов – Pub. Для того чтобы установить необходимые зависимости давайте добавим их в файл pubspec.yaml в конец секции dependecies:
После чего нам необходимо их установить. Для этого выполним в терминале команду:
$ flutter pub get
3. Сервис для работы с API
Информацию о погоде мы будем получать с сервиса OpenWeatherMap. Для начала вам необходимо зарегистрироваться, чтобы получить ключ для работы с API на почту. Давайте опишем сервис для работы с API:
// lib/services/WeatherService.dart
class WeatherService {
static String _apiKey = "Здесь должен быть ваш токен";
static Future<Weather> fetchCurrentWeather({query, String lat = "", String lon =""}) async {
var url =
'http://api.openweathermap.org/data/2.5/weather?q=$query&lat=$lat&lon=$lon&appid=$_apiKey&units=metric';
final response = await http.post(url);
if (response.statusCode == 200) {
return Weather.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load weather');
}
}
static Future<List<Weather>> fetchHourlyWeather({String query, String lat = "", String lon =""}) async {
var url =
'http://api.openweathermap.org/data/2.5/forecast?q=$query&lat=$lat&lon=$lon&appid=$_apiKey&units=metric';
final response = await http.post(url);
if (response.statusCode == 200) {
final jsonData = json.decode(response.body);
final List<Weather> data = (jsonData['list'] as List<dynamic>)
.map((item) {
return Weather.fromJson(item);
}).toList();
return data;
} else {
throw Exception('Failed to load weather');
}
}
}
У нас будут два метода fetchCurrentWeather – метод получения текущей погоды и fetchHourlyWeather – метод получения погоды по часам.
Опишем модель данных о погоде:
// lib/models/Weather.dart
class Weather {
final String cityName;
final int temperature;
final String iconCode;
final String description;
final DateTime time;
Weather(
{this.cityName,
this.temperature,
this.iconCode,
this.description,
this.time});
factory Weather.fromJson(Map<String, dynamic> json) {
return Weather(
cityName: json['name'],
temperature: double.parse(json['main']['temp'].toString()).toInt(),
iconCode: json['weather'][0]['icon'],
description: json['weather'][0]['main'],
time: DateTime.fromMillisecondsSinceEpoch(json['dt'] * 1000)
);
}
}
В Dart асинхронные запросы всегда возвращаются в виде Future. Если говорить совсем коротко, то это данные которые будут доступны «в будущем». То есть, мы выполнили запрос, но, для того, чтобы клиент получил ответ от сервера должно пройти какое-то время, и чтобы интерфейс пользователя не блокировался мы должны обработать этот ответ специальным образом, таким как BLoC.
5. BLoC
Существует множество разных паттернов отделения бизнес-логики от UI компонента: MVC, MVP, MVVM и так далее. Во Flutter чаще всего принято использовать BLoC, что расшифровывается как Business logic component. Суть этих «блоков», чтобы сделать event-driven архитектуру приложения. Для начала опишем список состояний компонента с погодой:
// lib/states/WeatherState.dart
abstract class WeatherState extends Equatable {
const WeatherState();
@override
List<Object> get props => [];
}
class WeatherInitial extends WeatherState {}
class WeatherLoadInProgress extends WeatherState {}
class WeatherLoadSuccess extends WeatherState {
final Weather weather;
final List<Weather> hourlyWeather;
const WeatherLoadSuccess(
{@required this.weather, @required this.hourlyWeather})
: assert(weather != null);
@override
List<Object> get props => [weather];
}
class WeatherLoadFailure extends WeatherState {}
Мы описали 4 состояния:
WeatherInitial – когда не происходит ничего
WeatherLoadInProgress – когда мы загружаем данные о погоде
WeatherLoadSuccess – когда данные загружены успешно
WeatherLoadFailure – когда произошла какая-то ошибка
Далее нам необходимо описать события которые будет обрабатывать BLoC:
// lib/events/WeatherEvent.dart
abstract class WeatherEvent extends Equatable {
const WeatherEvent();
}
class WeatherRequested extends WeatherEvent {
final String city;
final String lat;
final String lon;
const WeatherRequested({this.city = "", this.lat = "", this.lon = ""})
: assert(city != null);
@override
List<Object> get props => [city];
}
Сейчас нам достаточно одного события – WeatherRequested. Это событие для того, чтобы BLoC понимал что необходимо запрашивать данные о погоде по названию города.
Опишем сам BLoC:
// lib/bloc/WeatherBloc.dart
class WeatherBloc extends Bloc<WeatherEvent, WeatherState> {}
Для реализации BLoC мы выбрали библиотеки bloc и flutter_bloc. Как видим наш «блок» это просто класс который наследуется от дженерика Bloc из библиотеки Bloc. «Блок» принимает два типа:
WeatherEvent – события которые он будет обрабатывать
WeatherState – состояния в которых он может быть
Теперь нам необходимо реализовать метод mapEventToState для обработки событий в «блоке»:
Давайте соединим наш BLoC и UI. Входная точка в приложение в Dart/Flutter – функция main в файле lib/main.dart. Отсюда будет происходить запуск нашего приложения.
Под капотом BlocBuilder это обычный StatefulWidget. Он имеет колбек builder, который вызывается каждый раз когда внутри WeatherBloc вызывается метод mapEventToState и возвращает новое значение, тем самым заставляя компонент перерисовываться. Внутри builder у нас есть проверка состояния компонента, которая будет рисовать компонент только если данные получены успешно:
if (state is WeatherLoadSuccess) {
8. SearchDelegate
У Flutter есть метод showSearch который открывает окно поиска по гайдлайнам MaterialDesign.
В шапке Scaffold мы добавили иконку поиска по клику на которую вызывается окно поиска:
Открытие окна происходит с помощью метода showSearch, который принимает в себя текущий контекст приложения и класс SearchDelegate который будет обрабатывать результат поиска. Давайте напишем свой SearchDelegate:
Мы получаем результат ввода пользователя и кидаем событие WeatherRequested для WeatherBloc после чего происходит ререндер всего экрана.
Посмотрим на результат:
9. Определение текущей геолокации пользователя
Для того, чтобы определить геолокацию пользователя мы используем библиотеку geolocator.
Для того, чтобы она заработала нам необходимо добавить специальные разрешения в нативные части приложения под iOS и Android. О том как это сделать описано в документации библиотеки geolocator.
Для начала добавим новое событие в файл lib/events/WeatherEvent.dart:
// lib/events/WeatherEvent.dart
class WeatherCurrentPositionRequested extends WeatherEvent {
const WeatherCurrentPositionRequested() : super();
@override
List<Object> get props => [];
}
Интересно, на vc.ru этого не хватает, но почему без какого-то конвейерного скрипта чтобы собрать на github ci или Jenkins? Интересно было бы запустить этот проект в github codespaces и в облаке потестить в процессе собранное приложение на каком-то стенде например с Selenoid Android :)
Задумка хорошя, спасибо! Стоило бы добавить во все приведенные куски кода include, которые им нужны (причем в некоторых кусках они есть, что сбивает). Не каждый новичок сообразит и полезет в полную версию кода на гитхабе, да и не удобно это.
Это самый простой пример для того, чтобы показать как что-то кодить. Не удивлён что нашёлся похожий. Ну и конечно на оригинальность приложения я не претендовал. Цель статьи и приложения показать как можно написать быстро простое приложение с нормальной архитектурой.
Плюсанул за Flutter сразу)
Комментарий недоступен
Код без коментов это круто!
Интересно, на vc.ru этого не хватает, но почему без какого-то конвейерного скрипта чтобы собрать на github ci или Jenkins? Интересно было бы запустить этот проект в github codespaces и в облаке потестить в процессе собранное приложение на каком-то стенде например с Selenoid Android :)
Ну в случае флаттера лучше всё ж использовать https://codemagic.io/
Спасибо, Майк, потестирую!
Задумка хорошя, спасибо! Стоило бы добавить во все приведенные куски кода include, которые им нужны (причем в некоторых кусках они есть, что сбивает). Не каждый новичок сообразит и полезет в полную версию кода на гитхабе, да и не удобно это.
Спасибо, учту на будущее!
Сколько времени было потрачено на такой проект?
Ну у меня ушёл весь вчерашний вечер
https://github.com/LonelyCpp/flutter_weather
Эмм...15 месяцев назад на гитхабе выложен проект, очень похож на Ваш
Это самый простой пример для того, чтобы показать как что-то кодить. Не удивлён что нашёлся похожий. Ну и конечно на оригинальность приложения я не претендовал. Цель статьи и приложения показать как можно написать быстро простое приложение с нормальной архитектурой.
Спасибо, на самом деле приятный материал, изучаю flutter сам, и Вы внесли некие ясности в BLOC)
P.S.Не могли бы Вы выгрузить код в репозиторий на гитхаб?
В начале стать есть ссылка
Комментарий удален модератором
Кому интересен Flutter - заходите и к нам: https://openflutter.ru/ Есть канал Youtube, видео будут пополняться.
сайт не работает