This media is not supported in your browser
VIEW IN TELEGRAM
Привет! На связи Анна, Flutter Team Lead😉
Совсем недавно мы говорили про тесты во Flutter-приложениях, разобрались в видах и ситуациях использования. Сегодня поговорим об одном из самых известных инструментов для тестирования — библиотеке mocktail.
Часто в процессе написания тестов появляется необходимость имитировать работу того или иного экземпляра класса с целью проверить его работу в разных сценариях. В таком случае на помощь придет mocktail. Разберемся подробнее в возможностях этой библиотеки.
✔️ Создание Mock и Fake
Mock и Fake — понятия, которые существуют не только в тестировании Flutter-приложений. Это так называемые тестовые двойники — классы, предназначенные для упрощения процесса тестирования и изоляции тестируемого объекта от внешних зависимостей.
Оба понятия довольно схожи, но имеются небольшие различия. Моки имитируют поведения реальных объектов, а фейки — копируют функциональность реального объекта, но с некоторым упрощением.
Подробнее понять сущность тестовых двойников поможет эта статья.
Библиотека mocktail дает доступ к Mock- и Fake-классам. Чтобы создать моки или фейки, достаточно наследоваться от них.
✔️ Определение поведения метода — when()
Метод when() позволяет к тестовому объекту определить конкретное поведение.
✔️ Проверка вызовов метода — verify()
Метод verify() позволяет проверить, был ли вообще вызов того или иного метода, с какими параметрами и сколько раз.
Например, так мы можем проверить, что в нашем примере метод read() был вызван 1 раз.
А так можно получить список параметров, с которыми был вызван метод write().
✔️ Сбросить все настроенные поведения — reset()
Тут все просто — если вам нужно убрать все те настройки поведени, которые были заданы ранее, поможет метод reset().
Как показывает практика, mocktail являтся достаточно гибким инструментом. С его помощью можно протестировать даже сложные сценарии.
➡️ Документация библиотеки содержит еще много интересных примеров, рекомендую ознакомиться.
Совсем недавно мы говорили про тесты во Flutter-приложениях, разобрались в видах и ситуациях использования. Сегодня поговорим об одном из самых известных инструментов для тестирования — библиотеке mocktail.
Часто в процессе написания тестов появляется необходимость имитировать работу того или иного экземпляра класса с целью проверить его работу в разных сценариях. В таком случае на помощь придет mocktail. Разберемся подробнее в возможностях этой библиотеки.
Mock и Fake — понятия, которые существуют не только в тестировании Flutter-приложений. Это так называемые тестовые двойники — классы, предназначенные для упрощения процесса тестирования и изоляции тестируемого объекта от внешних зависимостей.
Оба понятия довольно схожи, но имеются небольшие различия. Моки имитируют поведения реальных объектов, а фейки — копируют функциональность реального объекта, но с некоторым упрощением.
Подробнее понять сущность тестовых двойников поможет эта статья.
Библиотека mocktail дает доступ к Mock- и Fake-классам. Чтобы создать моки или фейки, достаточно наследоваться от них.
class FakeExample extends Fake implements Example {}
class MockExample extends Mock implements Example {}
Метод when() позволяет к тестовому объекту определить конкретное поведение.
class Example {
String read() => 'ready!';
String write(String data) => 'data recorded!';
}
final mockExample = MockExample();
test(
'testing mockExample.read()',
() {
when(mockExample.read).thenReturn('successfully read');
expect(mockExample.read(), 'successfully read!');
},
);
Метод verify() позволяет проверить, был ли вообще вызов того или иного метода, с какими параметрами и сколько раз.
Например, так мы можем проверить, что в нашем примере метод read() был вызван 1 раз.
verify(mockExample.read).called(1);
А так можно получить список параметров, с которыми был вызван метод write().
final list = verify(() => mockExample.write(captureAny())).captured;
Тут все просто — если вам нужно убрать все те настройки поведени, которые были заданы ранее, поможет метод reset().
reset(mockExample);
Как показывает практика, mocktail являтся достаточно гибким инструментом. С его помощью можно протестировать даже сложные сценарии.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6🔥3❤🔥1
Привет, это Роза, Flutter Dev Friflex👋
Если ты хоть раз писал CLI-пакет на Dart, то точно задумывался, как обрабатывать аргументы в main().
Можно, конечно, написать свой парсер, но проще воспользоваться готовым решением, например, библиотекой args. Она превращает сырые аргументы из командной строки в понятный набор параметров и значений.
Как это работает?
Внутри args реализован простой парсер на основе Map, куда добавляются опции и флаги.
При запуске команды с аргументами библиотека определяет, что передано, и возвращает готовую структуру — ArgResults.
Как работать с args?
Первым делом добавь зависимость в pubspec.yaml, затем — создай парсер:
Также ты можешь задать дополнительные параметры при инициализации:
✔️ allowTrailingOptions (по умолчанию true) — позволяет указывать флаги и параметры после позиционных аргументов
✔️ usageLineLength — задает максимальную длину строки при выводе usage
Добавление параметров
🔹 Используй addOption, если параметр принимает значение
🔹 Используй addFlag, если параметр — просто переключатель
По умолчанию у флагов работает форма --no-flag. Если это не нужно, добавь negatable: false.
Дополнительные настройки
▫️Сокращения с помощью abbr:
▫️ Значение по умолчанию:
Примеры использования
Команды с аргументами можно вызывать так:
Если заданы сокращения, то можно:
Валидация и обратные вызовы
▫️Хочешь ограничить допустимые значения? Используй allowed:
▫️ Если передано недопустимое значение — будет выброшено исключение ArgParserException
▫️ Для дополнительной логики можно также передать callback
Обязательные параметры
Если параметр помечен как mandatory: true, но не передан — при его извлечении Dart выбросит ArgumentError.
Вывод команд
Но и наконец, чтобы пользователи знали, какие аргументы доступны и как их использовать, добавь флаг --help и выведи справку через parser.usage:
📌 Рекомендую завершать выполнение программы после вывода справки, чтобы избежать лишнего кода, если help — единственный аргумент.
В посте рассмотрела не все возможности пакета: обязательно загляни в документацию на pub.dev, чтобы узнать больше.
❤️ — если пост был полезным
Если ты хоть раз писал CLI-пакет на Dart, то точно задумывался, как обрабатывать аргументы в main().
Можно, конечно, написать свой парсер, но проще воспользоваться готовым решением, например, библиотекой args. Она превращает сырые аргументы из командной строки в понятный набор параметров и значений.
Как это работает?
Внутри args реализован простой парсер на основе Map, куда добавляются опции и флаги.
При запуске команды с аргументами библиотека определяет, что передано, и возвращает готовую структуру — ArgResults.
Как работать с args?
Первым делом добавь зависимость в pubspec.yaml, затем — создай парсер:
final parser = ArgParser();
Также ты можешь задать дополнительные параметры при инициализации:
✔️ allowTrailingOptions (по умолчанию true) — позволяет указывать флаги и параметры после позиционных аргументов
✔️ usageLineLength — задает максимальную длину строки при выводе usage
Добавление параметров
🔹 Используй addOption, если параметр принимает значение
parser.addOption('output');
🔹 Используй addFlag, если параметр — просто переключатель
parser.addOption('env', defaultsTo: 'dev');
По умолчанию у флагов работает форма --no-flag. Если это не нужно, добавь negatable: false.
Дополнительные настройки
▫️Сокращения с помощью abbr:
parser.addFlag('screenshot', abbr: 's');
▫️ Значение по умолчанию:
parser.addOption('env', defaultsTo: 'dev');
Примеры использования
Команды с аргументами можно вызывать так:
dart run command_name --verbose=false
dart run command_name --output=somevalue
Если заданы сокращения, то можно:
dart run command_name -v=false
dart run command_name -o=somevalue
Валидация и обратные вызовы
▫️Хочешь ограничить допустимые значения? Используй allowed:
parser.addOption('mode', allowed: ['dev', 'prod']);
▫️ Если передано недопустимое значение — будет выброшено исключение ArgParserException
▫️ Для дополнительной логики можно также передать callback
Обязательные параметры
Если параметр помечен как mandatory: true, но не передан — при его извлечении Dart выбросит ArgumentError.
Вывод команд
Но и наконец, чтобы пользователи знали, какие аргументы доступны и как их использовать, добавь флаг --help и выведи справку через parser.usage:
parser.addFlag(
'help',
abbr: 'h',
help: 'Show help information for available commands',
negatable: false,
);
if (results['help'] as bool) {
print('Command help:\n');
print(parser.usage);
exit(0);
}
📌 Рекомендую завершать выполнение программы после вывода справки, чтобы избежать лишнего кода, если help — единственный аргумент.
В посте рассмотрела не все возможности пакета: обязательно загляни в документацию на pub.dev, чтобы узнать больше.
❤️ — если пост был полезным
❤8👍4🔥3
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, это Катя, Flutter Dev Friflex👋
Сегодня разберемся что такое pubspec.yaml, зачем он нужен и что содержит.
Что это?
Файл pubspec.yaml — это настройка Flutter-приложения, без которого оно не соберется.
Что внутри этого файла?
1. Название и описание
2. Какие версии Dart и Flutter нужны
3. Библиотеки (дополнительные функции)
После добавления библиотек нужно запустить flutter pub get, чтобы их скачать.
4. Библиотеки только для разработки
5. Картинки, шрифты и другие файлы
Как обновлять библиотеки?
▫️Добавили новую библиотеку в pubspec.yaml?
✔️Запустите flutter pub get (чтобы скачать)
▫️Хотите обновить все библиотеки?
✔️Запустите flutter pub upgrade
▫️Хотите проверить, какие библиотеки устарели?
✔️Запустите flutter pub outdated
Важно:
▪️Если добавили картинку или шрифт, но забыли прописать его в pubspec.yaml — приложение не увидит этот файл
▪️Если указали не ту версию библиотеки — приложение может не запуститься
Этот файл — как инструкция для Flutter, где все должно быть четко прописано🚀
Сегодня разберемся что такое pubspec.yaml, зачем он нужен и что содержит.
Что это?
Файл pubspec.yaml — это настройка Flutter-приложения, без которого оно не соберется.
Что внутри этого файла?
1. Название и описание
Yaml
name: my_app # Название приложения (только английскими буквами, без пробелов)
description: Моё первое приложение # Описание (можно на любом языке)
version: 1.0.0+1 # Версия (1.0.0 — основная, +1 — номер сборки)
2. Какие версии Dart и Flutter нужны
Yaml
environment:
sdk: ">=2.12.0 <3.0.0" # Dart версии от 2.12.0 до 3.0.0
flutter: ">=2.0.0" # Flutter версии от 2.0.0
3. Библиотеки (дополнительные функции)
Yaml
dependencies:
flutter:
sdk: flutter # Обязательная строка для Flutter-приложений
http: ^0.13.3 # Библиотека для работы с интернетом
provider: ^6.0.0 # Библиотека для управления состоянием приложения
После добавления библиотек нужно запустить flutter pub get, чтобы их скачать.
4. Библиотеки только для разработки
Yaml
dev_dependencies:
flutter_test:
sdk: flutter # Нужно для тестирования
build_runner: ^2.1.0 # Помогает генерировать код
5. Картинки, шрифты и другие файлы
Yaml
flutter:
uses-material-design: true # Включает стандартные иконки Google
assets:
- assets/images/ # Папка с картинками
- assets/logo.png # Конкретный файл
fonts:
- family: Roboto # Шрифт Roboto
fonts:
- asset: assets/fonts/Roboto-Regular.ttf
Как обновлять библиотеки?
▫️Добавили новую библиотеку в pubspec.yaml?
✔️Запустите flutter pub get (чтобы скачать)
▫️Хотите обновить все библиотеки?
✔️Запустите flutter pub upgrade
▫️Хотите проверить, какие библиотеки устарели?
✔️Запустите flutter pub outdated
Важно:
▪️Если добавили картинку или шрифт, но забыли прописать его в pubspec.yaml — приложение не увидит этот файл
▪️Если указали не ту версию библиотеки — приложение может не запуститься
Этот файл — как инструкция для Flutter, где все должно быть четко прописано🚀
🔥11❤7👍2
Привет, с вами Анна, Friflex Flutter Team Lead.
С каждым месяцем разработка и портирование Flutter-приложений на ОС Аврора становится все более популярным запросом у заказчиков. Этот повышенный спрос рождает у разработчиков все больше интереса к операционной системе и работе с ней.
На Youtube-канале Friflex мой коллега Юрий Петров @mobile_developing выпустил серию видео, которые помогут вам подготовиться к разработке приложений для ОС Аврора, а также подскажут, как просто и быстро портировать Flutter-приложение. Особенно рекомендую видео:
▫️Полный гайд по установке и настройке Flutter Aurora
▫️Пример портирования Wonderous на Аврора.
Кто уже разрабатывал Flutter-приложения под ОС Аврора знают, что первым шагом необходимо добавить папку с нативным кодом, которая обеспечит поддержку системы. Для этого в терминале достаточно выполнить команду:
После успешного выполнения команды в корне проекта будет создана папка
▪️Папка desktop и файл .desktop
Файл .desktop является основной конфигурацией вашего приложения. Именно здесь вы можете менять название самого приложения, иконку, запрашиваемые разрешения. Выглядит содержимое файла следующим образом⬇️
Основные поля, которые могут вам потребоваться, пометила комментариями с описанием.
Здесь важно обратить внимание на поле Permissions — именно сюда необходимо добавлять все разрешения, которые требует функционал вашего приложения. Все эти разрешения будут запрошены одномоментно при запуске приложения. Если пользователь не согласится предоставить к ним доступ, приложение не запустится.
▪️Папка flutter
Сюда автоматически генерируются файлы для подключения плагинов. Все сгенерированные здесь файлы не рекомендую менять вручную.
▪️Папка icons
Здесь хранятся иконки приложения в png-формате в 4-х разрешениях — 86x86, 108x108, 128x128, 172x172. Если вы хотите заменить иконку, вам достаточно заменить картинки-заглушки своими изображениями. Важно — разрешения и формат должны сохраниться.
▪️Папка rpm
Здесь хранится еще один важный файл конфигурации .spec. Этот файл нечто сродни всем нам знакомому pubspec.yaml. Здесь мы указываем основную информацию о приложении:
Важно — версия приложения на Авроре не подтягивается из pubspec.yaml, как на Android или iOS. Ее требуется поднимать именно здесь.
Также в этом файле при подключении плагинов по необходимости можно указывать зависимости под ключом
А вы уже сталкивались с разработкой приложений для ОС Аврора?
С каждым месяцем разработка и портирование Flutter-приложений на ОС Аврора становится все более популярным запросом у заказчиков. Этот повышенный спрос рождает у разработчиков все больше интереса к операционной системе и работе с ней.
На Youtube-канале Friflex мой коллега Юрий Петров @mobile_developing выпустил серию видео, которые помогут вам подготовиться к разработке приложений для ОС Аврора, а также подскажут, как просто и быстро портировать Flutter-приложение. Особенно рекомендую видео:
▫️Полный гайд по установке и настройке Flutter Aurora
▫️Пример портирования Wonderous на Аврора.
Кто уже разрабатывал Flutter-приложения под ОС Аврора знают, что первым шагом необходимо добавить папку с нативным кодом, которая обеспечит поддержку системы. Для этого в терминале достаточно выполнить команду:
flutter-aurora create --platforms=aurora --org={название_организации} .
После успешного выполнения команды в корне проекта будет создана папка
aurora
. И сегодня мы подробно изучим ее содержимое. ▪️Папка desktop и файл .desktop
Файл .desktop является основной конфигурацией вашего приложения. Именно здесь вы можете менять название самого приложения, иконку, запрашиваемые разрешения. Выглядит содержимое файла следующим образом⬇️
Основные поля, которые могут вам потребоваться, пометила комментариями с описанием.
[Desktop Entry]
Type=Application
Name=Example // Название приложения (только на английском)
Name[ru]=Приложение-пример // Название приложения на русском
Comment=Мобильное приложение для сети магазинов Example. // Описание приложения
Icon=ru.example.mobile.aurora.example_mobile // Иконка приложения
Exec=/usr/bin/ru.example.mobile.aurora.example_mobile
X-Nemo-Application-Type=silica-qt5
[X-Application]
Permissions=DeviceInfo;Camera;Internet;UserDirs;SecureStorage // Запрашиваемые разрешения
OrganizationName=ru.example.mobile.aurora // Название организации, которое было указано при создании папки aurora
ApplicationName=example_mobile
Здесь важно обратить внимание на поле Permissions — именно сюда необходимо добавлять все разрешения, которые требует функционал вашего приложения. Все эти разрешения будут запрошены одномоментно при запуске приложения. Если пользователь не согласится предоставить к ним доступ, приложение не запустится.
▪️Папка flutter
Сюда автоматически генерируются файлы для подключения плагинов. Все сгенерированные здесь файлы не рекомендую менять вручную.
▪️Папка icons
Здесь хранятся иконки приложения в png-формате в 4-х разрешениях — 86x86, 108x108, 128x128, 172x172. Если вы хотите заменить иконку, вам достаточно заменить картинки-заглушки своими изображениями. Важно — разрешения и формат должны сохраниться.
▪️Папка rpm
Здесь хранится еще один важный файл конфигурации .spec. Этот файл нечто сродни всем нам знакомому pubspec.yaml. Здесь мы указываем основную информацию о приложении:
Name: ru.example.mobile.aurora.example_mobile // Название бандла
Summary: Мобильное приложение для сети магазинов Example // Описание продукта, которое будет отображено при запуске
Version: 1.0.1 // Версия приложения
Release: 20 // Версия сборки
Важно — версия приложения на Авроре не подтягивается из pubspec.yaml, как на Android или iOS. Ее требуется поднимать именно здесь.
Также в этом файле при подключении плагинов по необходимости можно указывать зависимости под ключом
BuildRequires:
BuildRequires: pkgconfig(streamcamera)
BuildRequires: pkgconfig(sqlite3)
BuildRequires: pkgconfig(runtime-manager-qt5)
А вы уже сталкивались с разработкой приложений для ОС Аврора?
❤7🔥4👍2🥰1
Привет, это Роза, Flutter Dev Friflex! 👋
Представь ситуацию: тебе нужно выполнить определенный код только после того, как интерфейс полностью будет отрисован. Как быть? Конечно же, использовать addPostFrameCallback! Давайте разберем его.
Немного теории и фактов
▫️addPostFrameCallback – это метод класса WidgetsBinding, который наследуется от SchedulerBinding во Flutter
▫️ Он позволяет зарегистрировать функцию обратного вызова, которая появится после завершения отрисовки текущего кадра, но до начала следующего
▫️ Обратный вызов выполняется один раз и не может быть отменен
▫️ В качестве параметра обратный вызов получает Duration с указанием времени его вызова
Когда это может быть полезно?
▪️Показ диалогового окна после полной визуализации дерева виджетов
▪️Прокрутка к нужной позиции после построения UI
▪️Программная прокрутка к определенной позиции после построения интерфейса
▪️Запуск анимаций, зависящих от уже построенного UI
Ты можешь подумать: «Я же могу использовать StatefulWidget и реализовать все в initState без всяких addPostFrameCallback! В чем же польза?»
На самом деле, добавлять такую логику в initState — не самая лучшая идея. Вот почему:
▫️ initState вызывается до первой отрисовки виджета. Если ты попытаешься получить context или размеры виджетов в initState, ты можешь словить ошибку или просто получить нулевые значения, потому что виджеты еще не отрисованы и не имеют реальных размеров и позиций
▫️Если ты вызовешь setState() в initState, когда build() еще не закончился, ты получишь ошибку, похожую на > setState() or markNeedsBuild() called during build
▫️addPostFrameCallback, в отличие от этого, гарантирует, что все уже отрисовано и можно спокойно работать с контекстом
И осталось самое главное — разобраться, как использовать. Представим, нам нужно показать уведомление после того, как экран будет полностью отрисован.
На заметку:
✔️ Не используй addPostFrameCallback для тяжелых операций — они могут замедлить отрисовку следующего кадра
✔️ Если тебе нужно выполнять действия на каждом кадре, то addPostFrameCallback не подойдет
✔️ А если просто нужно отложить выполнение кода без привязки к UI — можно использовать Future.delayed или Future.microtask.
На этом все! А сейчас предлагаю выполнить addPostFrameCallback по версии Telegram и поставить ❤️
Представь ситуацию: тебе нужно выполнить определенный код только после того, как интерфейс полностью будет отрисован. Как быть? Конечно же, использовать addPostFrameCallback! Давайте разберем его.
Немного теории и фактов
▫️addPostFrameCallback – это метод класса WidgetsBinding, который наследуется от SchedulerBinding во Flutter
▫️ Он позволяет зарегистрировать функцию обратного вызова, которая появится после завершения отрисовки текущего кадра, но до начала следующего
▫️ Обратный вызов выполняется один раз и не может быть отменен
▫️ В качестве параметра обратный вызов получает Duration с указанием времени его вызова
Когда это может быть полезно?
▪️Показ диалогового окна после полной визуализации дерева виджетов
▪️Прокрутка к нужной позиции после построения UI
▪️Программная прокрутка к определенной позиции после построения интерфейса
▪️Запуск анимаций, зависящих от уже построенного UI
Ты можешь подумать: «Я же могу использовать StatefulWidget и реализовать все в initState без всяких addPostFrameCallback! В чем же польза?»
На самом деле, добавлять такую логику в initState — не самая лучшая идея. Вот почему:
▫️ initState вызывается до первой отрисовки виджета. Если ты попытаешься получить context или размеры виджетов в initState, ты можешь словить ошибку или просто получить нулевые значения, потому что виджеты еще не отрисованы и не имеют реальных размеров и позиций
▫️Если ты вызовешь setState() в initState, когда build() еще не закончился, ты получишь ошибку, похожую на > setState() or markNeedsBuild() called during build
▫️addPostFrameCallback, в отличие от этого, гарантирует, что все уже отрисовано и можно спокойно работать с контекстом
И осталось самое главное — разобраться, как использовать. Представим, нам нужно показать уведомление после того, как экран будет полностью отрисован.
class _SomeScreenState extends State<SomeScreen> {
@override
void initState() {
super.initState();
// Показываем snackbar после полной отрисовки интерфейса
WidgetsBinding.instance.addPostFrameCallback((_) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Some text')),
);
});
}
// build method
На заметку:
✔️ Не используй addPostFrameCallback для тяжелых операций — они могут замедлить отрисовку следующего кадра
✔️ Если тебе нужно выполнять действия на каждом кадре, то addPostFrameCallback не подойдет
✔️ А если просто нужно отложить выполнение кода без привязки к UI — можно использовать Future.delayed или Future.microtask.
На этом все! А сейчас предлагаю выполнить addPostFrameCallback по версии Telegram и поставить ❤️
❤17👍6🔥3👌1
Привет, это Катя, Flutter Dev Friflex. Сегодня обсудим AutomaticKeepAliveClientMixin.
Когда создаем приложение с вкладками или списками, может возникнуть такая ситуация: пользователь переключается между вкладками, а состояние старой вкладки сбрасывается. Такое может раздражать, особенно если это список, который прокрутили до 155 номера, видео или текст.
Проблема
Допустим, у нас есть 3 вкладки, и каждая из них — это StatefulWidget с вложенными виджетами. Когда мы перелистываем между ними, Flutter по умолчанию «выгружает» неактивную вкладку, чтобы экономить память.
Что происходит:
Проскроллили вниз на 1-й вкладке ➡️ переключились на 2-ю ➡️
вернулись обратно на 1-ю ➡️
все обнулилось — нас кинуло в самое начало.
Решение
AutomaticKeepAliveClientMixin — это миксин, который говорит Flutter: «Пожалуйста, не выгружай меня, даже если я временно не на экране». Он работает вместе с PageStorage — специальным механизмом Flutter для хранения состояний виджетов.
Как использовать
На коленке наклепали такой виджет, который каждый раз будет сбрасывать свое состояние:
Теперь добавляем AutomaticKeepAliveClientMixin, чтобы этого не происходило:
❕Миксин работает только в тех местах, где Flutter может использовать KeepAlive, например, в TabBarView, PageView, ListView.builder, внутри PageStorage.
Где использовать
▫️Вкладки (TabBarView), чтобы запоминать положение и состояние
▫️Страницы в PageView, например, в onboarding-экранах
▫️Динамические списки, где хотим сохранить scroll-положение и состояние каждого элемента
Как быстро проверить, что KeepAlive работает
Работает, если initState called будет вызван только один раз:
📎Документы по миксину — здесь
🔥 — если использовали AutomaticKeepAliveClientMixin на практике
Когда создаем приложение с вкладками или списками, может возникнуть такая ситуация: пользователь переключается между вкладками, а состояние старой вкладки сбрасывается. Такое может раздражать, особенно если это список, который прокрутили до 155 номера, видео или текст.
Проблема
Допустим, у нас есть 3 вкладки, и каждая из них — это StatefulWidget с вложенными виджетами. Когда мы перелистываем между ними, Flutter по умолчанию «выгружает» неактивную вкладку, чтобы экономить память.
Что происходит:
Проскроллили вниз на 1-й вкладке ➡️ переключились на 2-ю ➡️
вернулись обратно на 1-ю ➡️
все обнулилось — нас кинуло в самое начало.
Решение
AutomaticKeepAliveClientMixin — это миксин, который говорит Flutter: «Пожалуйста, не выгружай меня, даже если я временно не на экране». Он работает вместе с PageStorage — специальным механизмом Flutter для хранения состояний виджетов.
Как использовать
На коленке наклепали такой виджет, который каждый раз будет сбрасывать свое состояние:
class MyTab extends StatefulWidget {
@override
_MyTabState createState() => _MyTabState();
}
class _MyTabState extends State<MyTab> {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 100,
itemBuilder: (_, index) => ListTile(title: Text('Item $index')),
);
}
}
Теперь добавляем AutomaticKeepAliveClientMixin, чтобы этого не происходило:
class _MyTabState extends State<MyTab> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true; // СУПЕР ВАЖНО
@override
Widget build(BuildContext context) {
super.build(context); // ОБЯЗАТЕЛЬНО ВЫЗЫВАТЬ
return ListView.builder(
itemCount: 100,
itemBuilder: (_, index) => ListTile(title: Text('Item $index')),
);
}
}
❕Миксин работает только в тех местах, где Flutter может использовать KeepAlive, например, в TabBarView, PageView, ListView.builder, внутри PageStorage.
Где использовать
▫️Вкладки (TabBarView), чтобы запоминать положение и состояние
▫️Страницы в PageView, например, в onboarding-экранах
▫️Динамические списки, где хотим сохранить scroll-положение и состояние каждого элемента
Как быстро проверить, что KeepAlive работает
Работает, если initState called будет вызван только один раз:
@override
void initState() {
super.initState();
print('initState called');
}
📎Документы по миксину — здесь
🔥 — если использовали AutomaticKeepAliveClientMixin на практике
👍17❤6🔥5
Всем привет! Это Анна, Friflex Flutter Team Lead👋
Каждый Flutter-разработчик в своей работе хотя бы раз сталкивался с необходимостью получить какую-то информацию из платформы или использовать определенный ее функционал. И чаще всего для таких целей используются уже готовые решения в виде плагинов. Сегодня же мы поговорим о том, как настроить вручную общение с платформой, и разберем на примере того, как это делают плагины.
Основной механизм общения нативного и Dart-кода во Flutter-приложениях — Platform Channels. Это общий термин для названия всех видов каналов.
Есть три вида платформенных каналов:
▫️ MethodChannel — канал для одноразовых асинхронных вызовов методов платформы. Работает по принципу запрос-ответ. Через MethodChannel методом
▫️ EventChannel — канал для непрерывного общения с платформой. Работает в формате потоков событий от платформы во Flutter
▫️ BasicMessageChannel — канал для простого асинхронного обмена сообщениями с платформой
❕При создании каналов каждому необходимо задавать свой уникальный строковый идентификатор. С помощью него платформа и Flutter «стыкуются» — образуется некий мост, по которому идет передача данных.
На примере всеми известного плагина permission_handler рассмотрим, как настраивается общение с платформой.
В Dart-коде создается экземпляр MethodChannel.
Затем этот канал используется в методах для запроса платформенных данных. Например, метод
В нативном коде Android тоже создается экземпляр канала, к нему подключается обработчик вызовов.
В самом нативном обработчике уже задается обработка вызываемых методов. Например, так обрабатывается запрос
❤️ — если пост был полезен
Каждый Flutter-разработчик в своей работе хотя бы раз сталкивался с необходимостью получить какую-то информацию из платформы или использовать определенный ее функционал. И чаще всего для таких целей используются уже готовые решения в виде плагинов. Сегодня же мы поговорим о том, как настроить вручную общение с платформой, и разберем на примере того, как это делают плагины.
Основной механизм общения нативного и Dart-кода во Flutter-приложениях — Platform Channels. Это общий термин для названия всех видов каналов.
Есть три вида платформенных каналов:
▫️ MethodChannel — канал для одноразовых асинхронных вызовов методов платформы. Работает по принципу запрос-ответ. Через MethodChannel методом
invokeMethod
Flutter делает запрос в платформу, которая затем возвращает некоторый результат▫️ EventChannel — канал для непрерывного общения с платформой. Работает в формате потоков событий от платформы во Flutter
▫️ BasicMessageChannel — канал для простого асинхронного обмена сообщениями с платформой
❕При создании каналов каждому необходимо задавать свой уникальный строковый идентификатор. С помощью него платформа и Flutter «стыкуются» — образуется некий мост, по которому идет передача данных.
На примере всеми известного плагина permission_handler рассмотрим, как настраивается общение с платформой.
В Dart-коде создается экземпляр MethodChannel.
const MethodChannel _methodChannel = MethodChannel('flutter.baseflow.com/permissions/methods');
Затем этот канал используется в методах для запроса платформенных данных. Например, метод
checkPermissionStatus
запрашивает статус текущего разрешения.
@override
Future<PermissionStatus> checkPermissionStatus(Permission permission) async {
final status = await _methodChannel.invokeMethod(
'checkPermissionStatus', permission.value);
return decodePermissionStatus(status);
}
В нативном коде Android тоже создается экземпляр канала, к нему подключается обработчик вызовов.
private void startListening(Context applicationContext, BinaryMessenger messenger) {
methodChannel = new MethodChannel(
messenger,
"flutter.baseflow.com/permissions/methods");
methodCallHandler = new MethodCallHandlerImpl(
applicationContext,
new AppSettingsManager(),
this.permissionManager,
new ServiceManager()
);
methodChannel.setMethodCallHandler(methodCallHandler);
}
В самом нативном обработчике уже задается обработка вызываемых методов. Например, так обрабатывается запрос
checkPermissionStatus
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) {
switch (call.method) {
...
case "checkPermissionStatus": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
permissionManager.checkPermissionStatus(
permission,
result::success);
break;
}
...
}
}
❤️ — если пост был полезен
❤13🔥2🤯1
Какой тип Platform Channel вам приходится использовать чаще всего?
Anonymous Poll
91%
MethodChannel
7%
EventChannel
1%
BasicMessageChannel
Привет, это Роза, Flutter Dev Friflex! 👋
В любой разработке рано или поздно придется столкнуться с такими аббревиатурами, как KISS, DRY и YAGNI. Чтобы в следующий раз точно знать, что они означают и как их расшифровывать, давай разберемся, что к чему.
Зачем все это?
Ты нарушаешь базовые архитектурные принципы, если в твоем коде:
▫️Повторяются фрагменты — одна и та же логика дублируется в нескольких местах
▫️Сложная логика — код трудно читать и поддерживать
▫️Есть «код на будущее» — ты добавляешь функции и классы, которые сейчас не нужны, но «вдруг пригодятся»
Такой код быстро превращается в ад, и его сложно поддерживать. Именно для этого и существуют три основных правила:
☑️ KISS (Keep It Simple, Stupid) — делай проще
☑️ DRY (Don't Repeat Yourself) — не повторяйся
☑️ YAGNI (You Aren’t Gonna Need It) — тебе это не понадобится
Это не просто модные слова — это база, которая делает твой код чище, понятнее и легче в поддержке.
KISS: Keep It Simple, Stupid — Будь проще!
Принцип KISS говорит: не усложняй! Простые решения всегда выигрывают, потому что их легче поддерживать. Чем проще код, тем легче его читать, понимать и изменять в будущем. Сложные архитектуры, перегруженные конструкции и «гениальные» решения часто оборачиваются проблемами.
В чем суть KISS:
▫️ Ясность — код должен быть понятным с первого взгляда
▫️ Эффективность — никаких избыточных решений
▫️ Гибкость — простые вещи проще адаптировать
▫️ Удобство — такой код приятно читать и трогать
▫️ Меньше багов — чем проще, тем меньше мест для ошибок
KISS — это стремление найти простое, но рабочее решение. Не примитивное, а минималистичное и логичное.
DRY: Don't Repeat Yourself — Не повторяйся!
DRY — это про избавление от дублирования. Если один и тот же кусок кода повторяется — его нужно вынести в отдельную функцию, класс или модуль. Это не только уменьшает размер кода, но и ускоряет поддержку: меняешь в одном месте — все работает.
Как применять DRY:
▫️ Выделяй повторяющуюся логику в переиспользуемые блоки
▫️ Избегай копипаста — он приводит к ошибкам
▫️ Держи один источник истины для каждой логики или сущности
DRY помогает держать код чистым, логичным и легко изменяемым.
YAGNI: You Aren’t Gonna Need It — Тебе это не понадобится!
YAGNI напоминает: не пиши код «на будущее», если в нем нет реальной необходимости прямо сейчас. То, что может вдруг пригодиться, скорее всего не пригодится вовсе. А если и пригодится, то в другой форме.
Что говорит YAGNI:
▫️ Делай только то, что нужно прямо сейчас
▫️ Не добавляй фичи «на будущее», если на них нет задачи
▫️ Сфокусируйся на реальных требованиях, а не на гипотетических сценариях
Чем меньше «запасного» кода — тем чище проект, и ,соответственно, меньше багов сейчас и в будущем.
Если ты уже пользуешься этими принципами, ставь ❤️
В любой разработке рано или поздно придется столкнуться с такими аббревиатурами, как KISS, DRY и YAGNI. Чтобы в следующий раз точно знать, что они означают и как их расшифровывать, давай разберемся, что к чему.
Зачем все это?
Ты нарушаешь базовые архитектурные принципы, если в твоем коде:
▫️Повторяются фрагменты — одна и та же логика дублируется в нескольких местах
▫️Сложная логика — код трудно читать и поддерживать
▫️Есть «код на будущее» — ты добавляешь функции и классы, которые сейчас не нужны, но «вдруг пригодятся»
Такой код быстро превращается в ад, и его сложно поддерживать. Именно для этого и существуют три основных правила:
☑️ KISS (Keep It Simple, Stupid) — делай проще
☑️ DRY (Don't Repeat Yourself) — не повторяйся
☑️ YAGNI (You Aren’t Gonna Need It) — тебе это не понадобится
Это не просто модные слова — это база, которая делает твой код чище, понятнее и легче в поддержке.
KISS: Keep It Simple, Stupid — Будь проще!
Принцип KISS говорит: не усложняй! Простые решения всегда выигрывают, потому что их легче поддерживать. Чем проще код, тем легче его читать, понимать и изменять в будущем. Сложные архитектуры, перегруженные конструкции и «гениальные» решения часто оборачиваются проблемами.
В чем суть KISS:
▫️ Ясность — код должен быть понятным с первого взгляда
▫️ Эффективность — никаких избыточных решений
▫️ Гибкость — простые вещи проще адаптировать
▫️ Удобство — такой код приятно читать и трогать
▫️ Меньше багов — чем проще, тем меньше мест для ошибок
KISS — это стремление найти простое, но рабочее решение. Не примитивное, а минималистичное и логичное.
DRY: Don't Repeat Yourself — Не повторяйся!
DRY — это про избавление от дублирования. Если один и тот же кусок кода повторяется — его нужно вынести в отдельную функцию, класс или модуль. Это не только уменьшает размер кода, но и ускоряет поддержку: меняешь в одном месте — все работает.
Как применять DRY:
▫️ Выделяй повторяющуюся логику в переиспользуемые блоки
▫️ Избегай копипаста — он приводит к ошибкам
▫️ Держи один источник истины для каждой логики или сущности
DRY помогает держать код чистым, логичным и легко изменяемым.
YAGNI: You Aren’t Gonna Need It — Тебе это не понадобится!
YAGNI напоминает: не пиши код «на будущее», если в нем нет реальной необходимости прямо сейчас. То, что может вдруг пригодиться, скорее всего не пригодится вовсе. А если и пригодится, то в другой форме.
Что говорит YAGNI:
▫️ Делай только то, что нужно прямо сейчас
▫️ Не добавляй фичи «на будущее», если на них нет задачи
▫️ Сфокусируйся на реальных требованиях, а не на гипотетических сценариях
Чем меньше «запасного» кода — тем чище проект, и ,соответственно, меньше багов сейчас и в будущем.
Если ты уже пользуешься этими принципами, ставь ❤️
❤13🔥4⚡1🥴1
Привет, это Катя, Flutter Dev Friflex. Сегодня расскажу про архитектурный подход MVP — один из способов организации кода в приложениях.
Что такое MVP
MVP расшифровывается как Model — View — Presenter и разделяет логику приложения на три компонента:
🔴 Model (Модель) — отвечает за данные и бизнес-логику. Она получает или сохраняет информацию, например, из API или локальной базы данных
🔴 View (Представление) — пассивный слой, отображающий данные на экране. View не содержит логики — она лишь сообщает презентеру о действиях пользователя и отображает то, что пришло от него
🔴 Presenter (Презентер) — посредник между моделью и представлением. Он обрабатывает пользовательские действия, запрашивает данные из модели, форматирует их и передает обратно в View
Поток коммуникации
➡️ Пользователь взаимодействует с View (например, нажимает кнопку)
➡️ View передает событие презентеру
➡️ Презентер запрашивает или обновляет данные в модели
➡️ Получив данные, презентер подготавливает их и отправляет обратно во View для отображения
View — самый поверхностный слой, он работает только с презентером, не зная ничего о модели напрямую. Это облегчает тестирование и делает код более модульным.
Пример
Представим экран входа в приложение:
🔸 View: показывает два текстовых поля (логин и пароль) и кнопку «Войти». Передает ввод пользователю презентеру при нажатии на кнопку
🔸 Presenter: получает логин и пароль, проверяет их (например, что они не пустые), отправляет их в модель
🔸 Model: выполняет запрос к серверу, возвращает результат (успешный вход или ошибка)
🔸 Presenter: получает результат, сообщает View показать следующий экран или отобразить сообщение об ошибке
Почему это удобно?
🔴 Презентер содержит бизнес-логику и отделен от UI
🔴 View можно переиспользовать или подменять, не затрагивая логику
🔴 Легко писать юнит-тесты, особенно для презентера и модели
🔴 Каждый View связан с одним презентером (но при необходимости может использовать несколько)
На блок-схеме можно посмотреть, как компоненты взаимодействуют между собой⬆️
Что такое MVP
MVP расшифровывается как Model — View — Presenter и разделяет логику приложения на три компонента:
Поток коммуникации
View — самый поверхностный слой, он работает только с презентером, не зная ничего о модели напрямую. Это облегчает тестирование и делает код более модульным.
Пример
Представим экран входа в приложение:
Почему это удобно?
На блок-схеме можно посмотреть, как компоненты взаимодействуют между собой
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6❤2👎1
Привет! С вами вновь Анна, Friflex Flutter Team Lead👋
Flutter-проекты поддерживают три основных режима сборки — debug, profile и release. Сегодня поговорим о каждом из них и разберемся, чем они отличаются и когда их лучше использовать.
◾️Debug-режим
Дословно переводится как «режим отладки». Этот режим самый популярный в работе разработчиков.
Именно debug-режим позволяет запустить приложение на симуляторах, эмуляторах и физических устройствах под полным контролем выполнения кода. В нем легко можно отслеживать значения переменных, останавливать выполнение по breakpoints (точкам останова).
Кроме этого, режим отладки поддерживает hot reload, что позволяет вам без пересборки приложения протестировать измененный код.
Еще одна очень удобная способность debug-режима — вы можете в рантайме подключать дополнительные инструменты, такие как DevTools и Flutter inspector.
◾️Profile-режим
Этот режим — что-то среднее между debug- и release-режимами. Визуально на физическом устройстве profile сборка не особо отличается от release, но в ней сохраняется возможность подключить некоторые иструменты, например, DevTools.
Как показывает практика, этот режим используется разработчиками наиболее редко, а зря! Сборки в этом режиме вам могут потребоваться, когда стоит цель исследовать производительность приложения, оптимизировать код.
Если ваше приложение работает медленно, требуется проверить и оптимизировать сложные анимации, сборка в режиме profile — ваш лучший помощник.
Но здесь важно помнить, что profile-режим не поддерживается эмуляторами и симуляторами. Такие сборки стоит запускать на физических устройствах, чтобы отследить реальную производительность.
◾️Release-режим
Это режим выпуска. Сборки именно в этом режиме загружаются в сторы, поступают конечному пользователю и передаются тестировщикам, если не было запроса на другой режим.
Сборка в release-режиме максимально оптимизирована, в отличие от debug- и profile-сборок. Подключить дополнительные инструменты к такой сборке невозможно, вся отладочная информация полностью очищается, прервать выполнение программы нельзя.
Как запустить сборку в разных режимах?
Все очень просто! Стандартная команда
📎Еще больше деталей — в официальной документации Flutter.
Flutter-проекты поддерживают три основных режима сборки — debug, profile и release. Сегодня поговорим о каждом из них и разберемся, чем они отличаются и когда их лучше использовать.
◾️Debug-режим
Дословно переводится как «режим отладки». Этот режим самый популярный в работе разработчиков.
Именно debug-режим позволяет запустить приложение на симуляторах, эмуляторах и физических устройствах под полным контролем выполнения кода. В нем легко можно отслеживать значения переменных, останавливать выполнение по breakpoints (точкам останова).
Кроме этого, режим отладки поддерживает hot reload, что позволяет вам без пересборки приложения протестировать измененный код.
Еще одна очень удобная способность debug-режима — вы можете в рантайме подключать дополнительные инструменты, такие как DevTools и Flutter inspector.
◾️Profile-режим
Этот режим — что-то среднее между debug- и release-режимами. Визуально на физическом устройстве profile сборка не особо отличается от release, но в ней сохраняется возможность подключить некоторые иструменты, например, DevTools.
Как показывает практика, этот режим используется разработчиками наиболее редко, а зря! Сборки в этом режиме вам могут потребоваться, когда стоит цель исследовать производительность приложения, оптимизировать код.
Если ваше приложение работает медленно, требуется проверить и оптимизировать сложные анимации, сборка в режиме profile — ваш лучший помощник.
Но здесь важно помнить, что profile-режим не поддерживается эмуляторами и симуляторами. Такие сборки стоит запускать на физических устройствах, чтобы отследить реальную производительность.
◾️Release-режим
Это режим выпуска. Сборки именно в этом режиме загружаются в сторы, поступают конечному пользователю и передаются тестировщикам, если не было запроса на другой режим.
Сборка в release-режиме максимально оптимизирована, в отличие от debug- и profile-сборок. Подключить дополнительные инструменты к такой сборке невозможно, вся отладочная информация полностью очищается, прервать выполнение программы нельзя.
Как запустить сборку в разных режимах?
Все очень просто! Стандартная команда
flutter run
по умолчанию запускает сборку в debug-режиме. Чтобы запустить в profile-режиме, стоит добавить опцию --profile
, в release-режиме — --release
.
flutter run
flutter run --profile
flutter run --release
📎Еще больше деталей — в официальной документации Flutter.
❤8🔥5❤🔥1
Anonymous Quiz
14%
Debug
74%
Profile
12%
Release
Всем привет! Это Роза, Flutter Dev в Friflex👋
Мы запускаем серию постов о принципах объектно-ориентированного программирования (ООП). Вы скажете, что это база, но и ее нужно иногда повторять. Поехали🚀
Dart — язык с ООП-подходом, поэтому без его ключевых принципов сложно представить разработку. Для начала разберемся, что вообще такое ООП.
Что такое ООП?
Объектно-ориентированное программирование (ООП) — это парадигма, основанная на использовании объектов, которые содержат данные (в виде полей) и поведение (в виде методов).
Звучит сложно? Если проще: объект — это экземпляр класса, а класс описывает структуру и поведение этих объектов.
ООП помогает:
✔️ структурировать код
✔️ переиспользовать функциональность
✔️ облегчить масштабирование и поддержку проекта
Основные принципы ООП:
▫️ наследование
▫️ инкапсуляция
▫️ полиморфизм
▫️ абстракция
Сегодня разберем наследование.
Что такое наследование?
Наследование — это механизм, который позволяет одному классу (дочернему) унаследовать свойства и методы другого класса (родительского).
Представьте, что вы пишете посты для Telegram. У вас есть базовый класс Article с методами write(), publish() полями title и author:
Вместо дублирования логики — создаем дочерний класс и наследуем поведение:
Используем:
В итоге:
▫️Базовый класс — содержит общую функциональность
▫️Дочерний класс — расширяет или переопределяет поведение
А что насчет наследования в dart?
В dart наследование реализуется через ключевое слово
📝 Прежде чем создавать базовый класс, загляните в пост о модификаторах классов.
Кроме того, Dart использует mixin-based inheritance. Класс может наследовать только один суперкласс (
Кстати, в Dart можно не только наследовать реализацию через extends, но и реализовывать интерфейсы с помощью implements. Тогда ты сам переопределяешь все методы, но это уже не наследование поведения.
Композиция vs наследование
Наследование — не единственный способ повторного использования кода. Есть композиция, когда один объект включается в другой, как поле. Такой подход часто делает код более гибким и читаемым. Об этом — в следующих постах.
В двух словах:
▪️
▪️
▪️
▪️ композиция — объединяем объекты, как части (вложенность, а не наследование)
На этом все! В следующем посте — инкапсуляция. Подписывайтесь, чтобы не пропустить💙
Мы запускаем серию постов о принципах объектно-ориентированного программирования (ООП). Вы скажете, что это база, но и ее нужно иногда повторять. Поехали🚀
Dart — язык с ООП-подходом, поэтому без его ключевых принципов сложно представить разработку. Для начала разберемся, что вообще такое ООП.
Что такое ООП?
Объектно-ориентированное программирование (ООП) — это парадигма, основанная на использовании объектов, которые содержат данные (в виде полей) и поведение (в виде методов).
Звучит сложно? Если проще: объект — это экземпляр класса, а класс описывает структуру и поведение этих объектов.
ООП помогает:
✔️ структурировать код
✔️ переиспользовать функциональность
✔️ облегчить масштабирование и поддержку проекта
Основные принципы ООП:
▫️ наследование
▫️ инкапсуляция
▫️ полиморфизм
▫️ абстракция
Сегодня разберем наследование.
Что такое наследование?
Наследование — это механизм, который позволяет одному классу (дочернему) унаследовать свойства и методы другого класса (родительского).
Представьте, что вы пишете посты для Telegram. У вас есть базовый класс Article с методами write(), publish() полями title и author:
class Article {
const Article(this.title, this.author);
final String title;
final String author;
void write() => print('Статья $title ...');
void publish() => print('Публикация статьи ...');
}
Вместо дублирования логики — создаем дочерний класс и наследуем поведение:
class TutorialArticle extends Article {
const TutorialArticle(super.title, super.author, this.topic);
/// Дочерние классы могут вызывать конструкторы родительского класса
/// с помощью ключевого слова `super`, передавая необходимые параметры.
/// Это важно для инициализации базового состояния.
final String topic;
void explain() => print('Объяснение темы: $topic');
@override
void publish() {
super.publish(); // сохраняем поведение родителя
print('Дополнительные шаги для публикации туториала по теме: $topic');
}
}
Используем:
void main() {
final article = TutorialArticle('ООП в Dart', 'Роза', 'Наследование');
article.write(); // унаследовано
article.publish(); // унаследовано
article.explain(); // новое поведение
}
В итоге:
▫️Базовый класс — содержит общую функциональность
▫️Дочерний класс — расширяет или переопределяет поведение
А что насчет наследования в dart?
В dart наследование реализуется через ключевое слово
extends
:class Child extends Parent { ... }
📝 Прежде чем создавать базовый класс, загляните в пост о модификаторах классов.
Кроме того, Dart использует mixin-based inheritance. Класс может наследовать только один суперкласс (
extends
), но может использовать несколько миксинов (with
) для добавления поведения.Кстати, в Dart можно не только наследовать реализацию через extends, но и реализовывать интерфейсы с помощью implements. Тогда ты сам переопределяешь все методы, но это уже не наследование поведения.
Композиция vs наследование
Наследование — не единственный способ повторного использования кода. Есть композиция, когда один объект включается в другой, как поле. Такой подход часто делает код более гибким и читаемым. Об этом — в следующих постах.
В двух словах:
▪️
extends
— наследуем реализацию (и можем ее переопределить)▪️
implements
— реализуем интерфейс (обязаны реализовать все вручную)▪️
with
— подключаем миксины (многоразовые наборы методов)▪️ композиция — объединяем объекты, как части (вложенность, а не наследование)
На этом все! В следующем посте — инкапсуляция. Подписывайтесь, чтобы не пропустить💙
❤9👍8🔥6
Привет, это Катя, Flutter Dev Friflex👋Сегодня мы продолжим разбирать базовые принципы ООП, и на очереди — инкапсуляция.
В предыдущем посте мы говорили о наследовании, теперь давайте разберемся, как и зачем прятать детали реализации внутри класса.
Что такое инкапсуляция?
Инкапсуляция — это способ скрыть внутреннюю реализацию объекта и предоставить только нужный интерфейс для взаимодействия с ним. Проще говоря, это как интерфейс микроволновки: ты видишь кнопки, но не знаешь, что происходит внутри.
В языке Dart инкапсуляция реализуется через:
▫️Приватные переменные (с помощью _)
▫️Публичные методы и геттеры/сеттеры для доступа к данным
Зачем нужна инкапсуляция?
Инкапсуляция делает код:
✔️безопасным — защищает данные от нежелательных изменений
✔️удобным — скрывает детали реализации, оставляя только то, что нужно пользователю класса
✔️контролируемым — ты решаешь, как и при каких условиях можно изменять данные
Пример инкапсуляции
Что тут происходит:
▪️_balance — скрытая реализация. Никто не может напрямую изменить баланс
▪️balance — только для чтения. Пользователь может узнать, сколько денег на счете
▪️deposit и withdraw — контролируют доступ к изменению данных (инкапсуляция в действии)
Надеюсь, стало немного яснее, как работает инкапсуляция и почему она так важна. В следующем посте разберем еще один принцип ООП — абстракцию.
В предыдущем посте мы говорили о наследовании, теперь давайте разберемся, как и зачем прятать детали реализации внутри класса.
Что такое инкапсуляция?
Инкапсуляция — это способ скрыть внутреннюю реализацию объекта и предоставить только нужный интерфейс для взаимодействия с ним. Проще говоря, это как интерфейс микроволновки: ты видишь кнопки, но не знаешь, что происходит внутри.
В языке Dart инкапсуляция реализуется через:
▫️Приватные переменные (с помощью _)
▫️Публичные методы и геттеры/сеттеры для доступа к данным
Зачем нужна инкапсуляция?
Инкапсуляция делает код:
✔️безопасным — защищает данные от нежелательных изменений
✔️удобным — скрывает детали реализации, оставляя только то, что нужно пользователю класса
✔️контролируемым — ты решаешь, как и при каких условиях можно изменять данные
Пример инкапсуляции
class BankAccount {
double _balance = 0.0;
double get balance => _balance;
void deposit(double amount) {
if (amount > 0) _balance += amount;
}
void withdraw(double amount) {
if (amount > 0 && amount <= _balance) _balance -= amount;
}
}
void main() {
final account = BankAccount();
account.deposit(100);
account.withdraw(30);
print(account.balance);
}
Что тут происходит:
▪️_balance — скрытая реализация. Никто не может напрямую изменить баланс
▪️balance — только для чтения. Пользователь может узнать, сколько денег на счете
▪️deposit и withdraw — контролируют доступ к изменению данных (инкапсуляция в действии)
Надеюсь, стало немного яснее, как работает инкапсуляция и почему она так важна. В следующем посте разберем еще один принцип ООП — абстракцию.
🔥8❤6
❤5👍5
Привет! Это Анна, Friflex Flutter Team Lead👋
Продолжаем разбирать основные принципы объектно-ориентированного программирования. В предыдущих постах мы уже поговорили о наследовании и инкапсуляции. Сегодня рассмотрим еще один принцип — абстракцию.
Абстракция как принцип ООП подразумевает выделение только самых важных методов и полей объекта, при этом все детали внутренней специфической реализации скрыты. В таком подходе разработчик создает некоторый интерфейс, абстрактный класс, который описывает только необходимые извне свойства.
Чем помогает абстракция?
1. Упрощает работу с кодом. Код становится более структурированным и понятным для восприятия
2. Позволяет обезопасить реализации. Все специфические детали реализации скрыты от внешнего воздействия. Это позволяет их сохранить в первозданном виде и снизить вероятность проблем по месту использования
3. Дает возможность безопасно масштабировать проект. Абстракция позволяет снизить зависимость некоторых модулей кода, что упрощает его расширение
Чтобы еще лучше понять абтракцию, можно провести аналогию со стиральной машиной — человек выбирает режим кнопками на панели, а машина уже самостоятельно пошагово выполняет тот или иной заданный алгоритм.
Подведем итог в виде простого примера реализации абстракции в Dart-коде.
У нас есть задача создать абстракцию оплаты и две реализаци — оплаты по карте и наличными. В самом простом виде получим такой результат.
Продолжаем разбирать основные принципы объектно-ориентированного программирования. В предыдущих постах мы уже поговорили о наследовании и инкапсуляции. Сегодня рассмотрим еще один принцип — абстракцию.
Абстракция как принцип ООП подразумевает выделение только самых важных методов и полей объекта, при этом все детали внутренней специфической реализации скрыты. В таком подходе разработчик создает некоторый интерфейс, абстрактный класс, который описывает только необходимые извне свойства.
Чем помогает абстракция?
1. Упрощает работу с кодом. Код становится более структурированным и понятным для восприятия
2. Позволяет обезопасить реализации. Все специфические детали реализации скрыты от внешнего воздействия. Это позволяет их сохранить в первозданном виде и снизить вероятность проблем по месту использования
3. Дает возможность безопасно масштабировать проект. Абстракция позволяет снизить зависимость некоторых модулей кода, что упрощает его расширение
Чтобы еще лучше понять абтракцию, можно провести аналогию со стиральной машиной — человек выбирает режим кнопками на панели, а машина уже самостоятельно пошагово выполняет тот или иной заданный алгоритм.
Подведем итог в виде простого примера реализации абстракции в Dart-коде.
У нас есть задача создать абстракцию оплаты и две реализаци — оплаты по карте и наличными. В самом простом виде получим такой результат.
/// Абстрактный класс — интерфейс оплаты
abstract class Payment {
void pay(double amount);
}
/// Реализация оплаты картой
class CardPayment implements Payment {
@override
void pay(double amount) {
final fee = _calculateFee(amount);
print(
'Оплата $amount руб. с помощью карты. Комиссия составила $fee руб. Итого — ${amount + fee} руб.',
);
}
/// Расчет комиссии за оплату картой - 2%
double _calculateFee(double amount) {
return amount * 0.02;
}
}
/// Реализация оплаты наличными
class CashPayment implements Payment {
@override
void pay(double amount) {
print('Оплата $amount руб. наличными');
}
}
void main() {
final Payment payment1 = CardPayment();
final Payment payment2 = CashPayment();
payment1.pay(
500,
); // Оплата 500 руб. с помощью карты. Комиссия составила 10 руб. Итого — 510 руб.
payment2.pay(200); // Оплата 200 руб. наличными
}
❤️ —
если было полезно❤15❤🔥4🔥2
Всем привет! Это Роза, Flutter Dev в Friflex!👋
Продолжаем наше путешествие по миру объектно-ориентированного программирования. Мы уже рассмотрели наследование, инкапсуляцию и абстракцию. Теперь настало время для полиморфизма.
Когда я только начинала разбираться в ООП, полиморфизм был для меня самым непонятным: «Объекты, которые могут принадлежать к разным классам, но имеют одинаковое поведение, определенное через общий интерфейс или базовый класс». О как! Давайте попробуем разобраться!
Полиморфизм буквально означает «много форм». Представьте: у вас есть базовый класс, который описывает общее поведение, а в классах-наследниках вы можете переопределить это поведение в соответствии с конкретной реализацией. Это и есть полиморфизм.
Полиморфизм в Dart
В Dart можно выделить два основных типа полиморфизма:
1. Полиморфизм на основе наследования. Дочерние классы наследуют поведение родителя и могут его переопределять
2. Полиморфизм через интерфейсы. Класс реализует интерфейс и обязан описать все его методы. Это позволяет задать единое поведение для разных реализаций
Помните пример со статьями из поста про наследование? Тогда я создала базовый класс с общими методами. Представим, что материалов стало больше. Теперь хочется разделить их по категориям.
Теперь создадим подклассы с разными разделами:
Теперь мы можем использовать их полиморфно:
Даже не зная точного типа статьи, мы можем обращаться к ней через базовый класс Article. В зависимости от конкретной реализации логика будет разной.
Кроме классического полиморфизма существуют и другие его формы:
▫️Полиморфизм подтипов (Subtype polymorphism)
Это наиболее привычный вариант, когда один класс наследуется от другого и переопределяет его поведение. Именно его чаще всего имеют в виду, говоря о полиморфизме в ООП.
▫️Параметрический полиморфизм (Generics)
Позволяет писать универсальный код, работающий с разными типами данных. Например:
Здесь функция identity не зависит от конкретного типа — она просто возвращает то, что получила. Тип указывается как параметр <T>.
▫️Ad hoc полиморфизм
Это перегрузка методов, когда у функции одно имя, но разные параметры. Но в Dart такая перегрузка не поддерживается напрямую.
А зачем это все?
Полиморфизм помогает писать чистый, расширяемый и переиспользуемый код, особенно когда работаешь с большим количеством похожих сущностей.
Но есть важное правило:
Не объединяйте все подряд в общее поведение. Интерфейс или базовый класс должен описывать действительно общее поведение. Иначе вы рискуете нарушить принцип единственной ответственности (SRP).
На этом все! Если понравилась статья, то ставьте 💙
Продолжаем наше путешествие по миру объектно-ориентированного программирования. Мы уже рассмотрели наследование, инкапсуляцию и абстракцию. Теперь настало время для полиморфизма.
Когда я только начинала разбираться в ООП, полиморфизм был для меня самым непонятным: «Объекты, которые могут принадлежать к разным классам, но имеют одинаковое поведение, определенное через общий интерфейс или базовый класс». О как! Давайте попробуем разобраться!
Полиморфизм буквально означает «много форм». Представьте: у вас есть базовый класс, который описывает общее поведение, а в классах-наследниках вы можете переопределить это поведение в соответствии с конкретной реализацией. Это и есть полиморфизм.
Полиморфизм в Dart
В Dart можно выделить два основных типа полиморфизма:
1. Полиморфизм на основе наследования. Дочерние классы наследуют поведение родителя и могут его переопределять
2. Полиморфизм через интерфейсы. Класс реализует интерфейс и обязан описать все его методы. Это позволяет задать единое поведение для разных реализаций
Помните пример со статьями из поста про наследование? Тогда я создала базовый класс с общими методами. Представим, что материалов стало больше. Теперь хочется разделить их по категориям.
class Article {
const Article(this.title, this.author);
final String title;
final String author;
void write() => print('Пишем статью: "$title"');
void publish() => print('Публикация статьи "$title", автор: $author');
void section() => print('Общий раздел');
}
Теперь создадим подклассы с разными разделами:
class OOPArticle extends Article {
const OOPArticle(String title, String author) : super(title, author);
@override
void section() => print('🔹 Раздел: Объектно-ориентированное программирование');
}
class PatternsArticle extends Article {
const PatternsArticle(String title, String author) : super(title, author);
@override
void section() => print('🔸 Раздел: Паттерны проектирования');
}
Теперь мы можем использовать их полиморфно:
void printArticleInfo(Article article) {
article.write();
article.section();
article.publish();
}
void main() {
final articles = [
OOPArticle('Что такое полиморфизм?', 'Роза'),
PatternsArticle('Паттерн Singleton', 'Роза'),
];
for (final article in articles) {
printArticleInfo(article);
print('---');
}
}
Даже не зная точного типа статьи, мы можем обращаться к ней через базовый класс Article. В зависимости от конкретной реализации логика будет разной.
Кроме классического полиморфизма существуют и другие его формы:
▫️Полиморфизм подтипов (Subtype polymorphism)
Это наиболее привычный вариант, когда один класс наследуется от другого и переопределяет его поведение. Именно его чаще всего имеют в виду, говоря о полиморфизме в ООП.
▫️Параметрический полиморфизм (Generics)
Позволяет писать универсальный код, работающий с разными типами данных. Например:
T identity<T>(T value) => value;
void main() {
print(identity<int>(10)); // 10
print(identity<String>('Да')); // Да
}
Здесь функция identity не зависит от конкретного типа — она просто возвращает то, что получила. Тип указывается как параметр <T>.
▫️Ad hoc полиморфизм
Это перегрузка методов, когда у функции одно имя, но разные параметры. Но в Dart такая перегрузка не поддерживается напрямую.
А зачем это все?
Полиморфизм помогает писать чистый, расширяемый и переиспользуемый код, особенно когда работаешь с большим количеством похожих сущностей.
Но есть важное правило:
Не объединяйте все подряд в общее поведение. Интерфейс или базовый класс должен описывать действительно общее поведение. Иначе вы рискуете нарушить принцип единственной ответственности (SRP).
На этом все! Если понравилась статья, то ставьте 💙
❤8🔥2
This media is not supported in your browser
VIEW IN TELEGRAM
Располагаемся ближе к экрану — Friflex выпустили Flutter Starter. И это —🔥
Упаковали наш опыт для всех, кто устал каждый раз собирать скелет приложения с нуля. Внутри — готовый корпоративный шаблон, чтобы вы начинали разработку с чистого и понятного фундамента.
Что вы получаете?
✅ Чистая архитектура: Presentation / Domain / Data — без лишнего хаоса
✅ Локализация, темы, модульность — ready
✅ Стиль кода, который не стыдно показать на ревью
✅ Разделение на сервисы для портирования на разные платформы
✅ Простой DI без лишних пакетов, настроенный навигатор
Для кого?
Для всех, кто хочет собирать приложения быстрее, чище, удобнее.
🔗 Качайте, меняйте название пакета и начинайте добавлять фичи:
GitHub Friflex Flutter Starter
Пусть меньше времени уходит на настройку — и больше на создание крутых проектов💜
Упаковали наш опыт для всех, кто устал каждый раз собирать скелет приложения с нуля. Внутри — готовый корпоративный шаблон, чтобы вы начинали разработку с чистого и понятного фундамента.
Что вы получаете?
Для кого?
Для всех, кто хочет собирать приложения быстрее, чище, удобнее.
GitHub Friflex Flutter Starter
Пусть меньше времени уходит на настройку — и больше на создание крутых проектов
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥23❤9👍8❤🔥2
Привет, это Катя, Flutter Dev в Friflex 👋 Сегодня поговорим про Observer.
Что такое Observer?
Паттерн «Наблюдатель» (Observer) — это поведенческий паттерн проектирования, суть которого в том, что один объект (наблюдаемый) может автоматически уведомлять другие объекты (наблюдатели) о своих изменениях.
Это похоже на подписку: один объект «подписывается» на другой, чтобы быть в курсе изменений. Когда наблюдаемый объект изменяется — все подписчики получают уведомление и могут отреагировать.
Где это полезно?
Этот паттерн часто используется, когда:
▫️у нас есть модель данных, и мы хотим автоматически обновлять UI
▫️нужно разделить логику обработки и отображения данных
▫️необходимо уведомить несколько частей системы об одном событии
Как реализовать Observer?
✔️Создаем абстракцию Observable (или Subject) — объект, за которым наблюдают
✔️Создаем абстракцию Observer — наблюдатель
✔️Реализуем логику хранения списка наблюдателей в Observable
✔️Реализуем метод update в Observer, чтобы обрабатывать изменения
Пример:
Что тут происходит:
▪️Observer — абстрактный класс с методом update, который вызывается при изменении состояния
▪️Observable — содержит список наблюдателей и уведомляет их при изменении значения переменной value
▪️LoggerObserver — конкретный наблюдатель, который просто печатает обновленное значение
В main() мы добавляем наблюдателя, изменяем значение, и он автоматически получает уведомления. Потом мы его удаляем из списка — и уведомления больше не приходят.
Плюсы:
✅ Слабая связанность — наблюдатель не зависит от внутренней логики наблюдаемого
✅ Гибкость — можно динамически добавлять и удалять наблюдателей
✅ Масштабируемость — несколько объектов могут реагировать на одно событие
Минусы:
⚠️ Перегрузка сообщений — большое количество наблюдателей может вызвать перегрузку
⚠️ Циклические зависимости — если наблюдатель и наблюдаемый зависят друг от друга
⚠️ Порядок уведомлений — не гарантируется порядок вызова update()
А во Flutter?
▫️ChangeNotifier и ChangeNotifierProvider в Provider
▫️ValueNotifier + ValueListenableBuilder
▫️StreamBuilder, который реагирует на события стрима
▫️InheritedWidget и InheritedNotifier — это тоже про наблюдение за изменениями
Какой паттерн разобрать в следующий раз?🔍
Что такое Observer?
Паттерн «Наблюдатель» (Observer) — это поведенческий паттерн проектирования, суть которого в том, что один объект (наблюдаемый) может автоматически уведомлять другие объекты (наблюдатели) о своих изменениях.
Это похоже на подписку: один объект «подписывается» на другой, чтобы быть в курсе изменений. Когда наблюдаемый объект изменяется — все подписчики получают уведомление и могут отреагировать.
Где это полезно?
Этот паттерн часто используется, когда:
▫️у нас есть модель данных, и мы хотим автоматически обновлять UI
▫️нужно разделить логику обработки и отображения данных
▫️необходимо уведомить несколько частей системы об одном событии
Как реализовать Observer?
✔️Создаем абстракцию Observable (или Subject) — объект, за которым наблюдают
✔️Создаем абстракцию Observer — наблюдатель
✔️Реализуем логику хранения списка наблюдателей в Observable
✔️Реализуем метод update в Observer, чтобы обрабатывать изменения
Пример:
abstract class Observer {
void update(int data);
}
class Observable {
final _observers = <Observer>[];
int _value = 0;
void addObserver(Observer observer) => _observers.add(observer);
void removeObserver(Observer observer) => _observers.remove(observer);
set value(int newValue) {
_value = newValue;
_notify();
}
void _notify() {
for (final o in _observers) {
o.update(_value);
}
}
}
class LoggerObserver implements Observer {
@override
void update(int data) => print('Updated value: $data');
}
void main() {
final observable = Observable();
final observer = LoggerObserver();
observable.addObserver(observer);
observable.value = 1;
observable.value = 2;
observable.removeObserver(observer);
}
Что тут происходит:
▪️Observer — абстрактный класс с методом update, который вызывается при изменении состояния
▪️Observable — содержит список наблюдателей и уведомляет их при изменении значения переменной value
▪️LoggerObserver — конкретный наблюдатель, который просто печатает обновленное значение
В main() мы добавляем наблюдателя, изменяем значение, и он автоматически получает уведомления. Потом мы его удаляем из списка — и уведомления больше не приходят.
Плюсы:
✅ Слабая связанность — наблюдатель не зависит от внутренней логики наблюдаемого
✅ Гибкость — можно динамически добавлять и удалять наблюдателей
✅ Масштабируемость — несколько объектов могут реагировать на одно событие
Минусы:
⚠️ Перегрузка сообщений — большое количество наблюдателей может вызвать перегрузку
⚠️ Циклические зависимости — если наблюдатель и наблюдаемый зависят друг от друга
⚠️ Порядок уведомлений — не гарантируется порядок вызова update()
А во Flutter?
▫️ChangeNotifier и ChangeNotifierProvider в Provider
▫️ValueNotifier + ValueListenableBuilder
▫️StreamBuilder, который реагирует на события стрима
▫️InheritedWidget и InheritedNotifier — это тоже про наблюдение за изменениями
Какой паттерн разобрать в следующий раз?
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤4👍3
Всем привет! Это Анна, Friflex Flutter Team Lead💭
В предыдущем посте мы узнали о том, что такое паттерн Observer (Наблюдатель) и как его применять в Dart-коде. Сегодня продолжим обсуждение поведенческих шаблонов проектирования и поговорим о шаблоне Mediator (Посредник).
Представьте ситуацию — сначала у вас в работе простой проект, в нем буквально несколько простых функций, каждая из которых особо не пересекается с другими. Но со временем ваш проект разрастается, количество классов становится все больше, их связи между собой все сложнее. В таком случае рано или поздно в коде воцарится хаос, в котором будет крайне сложно разобраться всем членам команды. Именно тут отлично впишется паттерн Посредник.
Посредник подразумевает создание некоторого класса, который берет на себя роль связующего звена между другими объектами. При его внедрении все взаимодействие объектов проходит только через него, непосредственное общение между объектами минимизируется.
✈️ Для простоты понимания представим посредником авиационную диспетчерскую вышку, а все остальные объекты в коде — самолетами. Самолеты не общаются друг с другом, так как это общение может привести к хаосу в полете. Всеми вопросами связи занимается именно диспетчер.
А с чем сравнить посредника во Flutter-приложении? Представим, что нам надо на трех разных экранах приложения сделать отображение значения счетчика. При этом счетчик должен быть общим.
Без посредника на каждом экране пришлось бы вызывать обновление счетчика не только на текущей странице, но и на двух других. Таким образом, все три экрана были бы связаны.
С посредником можно было бы создать класс CounterMediator, который возьмет на себя всю основную логику — он хранит общее значение счетчика, дает доступ к методам обновления этого значения и уведомляет слушателей.
В таком случае каждый из экранов получил бы простую обособленную реализацию. Экран принимает экземпляр посредника-счетчика, слушает и отображает значение счетчика, а также вызывает метод обновления. В таком случае значения на экранах обновляются без связи друг с другом.
В предыдущем посте мы узнали о том, что такое паттерн Observer (Наблюдатель) и как его применять в Dart-коде. Сегодня продолжим обсуждение поведенческих шаблонов проектирования и поговорим о шаблоне Mediator (Посредник).
Представьте ситуацию — сначала у вас в работе простой проект, в нем буквально несколько простых функций, каждая из которых особо не пересекается с другими. Но со временем ваш проект разрастается, количество классов становится все больше, их связи между собой все сложнее. В таком случае рано или поздно в коде воцарится хаос, в котором будет крайне сложно разобраться всем членам команды. Именно тут отлично впишется паттерн Посредник.
Посредник подразумевает создание некоторого класса, который берет на себя роль связующего звена между другими объектами. При его внедрении все взаимодействие объектов проходит только через него, непосредственное общение между объектами минимизируется.
А с чем сравнить посредника во Flutter-приложении? Представим, что нам надо на трех разных экранах приложения сделать отображение значения счетчика. При этом счетчик должен быть общим.
Без посредника на каждом экране пришлось бы вызывать обновление счетчика не только на текущей странице, но и на двух других. Таким образом, все три экрана были бы связаны.
С посредником можно было бы создать класс CounterMediator, который возьмет на себя всю основную логику — он хранит общее значение счетчика, дает доступ к методам обновления этого значения и уведомляет слушателей.
class CounterMediator {
int _counter = 0;
final List<Function(int)> _listeners = [];
/// Значение счетчика
int get counter => _counter;
/// Добавляет слушателя, который будет уведомлен при изменении счетчика
void addListener(Function(int) listener) {
_listeners.add(listener);
}
/// Изменяет значение счетчика и уведомляет всех слушателей
void increment() {
_counter++;
for (final listener in _listeners) {
listener(_counter);
}
}
}
В таком случае каждый из экранов получил бы простую обособленную реализацию. Экран принимает экземпляр посредника-счетчика, слушает и отображает значение счетчика, а также вызывает метод обновления. В таком случае значения на экранах обновляются без связи друг с другом.
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key, required this.mediator});
final CounterMediator mediator;
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _counter = 0;
@override
void initState() {
super.initState();
// Подписываемся на изменения посредника-счетчика
widget.iss.onediator.addListener((newCounter) {
setState(() => _counter = newCounter);
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Счетчик: $_counter'),
ElevatedButton(
onPressed: widget.iss.onediator
.increment, // Вызываем метод увеличения значения счетчика
child: const Text('+'),
),
],
);
}
}
❤️ –
если пост был полезенPlease open Telegram to view this post
VIEW IN TELEGRAM
❤18🔥2❤🔥1💩1
Привет, это Роза, Flutter Dev Friflex!😉
Недавно я решала задачу — нужно было валидировать файлы при загрузке и вызывать для них метод convert. Сложность в том, что у меня было 4-5 форматов, и со временем их могло стать еще больше.
Какие были варианты решения:
☠️ if-case
Просто перечислить все форматы и вызвать нужный метод:
В целом это будет работать, но немного отдает болью и будущими проблемами в виде сложно поддерживаемого кода.
Добавить новый формат — +1 условие.
Протестировать — сложновато.
Расширять — ну такое.
✅ Использовать паттерн проектирования
Я решила, что Стратегия — то, что нужно.
Что говорит теория?
Стратегия — поведенческий паттерн, который:
▫️ выделяет семейство алгоритмов
▫️ выносит каждый из них в отдельный класс
▫️ делает их взаимозаменяемыми
Проще говоря: если у тебя есть несколько вариантов поведения, которые могут меняться, стратегия позволяет:
▪️ вынести каждый вариант в отдельный класс
▪️ использовать их через единый интерфейс
▪️заменять поведение в рантайме без изменения основного кода
В жизни часто можно столкнуться со стратегией: выбор способа оплаты (карта, наличные), выбор транспорта (метро, такси, автобус).
Вернемся к задаче
Я определила общий интерфейс FileConversionStrategy, который описывает, как работать с файлом:
Затем написала реализации под каждый формат. Например, ArbConversionStrategy:
Каждая стратегия знает, как обрабатывать только свой формат. И вуаля — проблема решена! Теперь я могу добавлять сколько угодно новых форматов — достаточно просто написать для него реализацию FileConversionStrategy.
❕ Но это еще не все. У нас есть реализации, но как выбрать необходимую в нужный момент?
Для этого я написала менеджер стратегий. Он перебирает доступные реализации и возвращает ту, что подходит:
А теперь разберем, как все это работает вместе:
1. Контекст — запрашивает подходящую стратегию и работает с ней через единый интерфейс
2. Интерфейс — объединяет все алгоритмы в общую структуру (validate, convert)
3. Конкретные стратегии — реализуют обработку разных форматов (CSV, ARB, JSON и других)
4. Во время выполнения мы подбираем нужную стратегию
5. Можно легко заменить стратегию на новую
Но применять паттерны ради паттернов — плохая идея. Их сила не в модности, а в том, что они решают конкретные проблемы.
Поэтому используйте стратегию:
✔️ Когда у вас разные сценарии поведения, и хочется вынести их из основного кода
✔️ Когда поведение часто меняется или дополняется
✔️ Когда хочется разделить ответственность и не мешать все в одном классе
Это уже третий паттерн. Здесь подробнее про:
🔗Observer
🔗Mediator
Недавно я решала задачу — нужно было валидировать файлы при загрузке и вызывать для них метод convert. Сложность в том, что у меня было 4-5 форматов, и со временем их могло стать еще больше.
Какие были варианты решения:
☠️ if-case
Просто перечислить все форматы и вызвать нужный метод:
if (format == FileFormat.arb) {
validateArb(file);
} else if (format == FileFormat.csv) {
validateCsv(file);
}
// ...
// И это только validate, а еще есть convert...
В целом это будет работать, но немного отдает болью и будущими проблемами в виде сложно поддерживаемого кода.
Добавить новый формат — +1 условие.
Протестировать — сложновато.
Расширять — ну такое.
✅ Использовать паттерн проектирования
Я решила, что Стратегия — то, что нужно.
Что говорит теория?
Стратегия — поведенческий паттерн, который:
▫️ выделяет семейство алгоритмов
▫️ выносит каждый из них в отдельный класс
▫️ делает их взаимозаменяемыми
Проще говоря: если у тебя есть несколько вариантов поведения, которые могут меняться, стратегия позволяет:
▪️ вынести каждый вариант в отдельный класс
▪️ использовать их через единый интерфейс
▪️заменять поведение в рантайме без изменения основного кода
В жизни часто можно столкнуться со стратегией: выбор способа оплаты (карта, наличные), выбор транспорта (метро, такси, автобус).
Вернемся к задаче
Я определила общий интерфейс FileConversionStrategy, который описывает, как работать с файлом:
abstract class FileConversionStrategy {
bool supports(FileFormat format);
FutureOr<ImportFileModel> convert(
ImportFileModel file,
List<String> editedResult,
);
void validate(ImportFileModel file);
}
Затем написала реализации под каждый формат. Например, ArbConversionStrategy:
class ArbConversionStrategy implements FileConversionStrategy {
@override
bool supports(FileFormat format) => format == FileFormat.arb;
@override
FutureOr<ImportFileModel> convert(
ImportFileModel file,
List<String> editedResult,
) {
// логика конвертации
}
@override
void validate(ImportFileModel file) {
// логика валидации
}
}
Каждая стратегия знает, как обрабатывать только свой формат. И вуаля — проблема решена! Теперь я могу добавлять сколько угодно новых форматов — достаточно просто написать для него реализацию FileConversionStrategy.
Для этого я написала менеджер стратегий. Он перебирает доступные реализации и возвращает ту, что подходит:
class FileConversionStrategyManager {
final List<FileConversionStrategy> strategies;
FileConversionStrategyManager(this.strategies);
FileConversionStrategy getStrategy(FileFormat format) {
return strategies.firstWhere(
(strategy) => strategy.supports(format),
orElse: () => throw UnsupportedError('No strategy for format $format'),
);
}
}
А теперь разберем, как все это работает вместе:
1. Контекст — запрашивает подходящую стратегию и работает с ней через единый интерфейс
2. Интерфейс — объединяет все алгоритмы в общую структуру (validate, convert)
3. Конкретные стратегии — реализуют обработку разных форматов (CSV, ARB, JSON и других)
4. Во время выполнения мы подбираем нужную стратегию
5. Можно легко заменить стратегию на новую
Но применять паттерны ради паттернов — плохая идея. Их сила не в модности, а в том, что они решают конкретные проблемы.
Поэтому используйте стратегию:
✔️ Когда у вас разные сценарии поведения, и хочется вынести их из основного кода
✔️ Когда поведение часто меняется или дополняется
✔️ Когда хочется разделить ответственность и не мешать все в одном классе
Это уже третий паттерн. Здесь подробнее про:
🔗Observer
🔗Mediator
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍6🔥4💅1