Привет, это Катя, Flutter Dev Friflex. Сейчас расскажу про три решения: Bloc, Riverpod и yx_scope, и еще немного про альтернативные подходы.
Bloc
Bloc — это предсказуемый state-менеджмент, основанный на концепции Unidirectional Data Flow (однонаправленный поток данных).
Основные концепции
Events — действия, которые триггерят изменения
States — иммутабельные объекты, описывающие состояние приложения
Bloc — класс, который обрабатывает Events и эмитит States
Плюсы
◽️Четкое разделение логики и UI
◽️Хорошая документация и большое сообщество
◽️Поддержка Cubit (упрощенная версия Bloc)
Минусы
◽️Высокая шаблонность (много повторяющегося кода)
◽️Избыточность для простых сценариев — если состояние приложения простое, Bloc может быть слишком мощным
Пример использования
Riverpod
Riverpod — это улучшенная версия Provider, созданная тем же автором (Remi Rousselet). Он решает проблемы Provider (например, Null safety и тестируемость).
Основные концепции
Provider — источник данных (может быть StateProvider, FutureProvider или другой)
Consumer — виджет, который читает провайдер
AutoDispose — автоматическая отписка от провайдеров
Плюсы
◽️Нет зависимости от BuildContext
◽️Лучшая поддержка тестирования
◽️Гибкость (можно использовать как DI или state-менеджмент)
Минусы
◽️Неочевидная работа с асинхронностью — AsyncValue требует дополнительной обработки ошибок и загрузки
◽️Меньше документации по сравнению с Bloc
Пример использования
yx_scope
yx_scope — это легковесная библиотека для управления состоянием, вдохновленная ScopedModel и InheritedWidget.
Основные концепции
Scope — контейнер для состояния
InheritedScope — автоматически обновляет виджеты при изменении состояния
Плюсы
◽️Простота использования.
◽️Хорошо подходит для небольших приложений.
◽️Низкая шаблонность (минимум повторяющегося кода).
Минусы
◽️Меньше возможностей, чем у Bloc/Riverpod.
◽️Меньше документации
◽️Плохая масштабируемость — в больших проектах библиотека может стать непредсказуемой
Пример использования
Добавляю в комментарии табличку сравнений state-менеджментов. Давайте обсудим!
Bloc
Bloc — это предсказуемый state-менеджмент, основанный на концепции Unidirectional Data Flow (однонаправленный поток данных).
Основные концепции
Events — действия, которые триггерят изменения
States — иммутабельные объекты, описывающие состояние приложения
Bloc — класс, который обрабатывает Events и эмитит States
Плюсы
◽️Четкое разделение логики и UI
◽️Хорошая документация и большое сообщество
◽️Поддержка Cubit (упрощенная версия Bloc)
Минусы
◽️Высокая шаблонность (много повторяющегося кода)
◽️Избыточность для простых сценариев — если состояние приложения простое, Bloc может быть слишком мощным
Пример использования
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
@override
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) yield state + 1;
if (event is Decrement) yield state - 1;
}
}
Riverpod
Riverpod — это улучшенная версия Provider, созданная тем же автором (Remi Rousselet). Он решает проблемы Provider (например, Null safety и тестируемость).
Основные концепции
Provider — источник данных (может быть StateProvider, FutureProvider или другой)
Consumer — виджет, который читает провайдер
AutoDispose — автоматическая отписка от провайдеров
Плюсы
◽️Нет зависимости от BuildContext
◽️Лучшая поддержка тестирования
◽️Гибкость (можно использовать как DI или state-менеджмент)
Минусы
◽️Неочевидная работа с асинхронностью — AsyncValue требует дополнительной обработки ошибок и загрузки
◽️Меньше документации по сравнению с Bloc
Пример использования
final counterProvider = StateProvider<int>((ref) => 0);
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Text('Count: $count'),
);
}
}
yx_scope
yx_scope — это легковесная библиотека для управления состоянием, вдохновленная ScopedModel и InheritedWidget.
Основные концепции
Scope — контейнер для состояния
InheritedScope — автоматически обновляет виджеты при изменении состояния
Плюсы
◽️Простота использования.
◽️Хорошо подходит для небольших приложений.
◽️Низкая шаблонность (минимум повторяющегося кода).
Минусы
◽️Меньше возможностей, чем у Bloc/Riverpod.
◽️Меньше документации
◽️Плохая масштабируемость — в больших проектах библиотека может стать непредсказуемой
Пример использования
class CounterScope extends Scope {
var count = 0;
void increment() => notifyListeners(count++);
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) => TextButton(
onPressed: CounterScope.of(context).increment,
child: Text('Count: ${CounterScope.of(context).count}'),
);
}
Добавляю в комментарии табличку сравнений state-менеджментов. Давайте обсудим!
Всем привет! С вами Анна, Friflex Flutter Team Lead.
Любой Flutter-разработчик, создавая свой первый проект задавался хоть раз вопросом — с чего начать, что делать и, главное, как упростить себе работу? Сегодня разберу основные шаги, которые позволят подготовить базу.
1 шаг. Создание проекта
Здесь все очень просто - создать проект можно всего одной командой через терминал. Достаточно выполнить:
Вуаля! Проект с названием new_app создан и готов к работе. С помощью дополнительных опций можно конфигурировать проект. Например, опция
О других вариантах можно прочитать здесь.
2 шаг. Настройка запуска приложения
Все знают: чтобы запустить Flutter-проект, достаточно вызвать метод
Что обязательно стоит предусмотреть перед запуском — это верхнеуровневую обработку ошибок. Она поможет избежать падений приложения, если где-то в коде ошибка не будет локально обработана.
Здесь нужно инициализировать
Основной совет — раннер приложения стоит делать максимально простым, чтобы запуск не был слишком долгим. И постарайтесь предусмотреть любые вероятности возникновения ошибок во время запуска.
3 шаг. Настройка флаворов
Здесь на помощь вам придет библиотека flutter_flavorizr.
Подключаете пакет в зависимости вашего приложения, а дальше дело за малым — по примеру из документации нужно указать, какие именно флаворы необходимы, какое название и bundleId должно иметь приложение и какие плаформы оно будет поддерживать:
Далее остается только запустить кодогенерацию.
Немного подробнее про флаворы можно почитать здесь.
4 шаг. Реализовать DI
Здесь конкретных рекомендаций нет, каждый разработчик самостоятельно выбирает подход — можно сделать самописную реализацию, без кодогенерации и сторонних библиотек, можно интегрировать самые популярные пакеты, например, get_it и injectable.
Flutter тоже дает свое видение DI, можно ознакомиться с ним в документации.
5 шаг. Интегрировать роутер
Именно на этом этапе, когда в приложении нет как таковых экранов, удобно продумать подход к роутингу в проекте. В зависимости от подхода можно использовать как навигацию из коробки, так и сторонние библиотеки.
Наиболее популярные и стабильные — go_router и auto_route. Если есть необходимость и желание попробовать полноценный декларативный подход — вам подойдет octopus.
Готово! Приложение полностью подготовлено к написанию самой первой фичи.
В идеале на этом этапе настроить тему приложения и текстовые стили по дизайну, создать глобальные виджеты или даже целый UI kit, настроить http-клиент для управления запросами. Но все эти пункты зависят от вашего проекта, поэтому в основную последовательность не включаем.
Делитесь, каким было ваше первое приложение?
Любой Flutter-разработчик, создавая свой первый проект задавался хоть раз вопросом — с чего начать, что делать и, главное, как упростить себе работу? Сегодня разберу основные шаги, которые позволят подготовить базу.
1 шаг. Создание проекта
Здесь все очень просто - создать проект можно всего одной командой через терминал. Достаточно выполнить:
flutter create new_app
Вуаля! Проект с названием new_app создан и готов к работе. С помощью дополнительных опций можно конфигурировать проект. Например, опция
--empty
создаст его пустым, без шаблонов. О других вариантах можно прочитать здесь.
2 шаг. Настройка запуска приложения
Все знают: чтобы запустить Flutter-проект, достаточно вызвать метод
main() и
запустить функцию runApp()
с виджетом приложения внутри. Здесь вы также можете выполнять любые настройки, которые потребуются перед запуском вашего приложения — например, устанавливать ориентацию экрана и базовую локализацию. Что обязательно стоит предусмотреть перед запуском — это верхнеуровневую обработку ошибок. Она поможет избежать падений приложения, если где-то в коде ошибка не будет локально обработана.
Здесь нужно инициализировать
FlutterError.onError
и PlatformDispatcher.instance.onError
, указать, как именно приложение должно реагировать на ошибки фреймворка и платформы.Основной совет — раннер приложения стоит делать максимально простым, чтобы запуск не был слишком долгим. И постарайтесь предусмотреть любые вероятности возникновения ошибок во время запуска.
3 шаг. Настройка флаворов
Здесь на помощь вам придет библиотека flutter_flavorizr.
Подключаете пакет в зависимости вашего приложения, а дальше дело за малым — по примеру из документации нужно указать, какие именно флаворы необходимы, какое название и bundleId должно иметь приложение и какие плаформы оно будет поддерживать:
flavorizr:
flavors:
prod:
app:
name: "New App"
android:
applicationId: "com.example.prod"
ios:
bundleId: "com.example.prod"
dev:
app:
name: "New App Dev"
android:
applicationId: "com.example.dev"
ios:
bundleId: "com.example.dev"
Далее остается только запустить кодогенерацию.
flutter pub run flutter_flavorizr
Немного подробнее про флаворы можно почитать здесь.
4 шаг. Реализовать DI
Здесь конкретных рекомендаций нет, каждый разработчик самостоятельно выбирает подход — можно сделать самописную реализацию, без кодогенерации и сторонних библиотек, можно интегрировать самые популярные пакеты, например, get_it и injectable.
Flutter тоже дает свое видение DI, можно ознакомиться с ним в документации.
5 шаг. Интегрировать роутер
Именно на этом этапе, когда в приложении нет как таковых экранов, удобно продумать подход к роутингу в проекте. В зависимости от подхода можно использовать как навигацию из коробки, так и сторонние библиотеки.
Наиболее популярные и стабильные — go_router и auto_route. Если есть необходимость и желание попробовать полноценный декларативный подход — вам подойдет octopus.
Готово! Приложение полностью подготовлено к написанию самой первой фичи.
В идеале на этом этапе настроить тему приложения и текстовые стили по дизайну, создать глобальные виджеты или даже целый UI kit, настроить http-клиент для управления запросами. Но все эти пункты зависят от вашего проекта, поэтому в основную последовательность не включаем.
Делитесь, каким было ваше первое приложение?
Всем привет, это Роза, Flutter Dev Friflex! 👋
В прошлый раз мы обсудили, как можно создать собственное расширение для DevTools и какие пакеты для этого пригодятся. Я показала вам пример простенького расширения, оформленного как отдельный Dart-пакет.
Сегодня расскажу, как встроить такое расширение прямо в существующий pub-пакет.
Допустим, у вас уже есть пакет с реализованным функционалом, для которого вы хотите сделать DevTools-расширение. В таком случае нет смысла создавать отдельную зависимость только ради расширения — проще сделать расширение частью этого же пакета.
Например, когда пользователь подключает
💡 Как это реализовать?
Все довольно просто. В вашем Dart-пакете, который предоставляет расширение DevTools, нужно добавить каталог
А структура папки
Вы получите примерно такую структуру:
Но не стоит использовать этот подход, если функциональность расширения и самого пакета никак не связаны — это может запутать архитектуру. В этом случае лучше вынести расширение в отдельный pub-пакет и подключать его как
Если же расширение не планируется к повторному использованию и должно быть автономным, его можно разместить в том же репозитории, что и основной пакет, но как отдельный модуль. Такой подход упростит разработку и при подключении через
🔖 Теперь вы знаете еще больше о создании расширений DevTools! В следующий раз я расскажу, как можно взаимодействовать со сторонним кодом с помощью Eval — до встречи.
В прошлый раз мы обсудили, как можно создать собственное расширение для DevTools и какие пакеты для этого пригодятся. Я показала вам пример простенького расширения, оформленного как отдельный Dart-пакет.
Сегодня расскажу, как встроить такое расширение прямо в существующий pub-пакет.
Допустим, у вас уже есть пакет с реализованным функционалом, для которого вы хотите сделать DevTools-расширение. В таком случае нет смысла создавать отдельную зависимость только ради расширения — проще сделать расширение частью этого же пакета.
Например, когда пользователь подключает
package:some_package
к своему приложению, он автоматически получает доступ к расширению DevTools, встроенному в этот пакет. DevTools при запуске определит наличие расширения и добавит новую вкладку для него.Все довольно просто. В вашем Dart-пакете, который предоставляет расширение DevTools, нужно добавить каталог
extension
на верхнем уровне структуры:some_package/
extension/
lib/
...
А структура папки
extension
будет выглядеть следующим образом:extension/
devtools/
build/
config.yaml
Вы получите примерно такую структуру:
some_app/
packages/
some_package/
extension/
devtools/
build/
...
config.yaml
some_devtools_extension/
lib/
Но не стоит использовать этот подход, если функциональность расширения и самого пакета никак не связаны — это может запутать архитектуру. В этом случае лучше вынести расширение в отдельный pub-пакет и подключать его как
dev_dependency.
Если же расширение не планируется к повторному использованию и должно быть автономным, его можно разместить в том же репозитории, что и основной пакет, но как отдельный модуль. Такой подход упростит разработку и при подключении через
dev_dependency
не повлияет на размер конечного пользовательского приложения.Please open Telegram to view this post
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, это Катя, Flutter Dev Friflex.
Во время разработки мобильных приложений иногда возникает необходимость выполнять команды терминала прямо из кода. Это может пригодиться, например, для автоматизации задач, работы с внешними CLI-инструментами или интеграции с системами сборки. Dart предоставляет способ выполнения таких команд через класс Process.
💡 Что такое Process?
Класс dart:io → Process позволяет запускать внешние процессы и взаимодействовать с ними: передавать аргументы, получать стандартный вывод (stdout), ошибки (stderr) и код завершения.
Как использовать?
Рассмотрим на примере, в котором будем выводить результат flutter --version в консоль.
Что происходит:
▫️Process.run — запускает процесс и возвращает результат после его завершения
▫️'flutter' — команда, которую мы хотим выполнить
▫️['--version'] — список аргументов для команды
▫️result.stdout — стандартный вывод
▫️result.stderr — ошибка
▫️result.exitCode — код завершения (0 — успех, иначе ошибка)
Методы Process
▫️run(executable, arguments) — запускает процесс, дожидается его завершения и возвращает результат
▫️start(executable, arguments) — запускает процесс и возвращает объект Process, не дожидаясь завершения
▫️killPid(pid) — завершает любой процесс по его PID, даже если нет объекта Process
▫️kill() — завершает процесс, запущенный через start.
▫️getter exitCode — возвращает код выхода
Важно помнить
▫️Команда должна быть доступна в окружении (PATH)
▫️Всегда оборачивайте вызовы в try-catch на случай ошибок
▫️Будьте осторожны с пользовательским вводом: не передавайте его напрямую в Process без фильтрации
Мой опыт
Я использовала Process в проекте, где нужно было обрабатывать видео с помощью ffmpeg. Команды нарезки и склейки видео выполнялись прямо из Flutter-приложения, и Process стал отличным способом обернуть это взаимодействие с CLI.
Вывод
Process во Flutter (точнее, в Dart) — это мощный инструмент для расширения возможностей приложения: от автоматизации до взаимодействия с внешними утилитами.
Вы когда-нибудь использовали Process в Flutter или Dart? Для чего он вам понадобился?
Во время разработки мобильных приложений иногда возникает необходимость выполнять команды терминала прямо из кода. Это может пригодиться, например, для автоматизации задач, работы с внешними CLI-инструментами или интеграции с системами сборки. Dart предоставляет способ выполнения таких команд через класс Process.
Класс dart:io → Process позволяет запускать внешние процессы и взаимодействовать с ними: передавать аргументы, получать стандартный вывод (stdout), ошибки (stderr) и код завершения.
Как использовать?
Рассмотрим на примере, в котором будем выводить результат flutter --version в консоль.
import 'dart:io';
void main() async {
final result = await Process.run('flutter', ['--version']);
if (result.exitCode == 0) {
print('Flutter version: ${result.stdout}');
} else {
print(Error: ${result.stderr}');
}
}
Что происходит:
▫️Process.run — запускает процесс и возвращает результат после его завершения
▫️'flutter' — команда, которую мы хотим выполнить
▫️['--version'] — список аргументов для команды
▫️result.stdout — стандартный вывод
▫️result.stderr — ошибка
▫️result.exitCode — код завершения (0 — успех, иначе ошибка)
Методы Process
▫️run(executable, arguments) — запускает процесс, дожидается его завершения и возвращает результат
▫️start(executable, arguments) — запускает процесс и возвращает объект Process, не дожидаясь завершения
▫️killPid(pid) — завершает любой процесс по его PID, даже если нет объекта Process
▫️kill() — завершает процесс, запущенный через start.
▫️getter exitCode — возвращает код выхода
Важно помнить
▫️Команда должна быть доступна в окружении (PATH)
▫️Всегда оборачивайте вызовы в try-catch на случай ошибок
▫️Будьте осторожны с пользовательским вводом: не передавайте его напрямую в Process без фильтрации
Мой опыт
Я использовала Process в проекте, где нужно было обрабатывать видео с помощью ffmpeg. Команды нарезки и склейки видео выполнялись прямо из Flutter-приложения, и Process стал отличным способом обернуть это взаимодействие с CLI.
Вывод
Process во Flutter (точнее, в Dart) — это мощный инструмент для расширения возможностей приложения: от автоматизации до взаимодействия с внешними утилитами.
Вы когда-нибудь использовали Process в Flutter или Dart? Для чего он вам понадобился?
Please open Telegram to view this post
VIEW IN TELEGRAM
Всем привет! На связи Анна, Friflex Flutter Team Lead👋
🔑 Как часто вы используете ключи виджетов в ваших приложениях? Сегодня поговорим, какие ключи бывают, чем отличаются и как могут быть полезны на практике.
Каждый виджет во Flutter имеет nullable поле
Под капотом у Flutter — дерево виджетов (Widget tree) и дерево элементов (Element tree). Здесь кратко разберемся в отличиях.
Виджет можно описать как определенное визуальное представление объекта интерфейса. На экране может быть много его одинаковых экземпляров, они могут использоваться в разных местах. Каждое такое место характеризуется элементом. При этом виджеты могут меняться местами, а элементы остаются, лишь заменяя текущую связь на связь с другим виджетом.
Чтобы глубже разобраться в том, как работает дерево виджетов и дерево элементов, рекомендую статью на Хабре.
И тут возникает вполне понятный вопрос — при чем же тут ключи? Ключ создает связь конкретного виджета с элементом. С его помощью можно легко управлять рендерингом виджета, сохранять его состояние.
Ключи бывают двух видов — глобальные и локальные. К глобальным относится GlobalKey, у локальных есть три вариации — ValueKey, ObjectKey и UniqueKey.
Локальные ключи сохраняют состояние виджета на своем одном уровне дерева, только в рамках текущего контекста.
▫️UniqueKey — не принимает никакого значения, он уникален сам по себе.
▫️ValueKey и ObjectKey — уникальны за счет своего значения. Отличаются механизмом сравнения под капотом (ValueKey сравнивает значение value, а ObjectKey выполняет сравнение по ссылке).
▫️Глобальные ключи GlobalKey могут управлять состоянием по всему дереву, поэтому дают доступ к текущему контексту виджета.
Перед добавлением ключа важно определиться с целью. Например, для управления декларативной навигацией в приложении отлично подойдет GlobalKey. А если идентифицировать виджеты нужно только в рамках одной страницы, на помощь придут локальные виджеты.
Делитесь в комментариях своим опытом использования ключей🙌
Каждый виджет во Flutter имеет nullable поле
key
. Сюда мы можем передавать различные имплементации класса Key.Под капотом у Flutter — дерево виджетов (Widget tree) и дерево элементов (Element tree). Здесь кратко разберемся в отличиях.
Виджет можно описать как определенное визуальное представление объекта интерфейса. На экране может быть много его одинаковых экземпляров, они могут использоваться в разных местах. Каждое такое место характеризуется элементом. При этом виджеты могут меняться местами, а элементы остаются, лишь заменяя текущую связь на связь с другим виджетом.
Чтобы глубже разобраться в том, как работает дерево виджетов и дерево элементов, рекомендую статью на Хабре.
И тут возникает вполне понятный вопрос — при чем же тут ключи? Ключ создает связь конкретного виджета с элементом. С его помощью можно легко управлять рендерингом виджета, сохранять его состояние.
Ключи бывают двух видов — глобальные и локальные. К глобальным относится GlobalKey, у локальных есть три вариации — ValueKey, ObjectKey и UniqueKey.
Локальные ключи сохраняют состояние виджета на своем одном уровне дерева, только в рамках текущего контекста.
▫️UniqueKey — не принимает никакого значения, он уникален сам по себе.
▫️ValueKey и ObjectKey — уникальны за счет своего значения. Отличаются механизмом сравнения под капотом (ValueKey сравнивает значение value, а ObjectKey выполняет сравнение по ссылке).
▫️Глобальные ключи GlobalKey могут управлять состоянием по всему дереву, поэтому дают доступ к текущему контексту виджета.
Перед добавлением ключа важно определиться с целью. Например, для управления декларативной навигацией в приложении отлично подойдет GlobalKey. А если идентифицировать виджеты нужно только в рамках одной страницы, на помощь придут локальные виджеты.
Делитесь в комментариях своим опытом использования ключей
Please open Telegram to view this post
VIEW IN TELEGRAM
Проверка на внимательность. Какой ключ будете использовать для управления декларативной навигацией в приложении?
Anonymous Quiz
4%
ValueKey
11%
ObjectKey
78%
GlobalKey
8%
UniqueKey
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, это Роза, Flutter Dev Friflex, и я продолжаю серию постов про расширения DevTools.
В прошлый раз рассказала, как встроить расширение прямо в существующий pub-пакет. Сегодня разберем, как взаимодействовать с кодом приложения извне и динамически выполнять Dart-код. Поговорим об EvalOnDartLibrary.
EvalOnDartLibrary — это класс из пакета devtools_app_shared, который позволяет выполнять Dart-код прямо из вашего расширения DevTools.
Благодаря нему можно:
▫️управлять состоянием приложения
▫️вызывать методы
▫️получать значения из рантайма
Как это возможно?
1. Инициализация
Пояснения:
◽️ 'package:...' — путь до нужного кода
◽️ serviceManager.service! — экземпляр
◽️ Disposable — защищает от утечек памяти при закрытии DevTools
2. Выполнение кода
EvalOnDartLibrary предоставляет три метода:
▪️asyncEval — асинхронное выполнение кода
▪️ eval — синхронное выполнение
▪️ evalInstance — получение экземпляра объекта (для дальнейшей работы с его полями и методами)
▪️ safeEval — безопасная обертка над eval, которая дополнительно обрабатывает ошибки выполнения
Ограничение: у eval и asyncEval есть лимит на размер возвращаемых данных.
Рекомендуется:
1. Сначала вызвать метод через asyncEval или eval
2. Потом получить значение через evalInstance.
Не забывайте передавать isAlive: evalDisposable — без этого могут быть утечки памяти!
Что такое Instance?
Метод evalInstance возвращает Instance — ссылку на результат выполнения кода. Вы можете его преобразовать в строку через valueAsString. Но убедитесь, что значение действительно можно привести к строке (иначе может быть ошибка).
Пример:
Мини-памятка
✔️Используйте Disposable для управления жизненным циклом
✔️ Следите за ограничением на размер результата
✔️ Передавайте только валидные Dart-выражения
✔️ При изменениях в проекте обновляйте пути в EvalOnDartLibrary
Для меня это был неплохой опыт с DevTools — я даже не знала, что так можно. А еще через serviceManager можно получить код приложения через id изолята (но там свои нюансы).
На этом мы заканчиваем нашу мини-серию по расширениям DevTools! Я рассказала далеко не все, но теперь вам будет гораздо проще ориентироваться🚀
Подробнее о методах EvalOnDartLibrary — в комментариях👇
В прошлый раз рассказала, как встроить расширение прямо в существующий pub-пакет. Сегодня разберем, как взаимодействовать с кодом приложения извне и динамически выполнять Dart-код. Поговорим об EvalOnDartLibrary.
EvalOnDartLibrary — это класс из пакета devtools_app_shared, который позволяет выполнять Dart-код прямо из вашего расширения DevTools.
Благодаря нему можно:
▫️управлять состоянием приложения
▫️вызывать методы
▫️получать значения из рантайма
Как это возможно?
1. Инициализация
Future<void> initEval() async {
await serviceManager.onServiceAvailable; // Убедимся, что vmService доступен
_controllerEval = EvalOnDartLibrary(
'package:some_package/src/controller.dart', // Путь к библиотеке, где находится нужный код
serviceManager.service!, // Передаем vmService
serviceManager: serviceManager,
);
evalDisposable = Disposable(); // Обязательно создаем Disposable
}
Пояснения:
◽️ 'package:...' — путь до нужного кода
◽️ serviceManager.service! — экземпляр
VmService
◽️ Disposable — защищает от утечек памяти при закрытии DevTools
2. Выполнение кода
EvalOnDartLibrary предоставляет три метода:
▪️asyncEval — асинхронное выполнение кода
▪️ eval — синхронное выполнение
▪️ evalInstance — получение экземпляра объекта (для дальнейшей работы с его полями и методами)
▪️ safeEval — безопасная обертка над eval, которая дополнительно обрабатывает ошибки выполнения
Ограничение: у eval и asyncEval есть лимит на размер возвращаемых данных.
Рекомендуется:
1. Сначала вызвать метод через asyncEval или eval
2. Потом получить значение через evalInstance.
Future<String?> _getValue() async {
await _controllerEval.asyncEval(
'await SomeController.instance.calculateValue()', // Запуск асинхронной функции
isAlive: evalDisposable,
);
final result = await _controllerEval.evalInstance(
'SomeController.instance.sum.value', // Получение значения после выполнения
isAlive: evalDisposable,
); // Результатом является объект типа Instanse
return result?.valueAsString; // Возвращаем строковое значение
}
Не забывайте передавать isAlive: evalDisposable — без этого могут быть утечки памяти!
Что такое Instance?
Метод evalInstance возвращает Instance — ссылку на результат выполнения кода. Вы можете его преобразовать в строку через valueAsString. Но убедитесь, что значение действительно можно привести к строке (иначе может быть ошибка).
Пример:
final result = await _controllerEval.evalInstance(
'SomeController.instance.sum.value.toString()',
isAlive: evalDisposable,
);
final value = result?.valueAsString;
Мини-памятка
✔️Используйте Disposable для управления жизненным циклом
✔️ Следите за ограничением на размер результата
✔️ Передавайте только валидные Dart-выражения
✔️ При изменениях в проекте обновляйте пути в EvalOnDartLibrary
Для меня это был неплохой опыт с DevTools — я даже не знала, что так можно. А еще через serviceManager можно получить код приложения через id изолята (но там свои нюансы).
На этом мы заканчиваем нашу мини-серию по расширениям DevTools! Я рассказала далеко не все, но теперь вам будет гораздо проще ориентироваться🚀
Подробнее о методах EvalOnDartLibrary — в комментариях
Please open Telegram to view this post
VIEW IN TELEGRAM
Привет, это Катя, Flutter Dev Friflex.
При разработке на Flutter важно не просто уметь писать рабочий код, но и понимать, как работает язык Dart, на котором он основан. Одна из базовых тем — ключевые слова final, const и var. Они отвечают за то, как переменные создаются и ведут себя в процессе выполнения программы. Давайте разберем и повторим базу😁
var (переменная)
Используется для объявления переменной, значение которой может изменяться со временем. Тип переменной определяется автоматически при присвоении значения. Но после первого присвоения типа, переменная не может быть использована с другим типом данных.
Пример:
Если тип данных переменной
Переменная, объявленная с
Пример попытки изменить тип переменной:
final (константа во время выполнения)
Используется для переменных, значение которых можно установить только один раз. После инициализации переменной значение нельзя изменить. В отличие от
Пример:
Пример использования переменной, которая зависит от выполнения программы:
В этом примере время будет определено при выполнении программы и больше не изменится.
const (константа времени компиляции)
Это неизменяемая константа, значение которой должно быть известно на этапе компиляции. Это означает, что значения const должны быть определены заранее и не могут изменяться или вычисляться во время выполнения программы.
Пример:
◽️Переменные, объявленные с
◽️Если переменная
Пример со списком:
Различия между final и const
▫️
▫️
▫️
Пример разницы:
Итоговое сравнение:
▪️
▪️
▪️
При разработке на Flutter важно не просто уметь писать рабочий код, но и понимать, как работает язык Dart, на котором он основан. Одна из базовых тем — ключевые слова final, const и var. Они отвечают за то, как переменные создаются и ведут себя в процессе выполнения программы. Давайте разберем и повторим базу😁
var (переменная)
Используется для объявления переменной, значение которой может изменяться со временем. Тип переменной определяется автоматически при присвоении значения. Но после первого присвоения типа, переменная не может быть использована с другим типом данных.
Пример:
void main() {
var name = 'Alice'; // Тип определяется как String
print(name); // Alice
name = 'Bob'; // Допустимо, так как тип остаётся String
print(name); // Bob
}
Если тип данных переменной
var
известен при создании, Dart сам выводит тип переменной (например, String, int
и т.д.).Переменная, объявленная с
var,
может изменять свое значение, но не тип.Пример попытки изменить тип переменной:
var age = 30; age = "thirty"; // Ошибка: String нельзя присвоить переменной типа int.
final (константа во время выполнения)
Используется для переменных, значение которых можно установить только один раз. После инициализации переменной значение нельзя изменить. В отличие от
const
, переменная с final
может быть инициализирована значением, которое становится известно только во время выполнения программы.Пример:
void main() {
final name = 'Charlie'; // name будет неизменяемым
print(name); // Charlie
name = 'Dave'; // Ошибка, нельзя изменить значение переменной
}
final
используется для значений, которые известны только во время выполнения программы.Пример использования переменной, которая зависит от выполнения программы:
void main() {
final currentTime = DateTime.now(); // Значение определяется во время выполнения
print(currentTime);
}
В этом примере время будет определено при выполнении программы и больше не изменится.
const (константа времени компиляции)
Это неизменяемая константа, значение которой должно быть известно на этапе компиляции. Это означает, что значения const должны быть определены заранее и не могут изменяться или вычисляться во время выполнения программы.
Пример:
void main() {
const pi = 3.1415;
print(pi); // 3.1415
pi = 3.14; // Ошибка, нельзя изменить значение константы
}
◽️Переменные, объявленные с
const
, являются неизменяемыми и создаются во время компиляции программы.◽️Если переменная
const
имеет сложный тип (например, список), то она становится полностью неизменяемой (не только ссылка, но и сам объект).Пример со списком:
void main() {
const numbers = [1, 2, 3];
// numbers[0] = 10; // Ошибка, элементы списка изменить нельзя
print(numbers);
}
Различия между final и const
▫️
final
позволяет установить значение переменной один раз, но это значение может быть вычислено во время выполнения программы.▫️
const
требует, чтобы значение было известно на этапе компиляции.▫️
const
можно использовать для создания неизменяемых объектов, которые становятся доступными еще до выполнения программы, в отличие от final
.Пример разницы:
final currentTime = DateTime.now(); // Работает, так как значение вычисляется во время выполнения.
const timeConst = DateTime.now(); // Ошибка: const переменные не могут быть вычислены во время выполнения.
Итоговое сравнение:
▪️
var
— изменяемая переменная, тип выводится автоматически.▪️
final
— неизменяемая переменная, значение можно присвоить один раз, но это может произойти во время выполнения.▪️
const
— неизменяемая переменная, значение которой должно быть известно во время компиляции.Привет, это Анна, Friflex Flutter Team Lead!👋
Один из базовых приемов для повышения уровня безопасности вашего Flutter-приложения — это обфускация кода. Сегодня разберем, что это, как использовать и какие есть нюансы.
Обфускация простыми словами — некоторое запутывание кода с помощью определенного набора символов. При обфускации код сборки становится нечитаемым для человека. Названия всех методов и классов подменяются другими символами.
Какую проблему решает? Обфускация сильно затрудняет процесс понимания кода, полученного в процессе реверс-инжиниринга. Посмотрим, как это работает.
Возьмем простое приложение, тот шаблон, который генерируется автоматически при создании Flutter-проекта. Соберем две сборки, одну с обфускацией, другую — без. Затем проведем процесс реверс-инжиниринга обеих сборок с помощью инструмента Blutter.
Результат — на карточке👆
Вывод — код сборки с обфускацией прочитать почти невозможно.
Обфусцировать код Flutter-приложения очень просто – достаточно запустить команду сборки следующим образом:
❕ При обфускации нельзя в коде использовать сравнение строковых представлений runtime-типов.
Попробуйте для примера сделать подобную реализацию в сборке с обфускацией и без.
Без обфускации на экране появятся значения
🔥 — если используете обфускацию в своих проектах.
Один из базовых приемов для повышения уровня безопасности вашего Flutter-приложения — это обфускация кода. Сегодня разберем, что это, как использовать и какие есть нюансы.
Обфускация простыми словами — некоторое запутывание кода с помощью определенного набора символов. При обфускации код сборки становится нечитаемым для человека. Названия всех методов и классов подменяются другими символами.
Какую проблему решает? Обфускация сильно затрудняет процесс понимания кода, полученного в процессе реверс-инжиниринга. Посмотрим, как это работает.
Возьмем простое приложение, тот шаблон, который генерируется автоматически при создании Flutter-проекта. Соберем две сборки, одну с обфускацией, другую — без. Затем проведем процесс реверс-инжиниринга обеих сборок с помощью инструмента Blutter.
Результат — на карточке👆
Вывод — код сборки с обфускацией прочитать почти невозможно.
Обфусцировать код Flutter-приложения очень просто – достаточно запустить команду сборки следующим образом:
flutter build <build-target> \
--obfuscate \
--split-debug-info=/<symbols-directory>
Здесь очень важно использовать опцию --split-debug-info
, она по указанному пути выгрузит символы обфускации, с помощью которых можно будет в дальнейшем расшифровывать важные логи, например, стектрейсы ошибок. Попробуйте для примера сделать подобную реализацию в сборке с обфускацией и без.
class User {}
Text(user.runtimeType.toString());
Text ("${user.runtimeType.toString() == "User"");
Без обфускации на экране появятся значения
User
и true
. С обфускацией в первом виджете будет набор рандомных символов, во втором — false
. 🔥 — если используете обфускацию в своих проектах.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
Топ-5 за этот месяц:
Готовим на май еще больше полезного контента. Пусть ваш код летает, а баги обходят стороной
Please open Telegram to view this post
VIEW IN TELEGRAM
Привет! Это Роза, Flutter Dev Friflex! 👋
Когда я только начинала работать, почти не пользовалась горячими клавишами. У меня была мышка. И этого вроде бы хватало, но со временем мне захотелось ускорить свою работу, поэтому я целенаправленно начала внедрять шорткаты в свою разработку. И это было лучшим решением!
Хочу поделиться и с вами самыми полезными из них — вдруг пригодятся.
🔴 Начнем с навигации и поиска:
⌘ + P — открыть файл по имени
⌘ + Shift + F — поиск по всему проекту
⌘ + G — перейти к строке
⌘ + Shift + O — перейти к функции или символу в файле
🔴 Редактирование кода:
Control + Space — автодополнение
⌘ + / — закомментировать или раскомментировать строку
Option + ↑/↓ — переместить строку вверх или вниз
Shift + Option + ↑/↓ — скопировать строку вверх или вниз
⌘ + Shift + K — удалить строку
Shift + Option + F — отформатировать документ
Option + Click — добавить множественный курсор
⌘ + Shift + L — выделить все вхождения слова
⌘ + F, затем ⌘ + Option + Enter — быстрая замена
🔴 Мультикурсор и выделения:
Option + Click — добавить курсор в точку клика
⌥⌘ + ↑/↓ — вставить курсор выше / ниже
⌘ + U — отменить последнее действие с курсором
⇧⌥ + I — вставить курсор в конец каждой выбранной строки
⌘ + L — выделить текущую строку
⇧⌘ + L — выделить все вхождения текущего выделения
⌘ + F2 — выделить все вхождения текущего слова
Control + ⇧⌘ + →/← — расширить/уменьшить выделение
🔴 Блочное выделение:
⇧⌥ + drag — выделение прямоугольником (box selection)
⇧⌥⌘ + ↑/↓/←/→ — выделение вверх/вниз/влево/вправо
⇧⌥⌘ + PgUp/PgDn — постраничное вертикальное выделение
💡 Конечно, вы можете настроить сочетания клавиш под себя и увидеть весь их список:
Code → Settings → Keyboard Shortcuts или нажмите ⌘ + K, затем ⌘ + S.
✏️ Также полный список есть тут: PDF от VSCode.
Когда я только начинала работать, почти не пользовалась горячими клавишами. У меня была мышка. И этого вроде бы хватало, но со временем мне захотелось ускорить свою работу, поэтому я целенаправленно начала внедрять шорткаты в свою разработку. И это было лучшим решением!
Хочу поделиться и с вами самыми полезными из них — вдруг пригодятся.
⌘ + P — открыть файл по имени
⌘ + Shift + F — поиск по всему проекту
⌘ + G — перейти к строке
⌘ + Shift + O — перейти к функции или символу в файле
Control + Space — автодополнение
⌘ + / — закомментировать или раскомментировать строку
Option + ↑/↓ — переместить строку вверх или вниз
Shift + Option + ↑/↓ — скопировать строку вверх или вниз
⌘ + Shift + K — удалить строку
Shift + Option + F — отформатировать документ
Option + Click — добавить множественный курсор
⌘ + Shift + L — выделить все вхождения слова
⌘ + F, затем ⌘ + Option + Enter — быстрая замена
Option + Click — добавить курсор в точку клика
⌥⌘ + ↑/↓ — вставить курсор выше / ниже
⌘ + U — отменить последнее действие с курсором
⇧⌥ + I — вставить курсор в конец каждой выбранной строки
⌘ + L — выделить текущую строку
⇧⌘ + L — выделить все вхождения текущего выделения
⌘ + F2 — выделить все вхождения текущего слова
Control + ⇧⌘ + →/← — расширить/уменьшить выделение
⇧⌥ + drag — выделение прямоугольником (box selection)
⇧⌥⌘ + ↑/↓/←/→ — выделение вверх/вниз/влево/вправо
⇧⌥⌘ + PgUp/PgDn — постраничное вертикальное выделение
Code → Settings → Keyboard Shortcuts или нажмите ⌘ + K, затем ⌘ + S.
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from Friflex Dev
Норм или стрем использовать сторонние библиотеки под любую проблему? А выносить виджеты в методы вместо создания отдельных классов? Ответы — в Катиной статье «Антитренды в мобильной разработке на Flutter».
Она разобрала решения, которые кажутся удобными, а на деле раздражают и замедляют работу, и предложила, чем их стоит заменить.
Please open Telegram to view this post
VIEW IN TELEGRAM
Привет, с вами вновь Катя, Flutter Dev Friflex.
Сегодня поговорим про библиотеку equatable, которая помогает упростить сравнение объектов.
Установка
Зачем нужна библиотека?
В Dart по умолчанию два объекта считаются равными, только если они ссылаются на один и тот же экземпляр в памяти. Но часто требуется сравнивать объекты по их полям. Equatable автоматически реализует методы == (оператор равенства) и hashCode, что избавляет разработчиков от написания шаблонного кода.
Без Equatable:
С Equatable:
Основные возможности
▫️Equatable переопределяет == и hashCode на основе списка полей (props)
▫️Можно использовать Equatable в иерархии классов
▫️Рекомендуется использовать с неизменяемыми (final) полями
▫️Часто применяется в State Management для эффективного сравнения состояний
Использование EquatableMixin
Если ваш класс уже наследуется от другого класса, но вам нужна функциональность Equatable, можно использовать
EquatableMixin:
Преимущества EquatableMixin
▫️Позволяет добавить сравнение объектов без изменения иерархии наследования
▫️Сохраняет все возможности Equatable, но в виде миксина
Пример использования
📎 Ссылка на библиотеку
❤️ — если используете Equatable в ваших проетах
Сегодня поговорим про библиотеку equatable, которая помогает упростить сравнение объектов.
Установка
Yaml
dependencies:
equatable: ^2.0.7
Зачем нужна библиотека?
В Dart по умолчанию два объекта считаются равными, только если они ссылаются на один и тот же экземпляр в памяти. Но часто требуется сравнивать объекты по их полям. Equatable автоматически реализует методы == (оператор равенства) и hashCode, что избавляет разработчиков от написания шаблонного кода.
Без Equatable:
class Person {
Person(this.name, this.age);
final String name;
final int age;
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Person &&
other.name == name &&
other.age == age;
}
@override
int get hashCode => name.hashCode ^ age.hashCode;
}
С Equatable:
import 'package:equatable/equatable.dart';
class Person extends Equatable {
Person(this.name, this.age);
final String name;
final int age;
@override
List<Object?> get props => [name, age]; // Поля для сравнения
}
Основные возможности
▫️Equatable переопределяет == и hashCode на основе списка полей (props)
▫️Можно использовать Equatable в иерархии классов
▫️Рекомендуется использовать с неизменяемыми (final) полями
▫️Часто применяется в State Management для эффективного сравнения состояний
Использование EquatableMixin
Если ваш класс уже наследуется от другого класса, но вам нужна функциональность Equatable, можно использовать
EquatableMixin:
import 'package:equatable/equatable.dart';
class Person extends SomeOtherClass with EquatableMixin {
Person(this.name, this.age);
final String name;
final int age;
@override
List<Object?> get props => [name, age];
}
Преимущества EquatableMixin
▫️Позволяет добавить сравнение объектов без изменения иерархии наследования
▫️Сохраняет все возможности Equatable, но в виде миксина
Пример использования
void main() {
final person1 = Person("Alice", 30);
final person2 = Person("Alice", 30);
print(person1 == person2); // true (без Equatable было бы false)
}
📎 Ссылка на библиотеку
❤️ — если используете Equatable в ваших проетах
Всем привет! С вами Анна, Friflex Flutter Team Lead👋
Одна из самых распространенных болей многих Flutter-проектов — это отсутствие тестов в коде. И хоть тема не новая, поговорим сегодня о том, какие виды тестов бывают, для чего они нужны и почему их наличие может спасти ваш проект.
Существуют три вида тестов:
🔸 Unit-тесты
Их основная задача —протестировать работоспособность какой-то одной конкретной функции, метода, класса по всем возможным сценариям
🔸 Widget-тесты
Этот тип тестов призван проверять именно отдельные виджеты. Widget-тесты позволяют проверить, действительно ли тот или иной виджет корректно располагается на экране и правильно себя ведет при взаимодействии с ним пользователя
🔸 Integration-тесты
Интеграционные тесты — самый сложный в настройке, но очень полезный тип тестов. Он позволяет проверить большие участки вашего приложения, протестировать взаимодействие разных модулей и виджетов между собой. Если юнит- и виджет-тесты призваны проверять работу отдельно только одного объекта в проекте, то интеграционные проверяет все в совокупности. Кроме этого, они также позволяют проверить производительность всего приложения.
Почему же отсутствие тестов является проблемой? Многие разработчики скажут, что написание тестов — лишняя трата времени, человеческих ресурсов и вообще занятие монотонное и скучное. Но здесь есть пара весомых доводов.
✅ Когда проект маленький, простой, не нагружен сложной логикой, его ручное тестирование занимает немного времени и усилий QA-специалистов. Но с ростом приложения, с течением времени многие нюансы той или иной фичи забываются, их ручная проверка становится все менее эффективной.
✅ Сюда же можно отнести и человеческий фактор — разработчики, тестировщики могут меняться, а знания — теряться.
Здесь на помощь приходят тесты в коде проекта. Стоит всего один раз при разработке того или иного функционала покрыть тестами всевозможные сценарии, и в будущем вероятность пропустить в продакшн сломанный функционал сильно снижается.
💡 Совет: интегрируйте в свой CI/CD процесс проверки всех написанных тестов в проекте. Если тесты не проходят, сборке или пул-реквесту лучше не попадать на следующий этап жизненного цикла задачи. Так вы не будете забывать вовремя править тесты в случае изменения логики работы приложения, а также сэкономите время коллег на проверку.
Одна из самых распространенных болей многих Flutter-проектов — это отсутствие тестов в коде. И хоть тема не новая, поговорим сегодня о том, какие виды тестов бывают, для чего они нужны и почему их наличие может спасти ваш проект.
Существуют три вида тестов:
Их основная задача —протестировать работоспособность какой-то одной конкретной функции, метода, класса по всем возможным сценариям
Этот тип тестов призван проверять именно отдельные виджеты. Widget-тесты позволяют проверить, действительно ли тот или иной виджет корректно располагается на экране и правильно себя ведет при взаимодействии с ним пользователя
Интеграционные тесты — самый сложный в настройке, но очень полезный тип тестов. Он позволяет проверить большие участки вашего приложения, протестировать взаимодействие разных модулей и виджетов между собой. Если юнит- и виджет-тесты призваны проверять работу отдельно только одного объекта в проекте, то интеграционные проверяет все в совокупности. Кроме этого, они также позволяют проверить производительность всего приложения.
Почему же отсутствие тестов является проблемой? Многие разработчики скажут, что написание тестов — лишняя трата времени, человеческих ресурсов и вообще занятие монотонное и скучное. Но здесь есть пара весомых доводов.
Здесь на помощь приходят тесты в коде проекта. Стоит всего один раз при разработке того или иного функционала покрыть тестами всевозможные сценарии, и в будущем вероятность пропустить в продакшн сломанный функционал сильно снижается.
Please open Telegram to view this post
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, это Роза, Flutter Dev Friflex! 👋
Рано или поздно в вашем проекте может возникнуть задача — получить скриншот конкретного виджета. Как это реализовать?
Существует несколько подходов:
▪️Готовое решение — например, пакет
▪️Более низкоуровневый подход — через
💡Интересный факт: внутри себя
В этом посте мы рассмотрим, как превратить любой виджет в изображение, готовое для ваших дальнейших действий.
1. Создаем глобальный ключ
Он нужен, чтобы получить доступ к
2. Оборачиваем нужный виджет в
Это гарантирует, что будет захвачен только нужный виджет.
3. Захватываем изображение
Метод, который преобразует виджет в
Вот и все! Теперь у нас есть изображение в виде байтов (
✔️сохранить в файл
✔️передать по сети
✔️вложить в PDF
✔️отобразить в UI
🧠 Немного теории:
▫️изолировать перерисовки
▫️ повысить производительность
▫️ использовать offscreen rendering
▫️ делать захват изображения (как в нашем примере)
Ставьте ❤️ — и в следующем посте глубже рассмотрим
Рано или поздно в вашем проекте может возникнуть задача — получить скриншот конкретного виджета. Как это реализовать?
Существует несколько подходов:
▪️Готовое решение — например, пакет
screenshot
▪️Более низкоуровневый подход — через
RenderRepaintBoundary
💡Интересный факт: внутри себя
screenshot
использует RenderRepaintBoundary,
поэтому понимание его работы будет полезным даже при использовании готового пакета.В этом посте мы рассмотрим, как превратить любой виджет в изображение, готовое для ваших дальнейших действий.
1. Создаем глобальный ключ
Он нужен, чтобы получить доступ к
RenderRepaintBoundary
:final GlobalKey _widgetKey = GlobalKey();
2. Оборачиваем нужный виджет в
RepaintBoundary
Это гарантирует, что будет захвачен только нужный виджет.
RepaintBoundary(
key: _widgetKey,
child: const SomeWidget(),
)
3. Захватываем изображение
Метод, который преобразует виджет в
Uint8List:
Future<void> _captureAndSave(BuildContext context) async {
try {
// Получаем рендер-границу по ключу
final boundary = _widgetKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
// Захватываем изображение (можно увеличить pixelRatio для лучшего качества)
final image = await boundary.toImage(pixelRatio: 3.0);
// Преобразуем изображение в байты
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final pngBytes = byteData!.buffer.asUint8List();
// Тут можно сохранить файл.
...
} catch (e) {
debugPrint('Ошибка при захвате: $e');
}
}
Вот и все! Теперь у нас есть изображение в виде байтов (
Uint8List
), с которым можно делать все, что угодно:✔️сохранить в файл
✔️передать по сети
✔️вложить в PDF
✔️отобразить в UI
🧠 Немного теории:
RepaintBoundary
– важный инструмент для оптимизации производительности. Он создает «графическую границу», отделяя часть дерева виджетов от остального. Это позволяет:▫️изолировать перерисовки
▫️ повысить производительность
▫️ использовать offscreen rendering
▫️ делать захват изображения (как в нашем примере)
Ставьте ❤️ — и в следующем посте глубже рассмотрим
RepaintBoundary
: как он работает, влияет на рендеринг и какие у него есть особенностиПривет, с вами вновь Катя, Flutter Dev Friflex. Сегодня разберем основные способы декомпозиции и лучшие практики.
Декомпозиция виджетов — это процесс разделения сложного виджета на более простые и независимые компоненты.
Зачем это нужно?
▫️ Упрощение кода: когда разделяешь виджеты на более мелкие компоненты, код становится более понятным и легким для восприятия
▫️ Повторное использование: мелкие виджеты можно использовать в разных частях приложения, что снижает дублирование кода
▫️ Улучшение производительности: использование отдельных виджетов позволяет более эффективно управлять состоянием и перерисовкой интерфейса
▫️ Тестируемость: мелкие виджеты легче тестировать, что способствует созданию более надежного кода
Виды декомпозирования
1. Использование билд-методов
Вынесение частей виджета в отдельные методы внутри того же класса.
✅ Маленький плюсик:
Мало кода — не требует создания новых классов
❌ Минусы:
▫️Нет переиспользуемости — методы привязаны к конкретному виджету
▫️Сложность тестирования — нельзя протестировать отдельно от родителя
▫️Риск создания «метод-супов» — чрезмерное количество методов ухудшает читаемость
▫️Ухудшает производительность — каждый setState в таком методе будет полностью перерисовывать виджет
2. Разделение на Stateful и Stateless виджеты
Выделение логики с состоянием в отдельные StatefulWidget, а статичной верстки — в StatelessWidget.
✅ Плюсы:
▫️Четкое разделение ответственности — состояние отделено от представления
▫️Упрощенное тестирование — Stateless-часть можно тестировать без состояния
▫️Повышенная производительность — Stateless-виджеты не перестраиваются без необходимости
▫️Гибкость — можно менять состояние без изменения UI и наоборот
❌ Минусы:
Больше кода — требуется создание дополнительных классов
Если заметили, то плюсы и минусы у этих вариантов противоположны.
📎Есть классное короткое видео от Flutter-команды, рекомендую посмотреть его.
Декомпозиция виджетов — это процесс разделения сложного виджета на более простые и независимые компоненты.
Зачем это нужно?
▫️ Упрощение кода: когда разделяешь виджеты на более мелкие компоненты, код становится более понятным и легким для восприятия
▫️ Повторное использование: мелкие виджеты можно использовать в разных частях приложения, что снижает дублирование кода
▫️ Улучшение производительности: использование отдельных виджетов позволяет более эффективно управлять состоянием и перерисовкой интерфейса
▫️ Тестируемость: мелкие виджеты легче тестировать, что способствует созданию более надежного кода
Виды декомпозирования
1. Использование билд-методов
Вынесение частей виджета в отдельные методы внутри того же класса.
class ProductCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: [
_buildTitle(),
_buildPrice(),
],
),
);
}
Widget _buildTitle() { ... }
Widget _buildPrice() { ... }
}
✅ Маленький плюсик:
Мало кода — не требует создания новых классов
❌ Минусы:
▫️Нет переиспользуемости — методы привязаны к конкретному виджету
▫️Сложность тестирования — нельзя протестировать отдельно от родителя
▫️Риск создания «метод-супов» — чрезмерное количество методов ухудшает читаемость
▫️Ухудшает производительность — каждый setState в таком методе будет полностью перерисовывать виджет
2. Разделение на Stateful и Stateless виджеты
Выделение логики с состоянием в отдельные StatefulWidget, а статичной верстки — в StatelessWidget.
// Stateful часть
class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count = 0;
void increment() => setState(() => count++);
@override
Widget build(BuildContext context) {
return CounterView(
count: count,
onIncrement: increment,
);
}
}
// Stateless часть
class CounterView extends StatelessWidget {
// Логика
}
✅ Плюсы:
▫️Четкое разделение ответственности — состояние отделено от представления
▫️Упрощенное тестирование — Stateless-часть можно тестировать без состояния
▫️Повышенная производительность — Stateless-виджеты не перестраиваются без необходимости
▫️Гибкость — можно менять состояние без изменения UI и наоборот
❌ Минусы:
Больше кода — требуется создание дополнительных классов
Если заметили, то плюсы и минусы у этих вариантов противоположны.
📎Есть классное короткое видео от Flutter-команды, рекомендую посмотреть его.
Какой вид декомпозирования вы чаще всего используете?
Anonymous Poll
5%
Использование билд-методов
71%
Разделение на Stateful и Stateless виджеты
24%
Оба
Всем привет! Это Анна, Friflex Flutter Team Lead👋
Сегодня затронем еще одну тему, которая в сообществе разработчиков всегда вызывает бурные обсуждения. Поговорим о документации и комментировании кода: насколько это необходимо и каких правил стоит придерживаться.
Для начала разберемся, чем отличается документация от комментария в коде.
▫️Документация — это характеристика конкретного объекта, класса, метода, описание его назначения, параметров и механики работы. Оформляется в коде через
Если мы добавим этот пример в любую IDE, при наведении мышью на метод будет высвечиваться наша добавленная документация. При этом специальные знаки помогают удобно форматировать описание. Например, квадратные скобки позволяют обозначить параметр, а дефисы — организовать маркированный список.
▫️Комментарий — это обычное словесное описание алгоритма действий. Если в коде встречается сложный участок, где логика не совсем очевидна, или имеются какие-то важные сведения, которые разработчик хочет передать своим коллегам и себе в будущее, оформлять их стоит именно как комментарий. Они выделяются с помощью
Как показывает практика, и документация, и комметрирование кода — очень удобные инструменты, которые позволяют сделать проект максимально понятным и простым для вхождения новых разработчиков.
Так почему же тема холиварная?
В первую очередь потому что зачастую и документация, и комментарии в проекте не систематизированы, оформляются хаотично, чем только мешают и запутывают. Разработчики из-за этих проблем не видят в них никакой пользы, только лишь дополнительную трату времени.
Как это исправить?
Поможет соответствие ряду правил.
1. Зафиксировать в проекте порядок комментирования и документации кода.
Очень удобно на уровне всей команды заранее определить шаблоны. В будущем это позволит держать весь проект в едином формате.
2. Думать о том, как этот участок кода может восприниматься другими разработчиками или тобой через большой промежуток времени.
В больших проектах, которые разрабатываются и поддерживаются долгое время, не стоит полагаться на свою память или память коллег. Нюансы имеют свойство забываться, разработчики могут менять проекты и место работы, поэтому полезно фиксировать сложные места сразу.
3. Не комментировать лишнее.
Такая ошибка часто встречается, что вызывает негатив к комментированию в целом. Важно покрывать комментариями только то, что действительно нуждается в пояснении. Например, переменные с говорящими названиями или простые условия в блоке if описывать смысла нет.
4. Больше текста — не значит лучше.
Документация и комментарии должны быть емкими, простыми для восприятия. Если текста будет слишком много, вероятность, что его будут пропускать, увеличивается.
💬Делитесь своим опытом и лучшими практиками в комментариях.
Сегодня затронем еще одну тему, которая в сообществе разработчиков всегда вызывает бурные обсуждения. Поговорим о документации и комментировании кода: насколько это необходимо и каких правил стоит придерживаться.
Для начала разберемся, чем отличается документация от комментария в коде.
▫️Документация — это характеристика конкретного объекта, класса, метода, описание его назначения, параметров и механики работы. Оформляется в коде через
///
. Например, возьмем школьную задачку — надо написать метод, который по значениям катетов прямоугольного треугольника будет рассчитывать значение гипотенузы.
/// Метод для вычисления гипотенузы по известным значениям
/// катетов прямоугольного треугольника
///
/// Принимает:
/// - [firstLeg] - первый катет прямоугольного треугольника
/// - [secondLeg] - второй катет прямоугольного треугольника
double calculateHypotenuse(double firstLeg, double secondLeg) {
final hypotenuse = sqrt(firstLeg * firstLeg + secondLeg * secondLeg);
return hypotenuse;
}
Если мы добавим этот пример в любую IDE, при наведении мышью на метод будет высвечиваться наша добавленная документация. При этом специальные знаки помогают удобно форматировать описание. Например, квадратные скобки позволяют обозначить параметр, а дефисы — организовать маркированный список.
▫️Комментарий — это обычное словесное описание алгоритма действий. Если в коде встречается сложный участок, где логика не совсем очевидна, или имеются какие-то важные сведения, которые разработчик хочет передать своим коллегам и себе в будущее, оформлять их стоит именно как комментарий. Они выделяются с помощью
//
. Как показывает практика, и документация, и комметрирование кода — очень удобные инструменты, которые позволяют сделать проект максимально понятным и простым для вхождения новых разработчиков.
Так почему же тема холиварная?
В первую очередь потому что зачастую и документация, и комментарии в проекте не систематизированы, оформляются хаотично, чем только мешают и запутывают. Разработчики из-за этих проблем не видят в них никакой пользы, только лишь дополнительную трату времени.
Как это исправить?
Поможет соответствие ряду правил.
1. Зафиксировать в проекте порядок комментирования и документации кода.
Очень удобно на уровне всей команды заранее определить шаблоны. В будущем это позволит держать весь проект в едином формате.
2. Думать о том, как этот участок кода может восприниматься другими разработчиками или тобой через большой промежуток времени.
В больших проектах, которые разрабатываются и поддерживаются долгое время, не стоит полагаться на свою память или память коллег. Нюансы имеют свойство забываться, разработчики могут менять проекты и место работы, поэтому полезно фиксировать сложные места сразу.
3. Не комментировать лишнее.
Такая ошибка часто встречается, что вызывает негатив к комментированию в целом. Важно покрывать комментариями только то, что действительно нуждается в пояснении. Например, переменные с говорящими названиями или простые условия в блоке if описывать смысла нет.
4. Больше текста — не значит лучше.
Документация и комментарии должны быть емкими, простыми для восприятия. Если текста будет слишком много, вероятность, что его будут пропускать, увеличивается.
💬Делитесь своим опытом и лучшими практиками в комментариях.