Media is too big
VIEW IN TELEGRAM
Как и договаривались — ловите запись вчерашнего митапа с Сережей, Friflex Flutter Team Lead ⬆️
Если кратко:
🔸 Темизация должна быть системной — не только цвета, но и шрифты, отступы, состояния, анимации и взаимодействие между компонентами
🔸 Общение с дизайнерами критично — обсуждайте реализуемость решений заранее, чтобы избежать затрат и конфликтов
🔸 Разработчик — не просто исполнитель — можно и нужно предлагать улучшения, инициировать создание UI-кита, структурировать код
🔸 Theme Extensions — лучший компромисс — подходят большинству проектов, позволяют гибко кастомизировать без отказа от Material Design
Что еще почитать на эту тему:
📎 Flutter:
•ThemeExtensions
•MaterialApp/ Cupertino
•InheritedWidget
•Theme и ThemeData
📎 Design:
•Material Design + Human Interface Guidelines
•Стили, токены в Figma
Презентация — в комментариях⬇️
Повторим такой онлайн-формат?💜
Если кратко:
Что еще почитать на эту тему:
•ThemeExtensions
•MaterialApp/ Cupertino
•InheritedWidget
•Theme и ThemeData
•Material Design + Human Interface Guidelines
•Стили, токены в Figma
Презентация — в комментариях
Повторим такой онлайн-формат?
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14🔥9👍6
Недавно попробовала Flutter 3.32 и хочу поделиться интересными улучшениями в релизе, которые могут сэкономить время и сделать разработку чуточку приятнее. Особенно понравились изменения, связанные с вебом, доступностью и новым виджетом Expansible.
▪️Горячая перезагрузка на вебе (экспериментально)
Наконец-то можно использовать hot reload в браузере! Это сильно ускоряет цикл разработки — больше не нужно каждый раз ждать полной перезагрузки страницы.
Чтобы включить, достаточно запустить:
bash
flutter run -d chrome --web-experimental-hot-reload
В VS Code можно настроить в launch.json, просто добавив аргумент --web-experimental-hot-reload. Работает так же, как и в мобильных проектах — r в терминале для быстрой перезагрузки, R для полного рестарта. А в DartPad теперь даже появилась кнопка «Reload».
▪️Улучшения доступности
Семантическое дерево теперь строится примерно на 80% быстрее — полезно для экранных дикторов и других assistive-технологий.
Появился новый API SemanticsRole, позволяющий явно указывать роли элементов (button, header, textField и другие), и это работает на уровне поддерева.
▪️Инструменты разработчика
В DevTools появился Flutter Property Editor. С ним можно редактировать свойства виджетов прямо в интерфейсе и сразу видеть изменения.
Обновили интерфейс DevTools, улучшили отслеживание памяти и работу с CPU-профайлером. Все стало быстрее и нагляднее.
▪️Новые виджеты и возможности темизации
Появился новый виджет Expansible и контроллер к нему. Он удобнее, чем старый ExpansionTile, и легко кастомизируется.
Поддержка Material 3 расширилась:
▫️Новый эффект InkSparkle
▫️Улучшенные SegmentedButton и Divider с поддержкой радиусов
▫️Новые колбэки для TabBar на hover и focus
▪️Поддержка десктопа и multi-window
— Canonical помогает улучшать поведение окон: события мыши, фокус, текстовые поля теперь работают стабильнее
— На Linux отрисовка вынесена в отдельный поток
— На Windows и macOS вызовы к платформе через Dart-FFI — теперь быстрее благодаря объединенным потокам.
▪️Dart 3.8
Новые возможности языка, в том числе:
— Null-aware элементы в коллекциях (if, for внутри списков и map'ов)
— Улучшенный форматтер — теперь, например, запятая в конце не исчезает
— Поддержка кросс-компиляции: можно собирать бинарники под Linux с других платформ
Кто тоже уже обновился?
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍5❤2
Всем привет! С вами Анна, Friflex Flutter Team Lead💬
Сегодня поговорим об одном мощном инструменте, который обязательно вам пригодится — WidgetsBindingObserver.
WidgetsBindingObserver — это миксин, который дает возможность объекту, к которому подключается, отслеживать изменения системы и жизненного цикла приложения.
Подключить его очень просто. Для примера создадим Stateful-виджет Example. К состоянию этого виджета нужно подключить миксин, затем в
Что же дает нам такая реализация? WidgetsBindingObserver позволяет отслеживать:
✔️ Жизненный цикл приложения (активно, работает в фоне и другие параметры)
✔️ Изменения размеров приложения (например, при перевороте экрана)
✔️ Изменения темы системы
✔️ Изменения системной локализации
✔️ Возникновения нехватки памяти в системе
✔️ Изменения системного размера шрифта
📎 Еще больше возможностей WidgetsBindingObserver можно найти в документации
❤️ — если было полезно
Сегодня поговорим об одном мощном инструменте, который обязательно вам пригодится — WidgetsBindingObserver.
WidgetsBindingObserver — это миксин, который дает возможность объекту, к которому подключается, отслеживать изменения системы и жизненного цикла приложения.
Подключить его очень просто. Для примера создадим Stateful-виджет Example. К состоянию этого виджета нужно подключить миксин, затем в
initState()
необходимо обратиться к экземпляру WidgetsBinding и подключить текущий виджет как наблюдателя. При этом важно в методе dispose()
отсоединять наблюдателя. Подобная реализация state-виджета Example будет выглядеть следующим образом:
class _ExampleState extends State<Example> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
Widget build(BuildContext context) {
return const Placeholder();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
Что же дает нам такая реализация? WidgetsBindingObserver позволяет отслеживать:
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print('Жизненный цикл приложения изменился: $state');
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
final size = View.of(context).physicalSize;
print('Размер экрана изменился: ${size.width}x${size.height}');
}
@override
void didChangePlatformBrightness() {
super.didChangePlatformBrightness();
print('Изменилась тема системы');
}
@override
void didChangeLocales(List<Locale>? locales) {
super.didChangeLocales(locales);
print(
'Изменилась локализация: ${locales?.map((locale) => locale.toString()).join(', ')}');
}
@override
void didHaveMemoryPressure() {
super.didHaveMemoryPressure();
print('Обнаружена нехватка памяти!');
}
@override
void didChangeTextScaleFactor() {
super.didChangeTextScaleFactor();
print('Изменился размер шрифта системы');
}
❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
❤20🔥6❤🔥3
Привет, это Роза, Flutter Dev Friflex👋
Лучше всего мы учимся на практике, поэтому сегодня создадим окно фильтра, которое будет появляться рядом с кнопкой при нажатии.
Конечно, можно использовать готовые решения — например, PopupMenuButton с кастомным PopupMenuEntry или диалог. Но если вам нужно больше контроля над позиционированием и поведением окна, лучше реализовать его вручную.
Для этого используем три компонента:
1️⃣ Overlay — для отрисовки поверх текущего интерфейса
2️⃣ CompositedTransformTarget — якорь для привязки
3️⃣ CompositedTransformFollower — виджет, который будет следовать за якорем
Немного теории
Overlay — специальный стек, поверх которого можно отрисовывать виджеты вне основного дерева. Элементы добавляются в Overlay через OverlayEntry.
Чтобы связать два виджета в пространстве, используем:
✔️ CompositedTransformTarget — задает якорь
✔️ CompositedTransformFollower — следует за якорем, даже если его позиция меняется
Где:
🔵 link — объект LayerLink, соединяющий Target и Follower
🔵 showWhenUnlinked — показывает Follower, если связь потеряна
🔵 offset — смещение относительно якоря
🔵 targetAnchor и followerAnchor — точки привязки
Реализация
Для начала создадим StatefulWidget для управления окном. В State определим:
➡️ LayerLink — для связи кнопки и окна
➡️ OverlayEntry — элемент, который добавим в Overlay
➡️ OverlayState — текущий Overlay из контекста
В методе build оборачиваем кнопку в CompositedTransformTarget:
Добавим логику открытия/закрытия:
Создаем само окно через CompositedTransformFollower:
Не забываем удалить OverlayEntry при закрытии:
Готово!
Теперь у нас есть кастомное окно с фильтрами, которое:
✔️ Открывается по нажатию на кнопку
✔️ Позиционируется рядом с ней
✔️ Закрывается при клике вне области
❤️ — если было полезно
Лучше всего мы учимся на практике, поэтому сегодня создадим окно фильтра, которое будет появляться рядом с кнопкой при нажатии.
Конечно, можно использовать готовые решения — например, PopupMenuButton с кастомным PopupMenuEntry или диалог. Но если вам нужно больше контроля над позиционированием и поведением окна, лучше реализовать его вручную.
Для этого используем три компонента:
Немного теории
Overlay — специальный стек, поверх которого можно отрисовывать виджеты вне основного дерева. Элементы добавляются в Overlay через OverlayEntry.
Чтобы связать два виджета в пространстве, используем:
CompositedTransformTarget(
link: _layerLink,
child: ..., // ваш виджет
)
CompositedTransformFollower(
link: _layerLink,
offset: Offset.zero,
targetAnchor: Alignment.bottomLeft,
followerAnchor: Alignment.topLeft,
child: ..., // всплывающее окно
)
Где:
Реализация
Для начала создадим StatefulWidget для управления окном. В State определим:
final LayerLink _layerLink = LayerLink();
OverlayEntry? _overlayEntry;
OverlayState? _overlayState;
В методе build оборачиваем кнопку в CompositedTransformTarget:
@override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _layerLink,
child: LocalizationFilledIconButton(
icon: widget.icon ?? const FilterIcon(),
onPressed: _toggleDialog,
),
);
}
Добавим логику открытия/закрытия:
void _toggleDialog() {
// Если оверлей ещё не создан – создаем его
if (_overlayEntry == null) {
_overlayEntry = _createOverlayEntry(); // Создаём OverlayEntry
_overlayState = Overlay.of(context); // Получаем текущее состояние Overlay
_overlayState?.insert(_overlayEntry!); // Вставляем OverlayEntry в Overlay
} else {
// Если оверлей уже открыт – удаляем его
_overlayEntry?.remove(); // Удаляем OverlayEntry из Overlay
_overlayEntry = null; // Обнуляем ссылку на OverlayEntry
}
}
Создаем само окно через CompositedTransformFollower:
OverlayEntry _createOverlayEntry() {
return OverlayEntry(
builder: (context) {
return GestureDetector(
behavior: HitTestBehavior.translucent, // Позволяет "ловить" тап вне окна
onTap: _toggleDialog, // Закрываем окно при тапе вне его области
child: Stack(
children: [
Positioned(
width: MediaQuery.sizeOf(context).width / 3, // Ширина окна – треть экрана
child: CompositedTransformFollower(
link: _layerLink, // Связь с CompositedTransformTarget
showWhenUnlinked: false, // Скрываем окно, если связь потеряна
targetAnchor: Alignment.bottomLeft, // Привязываемся к нижнему левому углу кнопки
child: FilterDialog(toggleDialog: _toggleDialog), // Само окно фильтра
),
),
],
),
);
},
);
}
Не забываем удалить OverlayEntry при закрытии:
@override
void dispose() {
_overlayEntry?.remove();
super.dispose();
}
Готово!
Теперь у нас есть кастомное окно с фильтрами, которое:
❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍7🔥4
Привет, это снова Катя, Flutter Dev Friflex!💫
Иногда одной только документации (README) на pub.dev недостаточно — особенно, когда библиотека ведет себя странно или хочется понять, как она работает «под капотом». В такие моменты приходится читать исходный код библиотеки. Давайте разберем, куда смотреть и на что можно не тратить время, на примере популярной библиотеки intl_utils.
Шаг 1. Открываем репозиторий
1️⃣Идем на pub.dev
2️⃣В поиске вводим нужную библиотеку, например, intl_utils
3️⃣В карточке справа жмем Repository (GitHub) — нас перебросит на GitHub-репозиторий проекта
Шаг 2. Что стоит смотреть
▪️Папка bin/
Часто используется для CLI-скриптов. В intl_utils, например, есть исполняемый файл генератора локализаций. Он как раз запускается при выполнении команды dart run intl_utils:generate
▪️Папка lib/
Это сердце библиотеки. Тут обычно:
✔️логика импорта (intl_utils.dart)
✔️основной код библиотеки
✔️вспомогательные утилиты
▪️Файл генератора
Если есть генерация кода, как у intl_utils, стоит посмотреть, как он парсит pubspec.yaml, обрабатывает ключи и какие шаблоны использует. В intl_utils, например:
✔️generator.dart отвечает за запуск логики
✔️pubspec_config.dart — за чтение конфигурации
✔️templates/ — за шаблоны, по которым создаются dart-файлы с переводам
Иногда полезно заглянуть в build.yaml — он описывает, как работает генерация с build_runner.
Шаг 3. Что можно пропустить
❌ Тесты
Если вы просто хотите понять, как работает логика, тесты можно пропустить. Но если вы хотите проверить, что библиотека работает корректно, заглянуть все-таки стоит.
❌ Конфигурационные файлы
gitignore, analysis_options.yaml, metadata, vscode/ и прочие технические файлы не помогают в понимании логики работы.
Советы
✅ Начинайте с точки входа — файла, указанного в pubspec.yaml → executables: или lib/
✅ Ищите ключевые слова: generate, parse, template, config. Они помогут быстрее найти нужную часть
✅ Если запутались — зайдите в example/, если он есть. Там обычно видно, как библиотеку используют в реальном коде.
Часто ли вы читаете сторонние библиотеки?
Иногда одной только документации (README) на pub.dev недостаточно — особенно, когда библиотека ведет себя странно или хочется понять, как она работает «под капотом». В такие моменты приходится читать исходный код библиотеки. Давайте разберем, куда смотреть и на что можно не тратить время, на примере популярной библиотеки intl_utils.
Шаг 1. Открываем репозиторий
1️⃣Идем на pub.dev
2️⃣В поиске вводим нужную библиотеку, например, intl_utils
3️⃣В карточке справа жмем Repository (GitHub) — нас перебросит на GitHub-репозиторий проекта
Шаг 2. Что стоит смотреть
▪️Папка bin/
Часто используется для CLI-скриптов. В intl_utils, например, есть исполняемый файл генератора локализаций. Он как раз запускается при выполнении команды dart run intl_utils:generate
▪️Папка lib/
Это сердце библиотеки. Тут обычно:
✔️логика импорта (intl_utils.dart)
✔️основной код библиотеки
✔️вспомогательные утилиты
▪️Файл генератора
Если есть генерация кода, как у intl_utils, стоит посмотреть, как он парсит pubspec.yaml, обрабатывает ключи и какие шаблоны использует. В intl_utils, например:
✔️generator.dart отвечает за запуск логики
✔️pubspec_config.dart — за чтение конфигурации
✔️templates/ — за шаблоны, по которым создаются dart-файлы с переводам
Иногда полезно заглянуть в build.yaml — он описывает, как работает генерация с build_runner.
Шаг 3. Что можно пропустить
❌ Тесты
Если вы просто хотите понять, как работает логика, тесты можно пропустить. Но если вы хотите проверить, что библиотека работает корректно, заглянуть все-таки стоит.
❌ Конфигурационные файлы
gitignore, analysis_options.yaml, metadata, vscode/ и прочие технические файлы не помогают в понимании логики работы.
Советы
✅ Начинайте с точки входа — файла, указанного в pubspec.yaml → executables: или lib/
✅ Ищите ключевые слова: generate, parse, template, config. Они помогут быстрее найти нужную часть
✅ Если запутались — зайдите в example/, если он есть. Там обычно видно, как библиотеку используют в реальном коде.
Часто ли вы читаете сторонние библиотеки?
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥6👍3
Однажды Роза в своем посте уже делилась лайфхаками, как сделать скролящиеся списки красивыми, плавными и высокопроизводительными. Там же упоминались три основных виджета для реализации списков с прокруткой — SingleChildScrollView, ListView и CustomScrollView. Сегодня чуть глубже погрузимся в специфику работы каждого из них.
✔️SingleChildScrollView — самый простой виджет для прокрутки. Принимает в качестве «ребенка» только один виджет, поэтому при необходимости вложить несколько объектов необходимо обернуть их в Column.
SingleChildScrollView(
child: Column(
children: [
Child1()
Child2()
Child3()
],
),
)
Плюсы:
▪️максимально прост в использовании
▪️ отлично подходит для отрисовки статичных объектов
Минусы:
➖ отрисовывает одномоментно абсолютно все содержимое списка, за счет чего страдает производительность
➖ использует много памяти
✔️ListView — наиболее часто используемый виджет для скроллящихся списков. Имеет несколько конструкторов builder, separated и custom, каждый из которых в разных ситуациях может помочь максимально сократить количество кода.
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Child();
},
)
ListView(
children: [
Child1(),
Child2(),
],
)
Плюсы:
▪️ поддерживает высокую производительность
▪️ работает по принципу ленивой загрузки (вложенные виджеты билдятся не одновременно, а по мере прокрутки)
Минусы:
➖ позволяет вложить простой контент
➖ не дает сильно кастомизировать прокрутку
✔️CustomScrollView — идеальное решение для сложных интерфейсов. Работает не на стандартных виджетах, а на сливерах, что без проблем позволяет вкладывать разнообразный контент.
CustomScrollView(
slivers: [
SliverAppBar(
title: Child1(),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Child2(),
childCount: 20,
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
delegate: SliverChildBuilderDelegate(
(context, index) => Child3(),
childCount: 10,
),
),
],
)
Плюсы:
▪️ обеспечивает высокую производительность даже при сложном и разнообразном наполнении
▪️ поддерживает наибольшую возможность кастомизации
▪️ дает возможность добавлять дополнительные интересные эффекты в прокрутку
▪️ поддерживает ленивую загрузку контента
Минусы:
➖ имеет нестандартную для других виджетов скроллящихся списков механику, что требует от разработчика дополнительного изучения
➖ работает на сливерах, что так же может вызывать трудности у начинающих специалистов
Каждый из вариантов имеет место быть. Например, для простого короткого списка статичных контейнеров нет смысла создавать CustomScrollView, так как с этой целью прекрасно справится SingleChildScrollView. А при необходимости добавить на экран несколько сеток и вложенных списков со сложной анимацией оптимальным будет именно CustomScrollView. Здесь важно понимать различия и с умом подходить к выбору инструмента.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8❤6💯3👍2
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, это Роза, Flutter Dev Friflex!🚀
Сегодня немного о том, как улучшить UI-опыт в ваших Flutter web-приложениях.
Порой даже в самом продуманном интерфейсе нужны подсказки.
Допустим, у вас есть кнопка: иконка вроде есть, но… что она делает? Чтобы закрыть такие «дыры» в UX во Flutter есть такой виджет как Tooltip. Вроде мелочь, но при помощи одной лишь строчки вы заметно улучшите опыт пользователя.
Tooltip — это всплывающая подсказка, которая появляется:
✔️ при долгом тапе на мобильных устройствах
✔️при наведении курсора в вебе и на десктопе
Например, это может быть полезно:
▫️ для иконок без текста
▫️ для кнопок с непонятным назначением
Tooltip реализован через Overlay, чтобы подсказка могла отображаться поверх всех виджетов. Он отслеживает действие и вставляет в Overlay анимированный контейнер с текстом.
Положение, задержка и продолжительность отображения настраиваются через waitDuration, showDuration и другие параметры.
Базовое использование:
Но на этом не все, вы также можете его кастомизировать: задать фон, стили, задержки и даже использовать rich-контент.
Некоторые виджеты уже встроенно поддерживают тултипы. Например, IconButton.
❕При использовании tooltip не стоит перебарщивать. Добавляйте подсказки только в те места, где это действительно необходимо! Не надо дублировать то, что и так ясно.
Если вам хочется больше контроля, то можете сделать собственный тултип через OverlayEntry. Об это был пост.
Если было полезно — ❤️
Сегодня немного о том, как улучшить UI-опыт в ваших Flutter web-приложениях.
Порой даже в самом продуманном интерфейсе нужны подсказки.
Допустим, у вас есть кнопка: иконка вроде есть, но… что она делает? Чтобы закрыть такие «дыры» в UX во Flutter есть такой виджет как Tooltip. Вроде мелочь, но при помощи одной лишь строчки вы заметно улучшите опыт пользователя.
Tooltip — это всплывающая подсказка, которая появляется:
✔️ при долгом тапе на мобильных устройствах
✔️при наведении курсора в вебе и на десктопе
Например, это может быть полезно:
▫️ для иконок без текста
▫️ для кнопок с непонятным назначением
Tooltip реализован через Overlay, чтобы подсказка могла отображаться поверх всех виджетов. Он отслеживает действие и вставляет в Overlay анимированный контейнер с текстом.
Положение, задержка и продолжительность отображения настраиваются через waitDuration, showDuration и другие параметры.
Базовое использование:
Tooltip(
message: 'Добавить',
child: Icon(Icons.add),
)
Но на этом не все, вы также можете его кастомизировать: задать фон, стили, задержки и даже использовать rich-контент.
Tooltip(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
),
richMessage: WidgetSpan(
alignment: PlaceholderAlignment.baseline,
baseline: TextBaseline.alphabetic,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxWidth),
child: Text(
message,
style: Theme.of(context).textTheme.bodySmall,
),
),
),
),
child: const InfoIcon(isOutline: true, size: 20),
);
Некоторые виджеты уже встроенно поддерживают тултипы. Например, IconButton.
IconButton(
icon: Icon(Icons.search),
tooltip: 'Поиск',
onPressed: () {},
)
❕При использовании tooltip не стоит перебарщивать. Добавляйте подсказки только в те места, где это действительно необходимо! Не надо дублировать то, что и так ясно.
Если вам хочется больше контроля, то можете сделать собственный тултип через OverlayEntry. Об это был пост.
Если было полезно — ❤️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤7🔥4
Сегодня я расскажу о том, как Git помогает в повседневной работе с Flutter-проектами, какие встроенные инструменты есть в Android Studio и VS Code для работы с ним и как они делают жизнь разработчика чуть проще.
Зачем разработчикам нужен Git?
Все проекты — это, как правило, разные приложения, где часто работают несколько разработчиков, дизайнеров и тестировщиков. Git помогает:
✔️Хранить историю изменений проекта
✔️Безопасно тестировать фичи в отдельных ветках
✔️Устранять баги, не трогая основную логику
✔️Организовать code review- и pull request-процессы
Даже если вы работаете одни — все равно используйте Git. Это как страховка: можно всегда откатиться к стабильной версии, сравнить изменения или понять, когда что-то «сломалось».
Прописываете типовой флоу Git в Flutter-проекте:
git checkout -b feature/new-cool-widget
# Работаешь, коммитишь
git add .
git commit -m "Добавил новый виджет для экрана авторизации"
git push origin feature/new-cool-widget
Затем открываете pull request в GitHub или GitLab, проходите ревью, мержите — и вуаля!
Git + Android Studio
Android Studio (или IntelliJ) предоставляет шикарный визуальный интерфейс для работы с Git:
Встроенные фичи:
▫️Commit panel: показывает изменения, можно выбрать, что включить в коммит
▫️Version control tab: история изменений, лог коммитов, сравнение файлов
▫️Interactive rebase, cherry-pick и squash — всё доступно из GUI
▫️Branch manager: быстрое переключение, создание и удаление веток
Лайфхак: ⌘ + K (macOS) или Ctrl + K (Windows/Linux) — быстрое окно коммита
Git + VS Code
VS Code тоже хорош для Git, особенно если вы фанат клавиатуры и расширений.
Основные фичи:
▫️Панель Source Control (обычно слева)
▫️Быстрое создание веток, коммитов, пушей и пулов
▫️Интеграция с GitHub: можно прямо в редакторе открывать pull requests
Полезные расширения:
▫️GitLens — суперподробная история изменений
▫️Git Graph — визуализация веток
▫️GitHub Pull Requests — ревью прямо из редактора
Лайфхак: Cmd + Shift + P → Git: Commit — и вуаля, можно коммитить без мышки
Git ignore в Flutter
gitignore
# Flutter
.dart_tool/
.packages
.pub/
build/
ios/Pods/
android/.gradle/
Обязательно добавьте .gitignore в корень проекта, чтобы не заливать временные файлы, кэш и сборочные артефакты.
Советы из практики
▪️Названия веток лучше делать по шаблону: feature/, bugfix/, hotfix/, refactor/
▪️Используйте rebase осторожно, а лучше не используйте, если знаете, о чем я
▪️Часто пушьте — особенно в командной работе
▪️Никогда не коммитьте pubspec.lock в библиотеку, но обязательно — в приложение
Используйте встроенные возможности среды разработки, а не пишите все вручную
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤5👍4🤔1
Anonymous Poll
66%
После каждой маленькой задачи
16%
Раз в день
2%
Когда начальник напоминает
16%
Коммиты? А, ну да, надо бы...
❤5
This media is not supported in your browser
VIEW IN TELEGRAM
Привет! Это Анна, Friflex Flutter Team Lead👋
Наверняка, самым частоиспользуемым Flutter-разработчиками методом можно назвать метод
1. Использовать константные конструкторы везде, где можно.
При перестроении родительского виджета все дочерние виджеты, созданные как константные, перестраиваться не будут. С помощью const вы даете программе явно понять, что именно этот объект изменяться не должен.
2. Кэшировать обращения к MediaQuery.of(context) и Theme.of(context)
Каждое обращение у этих классов к методам
Кроме этого, если от MediaQuery вам необходим, например, только размер экрана, лучше вместо стандартного
3. Вызывать перестроение только тех участков, которые действительно должны измениться.
Здесь для примера рассмотрим Stateful-виджет NotOptimizedExample, State которого выглядит таким образом:
В этом случае при тапе на кнопку нам нужно обновить только CounterView. Но подобной реализацией мы заставляем перерисовать все содержимое экрана.
Оптимальное решение здесь — ребилдить только CounterView.
4. Оптимизировать скроллящиеся объекты на экране.
Основная идея — осознанно подбирать виджеты прокручиваемых списков, опираясь на размеры списка, сложность вложенных объектов и потребности в механике прокрутки. Больше деталей вы можете узнать из поста Розы об оптимизации списков и моего поста о сравнении трех подходов создания прокручиваемых списков.
Пятый способ — в комментариях👇
Наверняка, самым частоиспользуемым Flutter-разработчиками методом можно назвать метод
build()
в Stateful- и Stateless-виджетах. Сегодня поделюсь с вами своим топ-5 лучших практик для его оптимизации.1. Использовать константные конструкторы везде, где можно.
При перестроении родительского виджета все дочерние виджеты, созданные как константные, перестраиваться не будут. С помощью const вы даете программе явно понять, что именно этот объект изменяться не должен.
class Example extends StatelessWidget {
const Example({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
const Text('Example text'), // оптимально
Text('Example text not const'), // можно оптимизировать, добавив const
],
);
}
}
2. Кэшировать обращения к MediaQuery.of(context) и Theme.of(context)
Каждое обращение у этих классов к методам
of(context)
под капотом подразумевает вызов dependOnInheritedWidgetOfExactType()
. Этот метод производит поиск необходимых данных по всему дереву виджетов. Такая операция занимает некоторое время. Если таких операций было запущено несколько, то время увеличивается.Кроме этого, если от MediaQuery вам необходим, например, только размер экрана, лучше вместо стандартного
of(context)
использовать sizeOf(context)
.
/// Пример неоптимизированного использования
class NotOptimizedExample extends StatelessWidget {
const NotOptimizedExample({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.3,
margin: EdgeInsets.all(MediaQuery.of(context).size.width * 0.05),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
border: Border.all(color: Theme.of(context).dividerColor),
),
child: const Text('Hello'),
);
}
}
/// Пример оптимизированного использования
class OptimizedExample extends StatelessWidget {
const OptimizedExample({super.key});
@override
Widget build(BuildContext context) {
final sizeData = MediaQuery.sizeOf(context);
final themeData = Theme.of(context);
return Container(
width: sizeData.width * 0.8,
height: sizeData.height * 0.3,
margin: EdgeInsets.all(sizeData.width * 0.05),
decoration: BoxDecoration(
color: themeData.primaryColor,
border: Border.all(color: themeData.dividerColor),
),
child: const Text('Hello'),
);
}
}
3. Вызывать перестроение только тех участков, которые действительно должны измениться.
Здесь для примера рассмотрим Stateful-виджет NotOptimizedExample, State которого выглядит таким образом:
class _NotOptimizedExampleState extends State<NotOptimizedExample> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
PageHeader(), // Пересоздается
CounterView(),
CounterButton(
onTap: () => setState(() {
counter++;
}),
),
PageFooter(), // Пересоздается
],
);
}
}
В этом случае при тапе на кнопку нам нужно обновить только CounterView. Но подобной реализацией мы заставляем перерисовать все содержимое экрана.
Оптимальное решение здесь — ребилдить только CounterView.
4. Оптимизировать скроллящиеся объекты на экране.
Основная идея — осознанно подбирать виджеты прокручиваемых списков, опираясь на размеры списка, сложность вложенных объектов и потребности в механике прокрутки. Больше деталей вы можете узнать из поста Розы об оптимизации списков и моего поста о сравнении трех подходов создания прокручиваемых списков.
Пятый способ — в комментариях👇
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12🔥6❤🔥3
Привет, это Роза, Flutter Dev Friflex⭐️
Если вы пишете тесты во Flutter, то наверняка сталкивались с тем, как сложно тестировать асинхронные операции. Часто такие операции приходится мокать или обходить с помощью Future.delayed. Но что делать, если в коде используются не только отложенные Future, но и настоящие таймеры, задержки и другие механизмы, зависящие от времени?
Сегодня поговорим о пакете fake_async, который может помочь с этим делом.
Пакет fake_async создан для тестирования кода, зависящего от времени. Flutter-тесты могут запускаться в специальной зоне FakeAsync, где вы управляете временем вручную. То есть fake_async перехватывает все стандартные Dart-таймеры и заменяет их на свои «поддельные». Благодаря этому вы можете контролировать время в тестах, не дожидаясь реальных секунд или минут.
Например, у вас есть функция, которая загружает данные с задержкой:
Без fake_async тестирование этой функции потребовало бы реальной 2-секундной задержки.
Попробуем заменить на fake_async.
fakeAsync() создает виртуальное время, которым можно управлять вручную через elapse(). Это позволяет имитировать прохождение времени и тут же проверять результат асинхронных операций — без реальных пауз.
Также у fake_async есть другие полезные методы:
▪️ flushTimers() — для мгновенного выполнения всех запланированных таймеров
▪️ advanceTime(Duration duration) — похож на elapse, но иногда дает чуть более тонкий контроль
Хотя fake_async очень полезен, у него есть свои особенности, которые важно учитывать:
✅ каждый вызов fakeAsync(...) создает свою виртуальную временную зону, полностью изолированную от других
✅ он не управляет async/await напрямую, только таймерами и микрозадачами
✅ может конфликтовать с
✅
✅ потоки типа
✅ в
❤️ — если было полезно
Если вы пишете тесты во Flutter, то наверняка сталкивались с тем, как сложно тестировать асинхронные операции. Часто такие операции приходится мокать или обходить с помощью Future.delayed. Но что делать, если в коде используются не только отложенные Future, но и настоящие таймеры, задержки и другие механизмы, зависящие от времени?
Сегодня поговорим о пакете fake_async, который может помочь с этим делом.
Пакет fake_async создан для тестирования кода, зависящего от времени. Flutter-тесты могут запускаться в специальной зоне FakeAsync, где вы управляете временем вручную. То есть fake_async перехватывает все стандартные Dart-таймеры и заменяет их на свои «поддельные». Благодаря этому вы можете контролировать время в тестах, не дожидаясь реальных секунд или минут.
Например, у вас есть функция, которая загружает данные с задержкой:
Future<String> fetchData() {
return Future.delayed(Duration(seconds: 2), () => 'Data loaded');
}
Без fake_async тестирование этой функции потребовало бы реальной 2-секундной задержки.
Попробуем заменить на fake_async.
import 'package:fake_async/fake_async.dart';
import 'package:test/test.dart';
void main() {
test('fetchData должен вернуть "Data loaded" через 2 секунды', () {
fakeAsync((async) { // 1. Оборачиваем тест в fakeAsync()
final future = fetchData();
async.elapse(Duration(seconds: 2)); // 2. Перемещаем виртуальное время на 2 секунды
expect(future, completion('Data loaded')); // 3. Немедленно проверяем результат
});
});
}
fakeAsync() создает виртуальное время, которым можно управлять вручную через elapse(). Это позволяет имитировать прохождение времени и тут же проверять результат асинхронных операций — без реальных пауз.
Также у fake_async есть другие полезные методы:
▪️ flushTimers() — для мгновенного выполнения всех запланированных таймеров
▪️ advanceTime(Duration duration) — похож на elapse, но иногда дает чуть более тонкий контроль
Хотя fake_async очень полезен, у него есть свои особенности, которые важно учитывать:
✅ каждый вызов fakeAsync(...) создает свою виртуальную временную зону, полностью изолированную от других
✅ он не управляет async/await напрямую, только таймерами и микрозадачами
✅ может конфликтовать с
tester.runAsync
в flutter_test
✅
elapse()
не влияет на scheduleMicrotask
— только на макрозадачи✅ потоки типа
Stream.periodic
могут вести себя нестабильно✅ в
flutter_test
возможны конфликты с фреймами и анимациями❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
❤13👍3🔥3
Сегодня я расскажу, как улучшить доступность Flutter-приложений с помощью семантических ролей. Особенно полезно тем, кто разрабатывает под веб или стремится сделать интерфейс дружелюбным для пользователей с ограниченными возможностями.
Семантические роли помогают экранным дикторам и другим вспомогательным технологиям правильно интерпретировать элементы интерфейса. Они сообщают, что перед нами: кнопка, ссылка, заголовок, слайдер, элемент списка или таблицы.
📌 Подробнее о семантике во Flutter можно прочитать в официальной документации
🧠 Что такое Semantic Role?
Это описание назначения виджета, помогающее вспомогательным технологиям понять, как этот элемент должен «звучать» для пользователя. Flutter сам добавляет нужные роли в стандартные виджеты (например,
TabBar
, MenuAnchor
, Table
). Но если вы используете кастомный компонент — семантика может быть утеряна.Например, если вы делаете список с кастомной реализацией, не забудьте явно указать
SemanticsRole.list
и SemanticsRole.listItem
:
Semantics(
role: SemanticsRole.list,
explicitChildNodes: true,
child: Column(
children: [
Semantics(
role: SemanticsRole.listItem,
child: Text('Первый элемент списка'),
),
Semantics(
role: SemanticsRole.listItem,
child: Text('Второй элемент списка'),
),
],
),
)
🧪 Как тестировать доступность?
Flutter предлагает встроенные Guideline API.
Они проверяют:
✔️Размеры кликабельных областей (
48x48
на Android, 44x44
на iOS)✔️Контраст текста
✔️Наличие меток на интерактивных элементах
Такие тесты можно запускать параллельно с другими widget
-тестами, например, в test/a11y_test.dart
.🌐 Доступность на Web
Для отладки доступности веб-приложений можно включить визуализацию семантических узлов:
flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true
Вы увидите, как Flutter отрисовывает семантические элементы поверх виджетов. Это позволяет проверить корректность разметки.✅ Чеклист перед релизом
Перед выпуском убедитесь, что:
▫️Все интерактивные элементы работают и дают понятный фидбэк
▫️Экранный диктор читает все элементы (проверьте с TalkBack и VoiceOver)
▫️Контраст текста достаточный (не менее 4.5:1)
▫️Ничто не меняет контекст пользователя внезапно (например, не переходит на новый экран во время ввода)
▫️Размер кликабельных элементов — минимум
48x48
▫️Ошибки отображаются понятно и даются способы их исправить
▫️Интерфейс остается читаемым при масштабировании и в режимах для дальтоников
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14❤7🤩3
Одна из важнейших задач от бизнеса при разработке мобильного приложения — интеграция сервиса аналитики и сбор аналитических данных. Когда приложение открыто для реальных пользователей, аналитика может быть очень полезной. Она позволяет:
✔️ узнать реальное поведение пользователей, какие действия они предпринимают и что именно им интересно
✔️ определить «узкие места» — где пользователи теряются, какие функции вообще не используются
✔️ принять определенные бизнес-решения
✔️ оценить эффективность обновлений и внедрения нового функционала
✔️ обнаружить ошибки и падения приложения
Все эти пункты возможны только при грамотном сборе аналитических данных. А как же это сделать во Flutter-приложении? Что позволит собирать данные максимально эффективно?
1. Интегрировать подходящий сервис сбора аналитики
Как бы банально не звучал этот пункт, он самый важный. От того, какой сервис вы выберете, зависит то, насколько удобно можно будет анализировать данные.
Наиболее распространенные сервисы — AppMetrica и Firebase Analytics. Оба сервиса имеют адаптированные для Flutter плагины — appmetrica_plugin и firebase_analytics. Они обладают широким функционалом, который позволяет удобно структурировать собранные данные и формировать отчеты.
2. Создать централизованный класс сервиса
При создании класса-обертки для сервиса аналитики вы таким образом оберегаете себя от многочисленных изменений по проекту.
class AnalyticsService {
Future<void> init(String apiKey) async {
await AppMetrica.activate(AppMetricaConfig(apiKey));
}
Future<void> reportEvent(String event) async {
await AppMetrica.reportEvent(event);
}
}
Представьте, сегодня вы интегрировали один сервис аналитики, а через полгода бизнес принимает решение переехать на другой сервис. В таком случае, если у вас нет основного класса сбора аналитики, вам придется заново во всем проекте вносить изменения по всем точкам отправки данных. А в случае, если у вас есть класс-обертка, как в примере, изменения затронут лишь сам AnalyticsService.
3. Добавлять информативные события
Технически вы можете отправить события с любыми названиями, например,
event_1
, event_2
. Но важно помнить, что основная цель сбора аналитики — это ее дальнейшая расшифровка и исследование. Поэтому гораздо информативнее и полезнее окажутся события с говорящим названием, например, exit_profile_button_tap
. 4. Отправлять вложенные параметры
Представим, в вашем приложении несколько экранов, на которых есть кнопка «Добавить в корзину». При тапе на эту кнопку вы отправляете событие
add_product_to_cart
. Но в таком случае тут непонятно, что именно добавляется, с какого экрана. Для бизнеса значительно полезнее будет то же событие, но с дополнительными параметрами:
'add_product_to_cart', {
'screen': 'main',
'product_id': '123'
}
5. Вести документацию всех событий
С масштабированием приложения количество событий увеличивается. Со временем назначение того или иного события может забыться, что увеличивает вероятность отправки не того объекта в сервис аналитики. Чтобы предотвратить подобную проблему, лучшее решение — документировать каждое событие, описывать его интерпретацию и контекст вызова.
6. Обязательно согласовывать события с аналитиками и заказчиком
Наверное, самый важный пункт из перечисленных. Задача разработчика — грамотно расставить метрики в коде. А их интерпретацией будут заниматься другие люди, на опыт и требования которых нам безусловно стоит опираться.
Делитесь в комментариях своими лучшими практиками сбора аналитики во Flutter-приложениях👇
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥6👍5👌1
This media is not supported in your browser
VIEW IN TELEGRAM
Всем привет, с вами Роза, Flutter Dev Friflex👋
Мы уже обсуждали, как устроена локализация во Flutter через .arb-файлы и flutter gen-l10n. Это отлично работает, пока ваш проект небольшой.
Но что делать, если команда растет, количество языков увеличивается, а переводы начинают теряться в чатах и таблицах? Невольно задумываешься: как хранить переводы и правильно организовать работу.
Есть разные способы, но сегодня поговорим про Translation Management Systems (TMS) — системах управления переводами.
TMS — это не просто таблица с переводами, а полноценный центр управления локализацией, который объединяет все процессы: от извлечения строк из кода до финальной публикации в приложении.
Что же умеет TMS?
◾️ Централизует все переводы в одном месте
Можете забыть про разрозненные ARB-файлы или таблицы. Все ваши строки для всех языков хранятся в единой базе данных, доступной для всех участников команды.
◾️ Автоматизирует рутину
Вместо ручного экспорта/импорта, TMS автоматически подтягивает новые строки из кода, отправляет их на перевод, а затем генерирует готовые к использованию файлы для вашего приложения.
◾️ Разграничивает роли у пользователей
Каждый участник процесса видит только те задачи и языки, к которым у него есть доступ. Это помогает поддерживать порядок и безопасность.
◾️ Over-the-Air (OTA)
Пожалуй, одна из самых полезных фич! TMS позволяет обновлять тексты в приложении без выпуска новой версии.
◾️ Использует умные инструменты для переводчиков
Глоссарии, Translation Memory, подсказки по контексту — все это ускоряет работу и повышает качество переводов.
Когда стоит задуматься о TMS?
▫️ Команда растет, и вам нужен порядок в работе с переводами
▫️Проект масштабируется на 2-3 и более языков
▫️ Вы хотите видеть прогресс переводов по каждому языку и модулю
▫️ Устали от ручной работы по синхронизации файлов
▫️ Вам нужны функции вроде машинного перевода или памяти переводов
Если вы узнаете свой проект в одном или нескольких из этих пунктов, скорее всего, пришло время для централизованного решения.
Но TMS не всегда лучшее решение для проектов. Хотя они предлагают мощные возможности, есть и обратная сторона:
❕ Стоимость
Большинство TMS работают по подписке, и это может быть накладно для маленьких команд.
❕ Сложность внедрения
Интеграция TMS в существующий CI/CD-процесс может потребовать значительных усилий и времени.
❕ Избыточность для маленьких проектов
Если у вас всего один или два языка и ограниченное количество переводов, ручное управление через .arb-файлы и GitHub может быть более простым и экономичным решением
✔️ Наиболее популярные TMS: Lokalise, Phrase, Crowdin и Localizely.
Мы уже обсуждали, как устроена локализация во Flutter через .arb-файлы и flutter gen-l10n. Это отлично работает, пока ваш проект небольшой.
Но что делать, если команда растет, количество языков увеличивается, а переводы начинают теряться в чатах и таблицах? Невольно задумываешься: как хранить переводы и правильно организовать работу.
Есть разные способы, но сегодня поговорим про Translation Management Systems (TMS) — системах управления переводами.
TMS — это не просто таблица с переводами, а полноценный центр управления локализацией, который объединяет все процессы: от извлечения строк из кода до финальной публикации в приложении.
Что же умеет TMS?
◾️ Централизует все переводы в одном месте
Можете забыть про разрозненные ARB-файлы или таблицы. Все ваши строки для всех языков хранятся в единой базе данных, доступной для всех участников команды.
◾️ Автоматизирует рутину
Вместо ручного экспорта/импорта, TMS автоматически подтягивает новые строки из кода, отправляет их на перевод, а затем генерирует готовые к использованию файлы для вашего приложения.
◾️ Разграничивает роли у пользователей
Каждый участник процесса видит только те задачи и языки, к которым у него есть доступ. Это помогает поддерживать порядок и безопасность.
◾️ Over-the-Air (OTA)
Пожалуй, одна из самых полезных фич! TMS позволяет обновлять тексты в приложении без выпуска новой версии.
◾️ Использует умные инструменты для переводчиков
Глоссарии, Translation Memory, подсказки по контексту — все это ускоряет работу и повышает качество переводов.
Когда стоит задуматься о TMS?
▫️ Команда растет, и вам нужен порядок в работе с переводами
▫️Проект масштабируется на 2-3 и более языков
▫️ Вы хотите видеть прогресс переводов по каждому языку и модулю
▫️ Устали от ручной работы по синхронизации файлов
▫️ Вам нужны функции вроде машинного перевода или памяти переводов
Если вы узнаете свой проект в одном или нескольких из этих пунктов, скорее всего, пришло время для централизованного решения.
Но TMS не всегда лучшее решение для проектов. Хотя они предлагают мощные возможности, есть и обратная сторона:
❕ Стоимость
Большинство TMS работают по подписке, и это может быть накладно для маленьких команд.
❕ Сложность внедрения
Интеграция TMS в существующий CI/CD-процесс может потребовать значительных усилий и времени.
❕ Избыточность для маленьких проектов
Если у вас всего один или два языка и ограниченное количество переводов, ручное управление через .arb-файлы и GitHub может быть более простым и экономичным решением
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7❤3👍2💅1
Anonymous Poll
37%
Сравнение популярных TMS: Lokalise, Phrase, Crowdin и Localizely
62%
Примеры интеграции с Flutter
2%
Свой вариант в комментариях
🔥3❤1
Сегодня расскажу, как сделать кнопку загрузки во Flutter, которая умеет менять свое состояние: от обычной кнопки до загрузки с индикатором и кнопкой отмены. Такой подход пригодится, если вы хотите, чтобы пользователь видел прогресс, не нажимал на кнопку несколько раз или знал, когда загрузка завершилась.
Что мы хотим сделать?
Кнопка будет вести себя вот так:
Сначала — кнопка с текстом GET ➡️ нажимаем — она превращается в спиннер (идет подготовка загрузки) ➡️ потом появляется круговая загрузка с иконкой отмены ➡️ когда все скачалось — кнопка показывает OPEN
1. Создаем виджет DownloadButton
Это обычный StatelessWidget, который получает извне текущее состояние загрузки и отображает нужный UI.
enum DownloadStatus {
notDownloaded,
fetchingDownload,
downloading,
downloaded,
}
2. Показываем форму кнопки
Когда ничего не загружается — кнопка с закругленными краями.
Когда начинается загрузка — кнопка превращается в прозрачный круг (чтобы потом анимировать индикатор поверх него). Пример:
AnimatedContainer(
duration: transitionDuration,
decoration: ShapeDecoration(
shape: isDownloading || isFetching
? CircleBorder()
: StadiumBorder(),
color: isDownloading || isFetching
? Colors.transparent
: CupertinoColors.lightBackgroundGray,
),
child: ...
);
3. Добавляем текст
GET → когда еще не загружено
OPEN → когда уже загружено
В промежуточных состояниях текст скрыт
AnimatedOpacity(
opacity: isDownloading || isFetching ? 0.0 : 1.0,
duration: transitionDuration,
child: Text(
isDownloaded ? 'OPEN' : 'GET',
style: TextStyle(
fontWeight: FontWeight.bold,
color: CupertinoColors.activeBlue,
),
),
);
4. Добавляем индикатор загрузки
Если статус fetchingDownload — отображаем крутящийся спиннер.
Если downloading — прогресс-бар и иконка «стоп» по центру, чтобы отменить загрузку.
Stack(
alignment: Alignment.center,
children: [
CircularProgressIndicator(value: progress),
if (isDownloading)
Icon(Icons.stop, size: 14, color: CupertinoColors.activeBlue),
],
);
5. Обработка нажатий
Когда пользователь нажимает кнопку — вызывается соответствующий колбэк:
void _onPressed() {
switch (status) {
case DownloadStatus.notDownloaded:
onDownload();
break;
case DownloadStatus.downloading:
onCancel();
break;
case DownloadStatus.downloaded:
onOpen();
break;
case DownloadStatus.fetchingDownload:
break;
}
}
Подробнее можно прочитать в официальной документации.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤6👍5
This media is not supported in your browser
VIEW IN TELEGRAM
Всем привет! С вами Анна, Flutter Team Lead Friflex⭐️
Сегодня я поделюсь своим личным топ-5 практик для работы с изображениями и иконками в крупном Flutter-проекте.
1. Структурируйте файлы изображений
Когда проект масштабный, картинок и иконок может быть очень много. Чтобы было легко и просто ими управлять, хорошая практика — папку assets делить на вложенные папки по назначению изображений и группировать изображения по функционалу, где они применяются. Например, отделять фотографии от SVG-иконок, а иконки BottomNavigationBar от иконок фичи Корзина.
2. Храните изображения в оптимальном формате
Это тот формат, который позволяет минимизировать размер файла, при этом сохраняя максимальное качество. Как показывает практика, для больших картинок и фотографий оптимальным можно считать WEBP формат, а для небольших иконок — SVG.
P.S: а еще лучшебольшие изображения не вшивать в приложение, а хранить на сервере
3. Кэшируйте сетевые изображения
Здесь могу рекомендовать плагин cached_network_image. Он позволяет загружать изображение из сети всего один раз. При повторном появлении картинки на экране загрузка будет локальная, очень быстрая.
4. Добавляйте заглушки и лоадеры для загрузки сетевых изображений
В зависимости от размера изображения и скорости интернета пользователя изображения могут грузиться долго или не загрузиться совсем. Чтобы подобные сценарии не испортили внешний вид вашего интерфейса, стоит добавить лоадер на процесс загрузки и виджет-заглушку на случай падения ошибки загрузки. Так ваш ui будет всегда предсказуем и приятен для пользователя.
5. Актуализируйте assets и pubspec
Чем старше и больше становится проект, тем реже разработчики вспоминают об этом пункте. Какие-то картинки заменились на новые, какие-то случайно продублировались при создании новой фичи — за этим важно следить. Все файлы, добавленные в assets и включенные в pubspec.yaml, попадают в сборку, непосредственно увеличивая ее размер.
Возможно, некоторые пункты кажутся слишком банальными, но именно на подобных мелочах строится надежный проект, способный долгие годы поддерживаться и масштабироваться.
Делитесь своими советами по работе с картинками в проекте👇
Сегодня я поделюсь своим личным топ-5 практик для работы с изображениями и иконками в крупном Flutter-проекте.
1. Структурируйте файлы изображений
Когда проект масштабный, картинок и иконок может быть очень много. Чтобы было легко и просто ими управлять, хорошая практика — папку assets делить на вложенные папки по назначению изображений и группировать изображения по функционалу, где они применяются. Например, отделять фотографии от SVG-иконок, а иконки BottomNavigationBar от иконок фичи Корзина.
assets
|-- images
|-- feature_1
|-- feature_2
|-- icons
|-- bottom_navigation_bar
|-- features
|-- feature_1
|-- feature_2
2. Храните изображения в оптимальном формате
Это тот формат, который позволяет минимизировать размер файла, при этом сохраняя максимальное качество. Как показывает практика, для больших картинок и фотографий оптимальным можно считать WEBP формат, а для небольших иконок — SVG.
P.S: а еще лучше
3. Кэшируйте сетевые изображения
Здесь могу рекомендовать плагин cached_network_image. Он позволяет загружать изображение из сети всего один раз. При повторном появлении картинки на экране загрузка будет локальная, очень быстрая.
4. Добавляйте заглушки и лоадеры для загрузки сетевых изображений
В зависимости от размера изображения и скорости интернета пользователя изображения могут грузиться долго или не загрузиться совсем. Чтобы подобные сценарии не испортили внешний вид вашего интерфейса, стоит добавить лоадер на процесс загрузки и виджет-заглушку на случай падения ошибки загрузки. Так ваш ui будет всегда предсказуем и приятен для пользователя.
5. Актуализируйте assets и pubspec
Чем старше и больше становится проект, тем реже разработчики вспоминают об этом пункте. Какие-то картинки заменились на новые, какие-то случайно продублировались при создании новой фичи — за этим важно следить. Все файлы, добавленные в assets и включенные в pubspec.yaml, попадают в сборку, непосредственно увеличивая ее размер.
Возможно, некоторые пункты кажутся слишком банальными, но именно на подобных мелочах строится надежный проект, способный долгие годы поддерживаться и масштабироваться.
Делитесь своими советами по работе с картинками в проекте
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10👍5🔥4
В прошлом посте мы обсудили TMS-системы — что это такое и зачем они нужны в локализации.
Сегодня перейдем от теории к практике: посмотрим, как подключить TMS к Flutter-приложению на примере Localizely.
Сначала все просто: регистрируемся, создаем проект, выбираем базовый язык и языки перевода. Потом приглашаем команду и добавляем ключи — можно создать их с нуля или импортировать уже существующие.
Допустим, вы это сделали. Что дальше? Конечно, приступаем к внедрению!
Тут все зависит от того, какой генератор локализации вы используете:
▪️Если intl_utils — подключаете библиотеку, настраиваете нужные конфиги (ID проекта, API-ключ) и запускаете команду синхронизации
▪️Если другие генераторы — скачиваете файлы генерации (через интерфейс Localizely или API) и подключаете их в проект по стандартному шаблону
🚀Это все круто, но мы хотим больше. По первому плану вам нужно после каждого изменения ключей вручную синхронизировать переводы с сервисом. Но это можно автоматизировать — и еще добавить OTA (Over-the-Air), чтобы получать свежие переводы без релиза новой версии приложения.
Давайте по порядку!
✔️Автоматизация при помощи gitHub
Здесь все строится вокруг конфигурационного файла localizely.yml в репозитории. В нем указываете, какие файлы переводов импортировать (pull) из GitHub в Localizely и какие экспортировать (push) обратно. После этого в разделе Integrations настраиваете автоматическую синхронизацию и привязываете GitHub для автоматического push/pull. А чтобы было еще удобнее — добавляете веб-хук. Тогда переводы обновляются в Localizely сразу после пуша изменений в репозиторий.
✔️Так, а теперь давайте к самому интересному — OTA
С ним вы можете доставлять новые переводы прямо в приложение без релиза в сторы. Пользователь просто открывает приложение, а там уже актуальные тексты.
Устроено это примерно таким образом: вы подключаете библиотеку для работы с OTA, добавляете SDK-токен и указываете ID дистрибутива — ветку с версиями релизов приложения. Далее при запуске приложение запрашивает сервис и сохраняет в кэш самую свежую версию переводов. Можно настроить обновление не только при старте, но и в фоне — например, раз в несколько часов или дней.
Это полезно, если у вас много динамичного контента или часто правятся тексты.
⛓️💥В итоге у нас получается такая цепочка:
GitHub следит за синхронизацией файлов → Localizely получает свежие ключи и переводы → OTA мгновенно доставляет их в приложение
Все, процесс локализации живет своей жизнью, а вы тратите время только на сами переводы и улучшение приложения.
❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥2
Привет, это Катя, Flutter Dev Friflex 👋
Сегодня расскажу про распознавание лиц на Flutter. Это важный инструмент в современных мобильных приложениях: от разблокировки смартфона по лицу до автоматического заполнения данных по фото.
Используя Flutter, мы можем достаточно просто реализовать распознавание лиц, текста или звука, при этом кроссплатформенно. Сегодня я расскажу, как добавить распознавание лиц в свое приложение на Flutter с помощью Google ML Kit.
Зачем нужно распознавание лиц?
✔️Безопасность — контроль доступа по лицу
✔️Идентификация — определение пользователя среди базы данных
✔️Интерактивные приложения — фильтры, маски и другие AR-фичи
✔️Автоматизация — например, распознавание посетителей в системе учета
Что нам понадобится
Для примера будем использовать пакет:
▪️google_mlkit_face_detection — для обнаружения лиц на изображениях
▪️image_picker — для выбора фото из галереи или камеры
Добавим в pubspec.yaml:
Настройка и инициализация
1️⃣ Импортируем пакеты:
2️⃣ Создаем переменные:
3️⃣ Инициализируем в initState:
4️⃣Закрываем детектор в dispose:
Логика распознавания
▫️Метод для получения и обработки изображения:
▫️Отображение результата
Здесь FacePainter — это кастомный класс, который рисует рамки вокруг найденных лиц. Про CustomPainter рассказывала в этом посте.
Как видите, добавить распознавание лиц на Flutter можно буквально в несколько шагов. ML Kit обрабатывает изображение прямо на устройстве, без отправки данных на сервер, что повышает безопасность и скорость работы.
Сегодня расскажу про распознавание лиц на Flutter. Это важный инструмент в современных мобильных приложениях: от разблокировки смартфона по лицу до автоматического заполнения данных по фото.
Используя Flutter, мы можем достаточно просто реализовать распознавание лиц, текста или звука, при этом кроссплатформенно. Сегодня я расскажу, как добавить распознавание лиц в свое приложение на Flutter с помощью Google ML Kit.
Зачем нужно распознавание лиц?
✔️Безопасность — контроль доступа по лицу
✔️Идентификация — определение пользователя среди базы данных
✔️Интерактивные приложения — фильтры, маски и другие AR-фичи
✔️Автоматизация — например, распознавание посетителей в системе учета
Что нам понадобится
Для примера будем использовать пакет:
▪️google_mlkit_face_detection — для обнаружения лиц на изображениях
▪️image_picker — для выбора фото из галереи или камеры
Добавим в pubspec.yaml:
Yaml
dependencies:
google_mlkit_face_detection: ^0.13.1
image_picker: ^1.1.2
Настройка и инициализация
1️⃣ Импортируем пакеты:
import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart';
import 'package:image_picker/image_picker.dart';
2️⃣ Создаем переменные:
late ImagePicker _picker;
late FaceDetector _faceDetector;
dynamic _image;
Size? _imageSize;
List<Face> _faces = [];
3️⃣ Инициализируем в initState:
@override
void initState() {
super.initState();
_picker = ImagePicker();
_faceDetector = FaceDetector(
options: FaceDetectorOptions(),
);
}
4️⃣Закрываем детектор в dispose:
@override
void dispose() {
_faceDetector.close();
super.dispose();
}
Логика распознавания
▫️Метод для получения и обработки изображения:
Future<void> _getAndScanImage({final bool? isFromCamera}) async {
// Очищаем данные
setState(() {
_image = null;
_faces = [];
_imageSize = null;
});
// Получаем изображение
final imageXFile = await _picker.pickImage(
source: isFromCamera != null && isFromCamera
? ImageSource.camera
: ImageSource.gallery,
);
// Обрабатываем
if (imageXFile != null) {
final inputImage = InputImage.fromFilePath(imageXFile.path);
final facesList = await _faceDetector.processImage(inputImage);
final imageAsBytes = await imageXFile.readAsBytes();
final imageDecoded = await decodeImageFromList(imageAsBytes);
setState(() {
_faces = facesList;
_image = imageDecoded;
_imageSize = Size(
imageDecoded.width.toDouble(),
imageDecoded.height.toDouble(),
);
});
}
}
▫️Отображение результата
Expanded(
child: FittedBox(
child: SizedBox(
width: _imageSize!.width,
height: _imageSize!.height,
child: CustomPaint(
painter: FacePainter(
faceList: _faces,
imageFile: _image,
),
),
),
),
)
Здесь FacePainter — это кастомный класс, который рисует рамки вокруг найденных лиц. Про CustomPainter рассказывала в этом посте.
Как видите, добавить распознавание лиц на Flutter можно буквально в несколько шагов. ML Kit обрабатывает изображение прямо на устройстве, без отправки данных на сервер, что повышает безопасность и скорость работы.
❤3🔥2