После релиза обнаружен баг в новой фиче «чаты между пользователями». Как лучше всего поступить с feature flags?
Anonymous Quiz
7%
Удалить весь код фичи и выпустить хотфикс
86%
Отключить фичу через Feature flag для всех пользователей
1%
Оставить баг, пока команда не напишет новый релиз
7%
Временно скрыть кнопку «чаты» через CSS
👍4❤2🔥2👎1
Сегодня поговорим про виджеты для обработки нажатий и жестов на экране во Flutter-приложениях. Самые распространенные — GestureDetector, InkWell и Listener. Разберем, чем они отличаются и в каком случае лучше использовать тот или иной виджет.
GestureDetector — самый универсальный виджет. Он имеет широкий спектр отслеживания жестов на экране. С его помощью можно обработать как простые одинарные или двойные нажатия (onTap, onDoubleTap), так и более сложные действия, например, свайпы и жесты масштабирования (onHorizontalDrag, onVerticalDrag, onPan, onScale).
Кроме того, что GestureDetector дает возможность отслеживать жесты, некоторые колбэки также возвращают объекты дополнительных сведений о манипуляции пользователя. Например, колбэк
onHorizontalDragUpdate
вызывается при горизонтальном свайпе и дает доступ к объекту DragUpdateDetails
. В нем содержатся данные о позиции указателя и координатах смещения. Это может быть полезно, когда необходимо отслеживать, где именно был выполнен свайп. Визуально виджет никак не реагирует на жесты и не требует обязательной обертки в Material-виджет.
GestureDetector(
onTap: () => print('Tapped!'),
onLongPress: () => print('Long Pressed!'),
onScaleUpdate: (details) => print(
'Scale Updated: ${details.scale}',
),
onHorizontalDragUpdate: (details) => print(
'Drag Updated: ${details.delta}',
),
);
InkWell — наиболее простой виджет из сегодняшнего списка. Он позволяет отслеживать только самые простые нажатия (onTap, onDoubleTap, onLongPress).
Как и GestureDetector, некоторые колбэки возвращают основную информацию о произведенном жесте.
Но в отличие от GestureDetector, отображает анимацию нажатия. Этот визуальный эффект легко кастомизировать. InkWell требует обязательного наличия любого Material-виджета в качестве родителя.
InkWell(
onTap: () => print('Tapped!'),
onTapUp: (details) => print('Tap Up: ${details.localPosition}'),
hoverDuration: const Duration(milliseconds: 100),
highlightColor: Colors.red,
child: Container(
padding: const EdgeInsets.all(16.0),
child: const Text('Tap me!'),
),
);
Listener — низкоуровневый виджет для обработки событий указателя.
Его основное отличие от GestureDetector и InkWell в том, что он не отслеживает сами жесты, не распознает их отличия друг от друга. Он работает непосредственно с указателем, дает полные данные о его поведении.
Как и GestureDetector, виджет не дает никакого визуального отклика.
Listener(
onPointerDown: (event) => print('Pointer Down: ${event.localPosition}'),
onPointerPanZoomStart: (event) => print('Pointer Pan Zoom Start: ${event.localPosition}'),
onPointerMove: (event) => print('Pointer Move: ${event.localPosition}'),
);
Подведем итог — когда же применять каждый из них?
✔️InkWell используйте для обработки самых простых нажатий, когда требуется минимум контроля над действием, а также когда в интерфейсе необходима визуальная анимация жеста.
✔️GestureDetector будет полезен при отслеживании нестандартных жестов, например, свайпов в каком-то конкретном направлении или масштабировании. Также его можно использовать вместо InkWell, когда не требуется показывать никакие визуальные эффекты.
✔️Listener применяйте для самых специфических задач. Он отлично подойдет, когда вам потребуется отслеживать события указателя в нестандартных движениях пользователя. Если GestureDetector не сможет помочь в вашей задаче, Listener точно справится.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥2👍1💯1
Anonymous Quiz
8%
InkWell
18%
Listener
74%
GestureDetector
❤1
This media is not supported in your browser
VIEW IN TELEGRAM
Пусть код компилируется с первого раза, баги боятся вас, а перерывы на кофе длятся дольше, чем митинги.
Вы — волшебники, которые превращают идеи в работающие продукты. А теперь отложите телефон, суббота — для отдыха
Please open Telegram to view this post
VIEW IN TELEGRAM
❤19🎉3🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
В прошлом посте мы разобрали slivers и зачем они нужны. Сегодня поговорим про SliverPersistentHeader.
SliverPersistentHeader — это виджет, который меняет свою высоту при прокрутке. Его можно использовать, например, для создания сжимающихся шапок в профилях пользователей или в деталях товара.
Так как SliverPersistentHeader сам по себе не знает, как отображать содержимое, он использует делегат — SliverPersistentHeaderDelegate. Он управляет его поведением и внешним видом через:
✔️ build — определяет, как выглядит виджет на каждом этапе прокрутки
✔️ maxExtent — задает максимальную высоту заголовка в его развернутом состоянии
✔️ minExtent — определяет минимальную высоту, до которой заголовок сжимается
✔️ shouldRebuild — условие для перерисовки делегата
Внутри метода build доступны параметры:
▪️ double shrinkOffset — показывает, насколько сильно заголовок сжался (от 0 до maxExtent — minExtent)
▪️ bool overlapsContent — указывает, перекрывает ли содержимое последующие сливеры
Для управления поведением заголовка при прокрутке у SliverPersistentHeader есть параметры pinned и floating:
▫️ pinned: true — шапка остается закрепленной, когда прокрутка достигает минимальной высоты
▫️ floating: true — заголовок появляется сразу, как только пользователь начинает прокручивать страницу вверх, даже если прокрутка еще не дошла до самого верха
Пример использования
Давайте реализуем пример «сжимающейся» шапки профиля, где аватар и заголовок плавно меняют свой размер и положение при скролле.
Шаг 1: Подготовка виджетов
Сначала создадим три вспомогательных виджета, которые будут анимироваться:
1. ColoredContainer для затемнения фона
class ColoredContainer extends StatelessWidget {
const ColoredContainer({super.key, required this.progress});
final double progress;
@override
Widget build(BuildContext context) {
return Opacity(
opacity: progress,
child: const ColoredBox(color: Colors.black54),
);
}
}
2. HeaderTitle для заголовка, который будет смещаться к центру
class HeaderTitle extends StatelessWidget {
const HeaderTitle({super.key, required this.progress});
final double progress;
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(milliseconds: 100),
padding: EdgeInsets.lerp(
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
const EdgeInsets.only(bottom: 16),
progress,
),
alignment: Alignment.lerp(
Alignment.bottomLeft,
Alignment.bottomCenter,
progress,
),
child: Text(
'Profile',
style: TextStyle.lerp(
Theme.of(context).textTheme.titleLarge!.copyWith(color: Colors.white),
Theme.of(context).textTheme.titleMedium!.copyWith(color: Colors.white),
progress,
),
),
);
}
}
Продолжение — в комментариях 👇
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍6❤3
Сегодня расскажу про dart:ffi — интерфейс для вызова нативного кода (C/C++) напрямую из Dart.
Зачем нужен dart:ffi
Обычно для связи Flutter с нативом мы используем MethodChannel. Но бывают задачи, где этот способ слишком громоздкий или недостаточно быстрый. В таких случаях выручает dart:ffi:
✔️доступ к системным API напрямую
✔️использование готовых библиотек на C (SQLite, OpenSSL, TensorFlow Lite и других)
✔️оптимизация производительности тяжелых вычислений
Как это работает
▫️Dart связывается с C через указатели и структуры
▫️Нужные функции подключаются через DynamicLibrary.open()
▫️Типы приходится преобразовывать: Dart ⇄ C
Пример: подключаем простую C-функцию
C-библиотека math.c:
C
// math.c
int sum(int a, int b) {
return a + b;
}
Компиляция в динамическую библиотеку:
▪️Linux/Mac: gcc -shared -o libmath.so math.c
▪️Windows: gcc -shared -o math.dll math.c
Dart-код (main.dart):
import 'dart:ffi';
import 'dart:io';
// Загружаем библиотеку
final dylib = Platform.isWindows
? DynamicLibrary.open("math.dll")
: DynamicLibrary.open("libmath.so");
// Сигнатура функции C
typedef c_sum_func = Int32 Function(Int32, Int32);
typedef dart_sum_func = int Function(int, int);
// Получаем функцию
final sum = dylib.lookupFunction<c_sum_func, dart_sum_func>('sum');
void main() {
print(sum(3, 4)); // 7
}
Основные типы FFI
▪️ Int8, Int16, Int32, Int64 ↔ int
▪️ Float, Double ↔ double
▪️ Pointer<T> — указатель на C-объект
▪️ Struct — структуры данных
Когда использовать
✅ Использовать:
▫️подключение нативных библиотек (sqlite3, opus, zlib)
▫️задачи производительности (машинное обучение, криптография)
▫️системные API iOS/Android
❌ Не использовать:
▫️в обычных Flutter-приложениях (чаще достаточно MethodChannel)
▫️если задачу можно решить чистым Dart или готовым пакетом
❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
❤17🔥5👍2
При создании мобильных приложений разработчики часто используют фразу «адаптивная верстка». Сегодня поговорим о том, что же это такое и как реализовать.
Адаптивная верстка — это верстка, которая автоматически подстраивается под разные размеры и типы экранов.
При создании мобильного приложения разработчику всегда стоит держать в голове мысль о том, что устройства пользователей могут быть абсолютно разными. Кто-то пользуется смартфоном со стандартным экраном, а кто-то предпочитает огромные планшеты или даже нестандарные складные устройства, по типу Samsung Fold. При всем этом разнообразии верстка никогда не должна ломаться и тем более падать в ошибку, так как это всегда вызывает много негатива со стороны юзеров.
Для поддержания адаптивности верстки могу порекомендовать 5 самых полезных и универсальных виджетов.
1. MediaQuery
Довольно распространенный виджет для получения данных об устройстве — размерах и ориентации экрана, соотношении сторон, плотности пикселей и многом другом. С его помощью вы можете определять и даже рассчитывать размеры других виджетов на странице.
final screenWidth = MediaQuery.sizeOf(context).width;
final aspectRatio = MediaQuery.sizeOf(context).aspectRatio;
final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait;
2. OrientationBuilder
Этот виджет позволяет отслеживать ориентацию вашего экрана и ее изменения. В случае, если пользователь перевернет устройство, виджеты, вложенные в OrientationBuilder будут перестроены. Например, это может быть полезно, когда в вертикальной ориентации на экран нужно вывести карточки в одну колонку, а на горизонтальной — в две.
OrientationBuilder(
builder: (context, orientation) {
return orientation == Orientation.portrait
? OneColumnView()
: TwoColumnsView();
},
)
3. LayoutBuilder
Этот виджет отслеживает изменения размеров родительского виджета (или экрана, если родительский виджет не будет иметь ограничений). Так же, как и OrientationBuilder, при изменении отслеживаемых параметров он будет перестраивать вложенные виджеты.
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 400) {
return WideLayout();
} else {
return NarrowLayout();
}
},
)
4. AspectRatio
Позволяет задавать дочернему виджету соотношение сторон. Например, у вас стоит задача добавить на экран рекламный баннер, в который будут приходить изображения с сервера. В таком случае, если зафиксировать, допустим, высоту, а ширину оставить по размеру экрана, то картинка будет обрезаться. Здесь оптимальным решением будет добавить AspectRatio с соотношением сторон, совпадающим с соотношением изображения баннера.
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(src),
)
5. ConstrainedBox
Довольно часто при адаптации верстки под планшеты требуется ограничивать ширину содержимого страницы так, чтобы она не была шире определенного значения. При этом на меньших устройствах содержимое должно быть по ширине экрана. Именно здесь на помощь придет ConstrainedBox. Он позволяет задать минимальные и максимальные размеры дочерних виджетов.
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 400,
),
child: const Card(child: Text('Hello World!')),
)
P.S. и конечно же не забываем про базовые flex-виджеты — Flexible и Expanded.
Делитесь своими советами по адаптивной верстке в комментариях
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍7🔥5🤩1
Если вы хоть раз делали приложение и для Web, и для мобильных платформ, то точно сталкивались с конфликтами библиотек dart:io и dart:html (а точнее, уже dart:web) или других платформенно-специфичных пакетов. Вы могли увидеть примерно такое сообщение в консоли:
Failed to build iOS app
Error (Xcode): lib/main.dart:7:8: Error: Dart library 'dart:html' is not available on this platform.
или, например:
Running Gradle task 'assembleDebug'...
../lib/web_widgets/url_functions_web.dart:19:31: Error: Type 'ui.HashUrlStrategy' not found.
class UrlPathStrategy extends ui.HashUrlStrategy {
^^^^^^^^^^^^^^^^^^
........
И это объяснимо, ведь мы используем пакет, который не поддерживается в мобильном устройстве, все просто и понятно.
Давайте по порядку!
◾️ dart:io — библиотека для работы с файлами, сокетами и прочим «системным» API. Он поддерживается только на мобильных и desktop устройствах.
◾️ dart:html(dart:web) — библиотека для работы с DOM и Web API. Поддерживается только в браузере.
Соответственно, если мы подключаем dart:io в вебе или dart:web на мобильных устройствах, компилятор выдаст ошибку.
Чтобы решить эту проблему, можно:
1. Использовать сторонние кроссплатформенные библиотеки:
Например, cross_file.
Этот пакет предоставляет абстракцию XFile, которая позволяет работать с файлами на всех платформах (Web, Desktop, Mobile).
При помощи cross_file, например, вы можете сделать файловый пикер:
Future<XFile?> showFilePicker({List<String>? extensions}) {
return FilePicker.platform.pickFiles(
allowedExtensions: extensions,
type: extensions?.isNotEmpty ?? false ? FileType.custom : FileType.any,
).then((result) {
return result?.xFiles.firstOrNull;
});
}
2. Делать условные импорты
Если подходящей библиотеки нет, можно подключать разные реализации под разные платформы. Например, для настройки ClientChannelBase для grpc. Подробнее о grpc можете прочитать здесь.
export 'grpc_stub.dart'
if (dart.library.io) 'grpc_io.dart'
if (dart.library.js_interop) 'grpc_web.dart';
При использовании этого файла (импорте его в ваш виджет) Dart сам подставит нужную реализацию в зависимости от платформы:
✔️ если это мобильные или desktop → grpc_io.dart
✔️ если это Web → grpc_web.dart
✔️ а если ни одна из библиотек не доступна → grpc_stub.dart
grpc_stub.dart (заглушка):
import 'package:grpc/grpc_connection_interface.dart';
ClientChannelBase setupChannel(String url) =>
throw UnimplementedError();
grpc_io.dart (Mobile/Desktop):
import 'package:grpc/grpc.dart';
import 'package:grpc/grpc_connection_interface.dart';
ClientChannelBase setupChannel(String url) {
return ClientChannel(
uri.host,
port: uri.port,
options: ChannelOptions(credentials: credentials),
);
}
grpc_web.dart (Web):
import 'package:grpc/grpc_connection_interface.dart';
import 'package:grpc/grpc_web.dart';
ClientChannelBase setupChannel(String url) {
return GrpcWebClientChannel.xhr(uri);
}
А в коде используем :
late final ClientChannelBase channel = setupChannel(baseUrl);
Вот и все! Делитесь в комментах вашими лайфхаками при работе с кроссплатформой!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥3❤1
This media is not supported in your browser
VIEW IN TELEGRAM
Google Play 2025: что нового для разработчиков и пользователей
Привет, это Катя, Flutter Dev Friflex. Недавно Google Play выпустил большую обнову, которая затронула разработчиков, игроков и обычных пользователей. Сегодня коротко расскажу про самое главное, а подробнее можно почитать тут.
👨💻 Для разработчиков
В Play Console появились новые метрики (предрегистрации, открытия страниц), библиотека для хранения креативов и даже кнопка, чтобы остановить проблемный релиз
Engage SDK теперь работает и в Play Store, а значит — больше персональных подборок и контента прямо на главной
В Play Games Services достижения стали заметнее — их видно в поиске и на карточках игр
🎮 Для игроков
В программе Play Points запустили Diamond District в Roblox — с эксклюзивным VIP-доступом и мерчем для Gold+
Google Play Games for PC добавил мультиаккаунты и мультиокна. Теперь можно играть сразу в несколько игр (или в одну, но с разных аккаунтов 😏)
На Google TV появились рейтинги и отзывы, а на часах (Wear OS) — персональные рекомендации циферблатов
🔒 Безопасность
Кампания Download Apps, Not Traps — теперь каждое приложение проходит больше 10 000 проверок безопасности
В Play Integrity API добавили новые защиты от взломов и злоупотреблений
💳 Платежи
Больше способов оплаты: PayPal в Сингапуре, Napas во Вьетнаме, Airtel в Кении и других странах
В Индонезии появился QRIS — новый стандарт для бесконтактных платежей
Подписки стали удобнее: можно покупать несколько сразу, есть напоминания о бенефитах и увеличенные grace-периоды. Для разработчиков — возможность ставить цены до $5000
🌍 Глобальные проекты
Google продолжает поддерживать стартапы по всему миру:
программы для студий в Корее, Индонезии, Индии и Латинской Америке
#WeArePlay — серия историй про разработчиков, например, про игру, которая помогла высадить 360 000 деревьев
В общем и целом, Google Play не стоит на месте — теперь тут больше возможностей для всех: и для игроков, и для разработчиков, и для тех, кто просто любит удобные и безопасные приложения.
💬 А что думаете вы по поводу этих обновлений?
Привет, это Катя, Flutter Dev Friflex. Недавно Google Play выпустил большую обнову, которая затронула разработчиков, игроков и обычных пользователей. Сегодня коротко расскажу про самое главное, а подробнее можно почитать тут.
👨💻 Для разработчиков
В Play Console появились новые метрики (предрегистрации, открытия страниц), библиотека для хранения креативов и даже кнопка, чтобы остановить проблемный релиз
Engage SDK теперь работает и в Play Store, а значит — больше персональных подборок и контента прямо на главной
В Play Games Services достижения стали заметнее — их видно в поиске и на карточках игр
🎮 Для игроков
В программе Play Points запустили Diamond District в Roblox — с эксклюзивным VIP-доступом и мерчем для Gold+
Google Play Games for PC добавил мультиаккаунты и мультиокна. Теперь можно играть сразу в несколько игр (или в одну, но с разных аккаунтов 😏)
На Google TV появились рейтинги и отзывы, а на часах (Wear OS) — персональные рекомендации циферблатов
🔒 Безопасность
Кампания Download Apps, Not Traps — теперь каждое приложение проходит больше 10 000 проверок безопасности
В Play Integrity API добавили новые защиты от взломов и злоупотреблений
💳 Платежи
Больше способов оплаты: PayPal в Сингапуре, Napas во Вьетнаме, Airtel в Кении и других странах
В Индонезии появился QRIS — новый стандарт для бесконтактных платежей
Подписки стали удобнее: можно покупать несколько сразу, есть напоминания о бенефитах и увеличенные grace-периоды. Для разработчиков — возможность ставить цены до $5000
🌍 Глобальные проекты
Google продолжает поддерживать стартапы по всему миру:
программы для студий в Корее, Индонезии, Индии и Латинской Америке
#WeArePlay — серия историй про разработчиков, например, про игру, которая помогла высадить 360 000 деревьев
В общем и целом, Google Play не стоит на месте — теперь тут больше возможностей для всех: и для игроков, и для разработчиков, и для тех, кто просто любит удобные и безопасные приложения.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4🔥4👍3
Поговорим об одном из самых распространенных плагинов для Flutter-приложений — cached_network_image. Думаю, уже многие с ним знакомы, но сегодня разберем как эффективно использовать его функционал.
cached_network_image — это плагин, который позволяет не только загружать и отображать изображения из сети, но кэшировать их на устройстве. Эта функция является его главным преимуществом перед стандартным Image.network.
Для его использования достаточно добавить в верстку виджет CachedNetworkImage и передать ему ссылку на изображение в поле
imageUrl
. CachedNetworkImage(
imageUrl: url,
),
Как мы все знаем, загрузка изображений из сети может занимать некоторое время, поэтому рекомендую добавлять индикатор загрузки. Самый простой вариант — использовать
placeholder
. Сюда вы можете добавить любой виджет для отображения загрузки, например, кастомный скелетон. CachedNetworkImage(
imageUrl: url,
placeholder: (context, url) => const AppSkeleton(),
),
Если вам необходимо отображать прогресс загрузки, идеальным решением вместо
placeholder
станет progressIndicatorBuilder
. Он с помощью данных класса DownloadProgress даст вам полный доступ к информации о размере изображения и текущему загруженному объемуCachedNetworkImage(
imageUrl: url,
progressIndicatorBuilder: (context, url, progress) {
return AppProgressIndicator(progressValue: progress.progress);
},
),
Также важно не забывать, что загрузка может прерваться из-за ошибки. На такой случай рекомендую использовать
errorWidget
, который позволит отобразить заглушку. А для отслеживания самой ошибки будет полезен колбэк errorListener
. Он оповестит вас о том, какая возникла ошибка и в какой момент. CachedNetworkImage(
imageUrl: url,
errorWidget: (context, url, error) => AppErrorView(),
errorListener: (error) => print(error),
),
CachedNetworkImage также имеет очень много дополнительных параметров для оформления изображения, которые могут потребоваться вам для кастомизации. Но еще одна немаловажная функция — это управление кэшем с помощью
cacheManager
. Для этого вам потребуется дополнительно подключить в проект flutter_cache_manager. Библиотека дает доступ к объекту DefaultCacheManager, который в свою очередь необходимо передать в CachedNetworkImage.
final cacheManager = DefaultCacheManager();
...
CachedNetworkImage(
imageUrl: url,
cacheManager: cacheManager,
),
Теперь через cacheManager можно выполнять всевозможные манипуляции с кэшированными изображениями: извлекать или удалять по ключу, очищать кэш полностью.
await cacheManager.emptyCache(); // полная очистка кэша
await cacheManager.removeFile(key); // удаление конкретного файла из кэша
await cacheManager.getFileFromCache(key); // извлечение конкретного файла из кэша
А если функционала дефолтного менеджера вам недостаточно, можно создать свой кастомный. Он позволит настроить дополнительные параметры по кэшированию, например, срок жизни кэша или максимальное количество объектов в нем.
final customCacheManager = CacheManager(
Config(
'customCacheKey',
stalePeriod: const Duration(days: 7), // срок жизни кэша
maxNrOfCacheObjects: 100, // максимальное количество объектов в кэше
)
);
...
CachedNetworkImage(
imageUrl: url,
cacheManager: customCacheManager,
),
❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
❤18👍3🔥3😍1
Всем привет! Это Роза, Flutter Dev Friflex 👋
Сегодня расскажу про пакет, с помощью которого можно легко настроить запросы к вашему бэкенду — dart_frog. Он упрощает создание роутингов благодаря автоматической генерации кода. В этом посте покажу пару команд и расскажу основные моменты при работе с пакетом.
🐸 Как начать
Чтобы начать работу с dart_frog, сначала необходимо установить зависимость. Далее можно работать с командами пакета
Для создания проекта используйте:
В результате у вас создастся структура проекта, которая включает в себя DockerFile.
Основная логика обработки запросов в Dart Frog строится на маршрутах (routes). Каждый маршрут соответствует определенному пути и методу HTTP, и чтобы его добавить необходимо создать файл-обработчик в директории routes/.
Например, чтобы добавить POST /api/localization_key/export, создайте файл вручную:
Или используйте команду:
Далее внутри созданного роута реализуйте функцию:
RequestContext позволяет получить доступ к входящему запросу, а также к зависимостям, представленным контексту запроса.
🐸 Middleware
В Dart Frog middleware реализуется как функция, которая оборачивает обработчик маршрута и возвращает новый обработчик с дополнительной логикой.
Middleware добавляется в проект в виде файла с названием _middleware.dart.
Создать middleware можно также с помощью команды:
После добавления или изменения middleware необходимо перезапустить генерацию кода для применения изменений.
Пару важных моментов:
◽️Если файл _middleware.dart находится в папке маршрута без вложенности, middleware применяется ко всем маршрутам на этом уровне
◽️Чтобы middleware применялся только к определенным маршрутам, необходимо создать _middleware.dart внутри соответствующей папки маршрутов
◽️ Если middleware создан в произвольной, не связанной с маршрутом папке, он применен не будет
Структура middleware выглядит таким образом:
Кроме маршрутов и middleware, часто нужно работать с заголовками запросов. В Dart Frog это реализуется так:
Когда вы настроили маршруты, middleware и работу с заголовками, нужно сгенерировать код маршрутов, чтобы сервер мог их использовать.
Для этого используйте:
Это создает сгенерированный код маршрутов в директории build/, который используется сервером (bin/server.dart).
❗️ При работе с dart_frog важно поддерживать правильную вложенность папок, чтобы маршруты сгенерировались корректно. Более подробно можно прочитать в документации Dart Frog.
Сегодня расскажу про пакет, с помощью которого можно легко настроить запросы к вашему бэкенду — dart_frog. Он упрощает создание роутингов благодаря автоматической генерации кода. В этом посте покажу пару команд и расскажу основные моменты при работе с пакетом.
🐸 Как начать
Чтобы начать работу с dart_frog, сначала необходимо установить зависимость. Далее можно работать с командами пакета
Для создания проекта используйте:
dart_frog build
В результате у вас создастся структура проекта, которая включает в себя DockerFile.
Основная логика обработки запросов в Dart Frog строится на маршрутах (routes). Каждый маршрут соответствует определенному пути и методу HTTP, и чтобы его добавить необходимо создать файл-обработчик в директории routes/.
Например, чтобы добавить POST /api/localization_key/export, создайте файл вручную:
lib/routes/api/localization_key/export.dart
Или используйте команду:
dart_frog new route "/api/localization_key/export"
Далее внутри созданного роута реализуйте функцию:
Future<Response> onRequest(RequestContext context) async {
// Логика обработки запроса
return Response.json(body: {'message': 'Экспорт данных'});
}
RequestContext позволяет получить доступ к входящему запросу, а также к зависимостям, представленным контексту запроса.
🐸 Middleware
В Dart Frog middleware реализуется как функция, которая оборачивает обработчик маршрута и возвращает новый обработчик с дополнительной логикой.
Middleware добавляется в проект в виде файла с названием _middleware.dart.
Создать middleware можно также с помощью команды:
dart_frog new middleware 'NAME_OF_FOLDER'
После добавления или изменения middleware необходимо перезапустить генерацию кода для применения изменений.
Пару важных моментов:
◽️Если файл _middleware.dart находится в папке маршрута без вложенности, middleware применяется ко всем маршрутам на этом уровне
◽️Чтобы middleware применялся только к определенным маршрутам, необходимо создать _middleware.dart внутри соответствующей папки маршрутов
◽️ Если middleware создан в произвольной, не связанной с маршрутом папке, он применен не будет
Структура middleware выглядит таким образом:
Handler middleware(Handler handler) {
return handler.use(requestLogger());
}
Кроме маршрутов и middleware, часто нужно работать с заголовками запросов. В Dart Frog это реализуется так:
Response onRequest(RequestContext context) {
final request = context.request;
final headers = request.headers; // Map<String, dynamic>
...
}
Когда вы настроили маршруты, middleware и работу с заголовками, нужно сгенерировать код маршрутов, чтобы сервер мог их использовать.
Для этого используйте:
dart_frog build
Это создает сгенерированный код маршрутов в директории build/, который используется сервером (bin/server.dart).
❗️ При работе с dart_frog важно поддерживать правильную вложенность папок, чтобы маршруты сгенерировались корректно. Более подробно можно прочитать в документации Dart Frog.
👍5❤4💩2
Привет, это Катя, Friflex Flutter Dev. Сегодня разберем TDD (Test-Driven Development) — подход к разработке, при котором сначала пишут тесты, а потом уже код.
Как это работает
▪️Пишем маленький тест, описывающий нужное поведение
▪️Запускаем его — тест падает, ведь кода еще нет
▪️Реализуем минимальный код, чтобы тест прошел
▪️Рефакторим код, при этом тесты должны оставаться зелеными
Пример для понимания
Допустим, нам нужна функция sum, которая складывает два числа.
🔴 Сначала пишем тест:
Он упадет, так как функции еще нет.
🟢Пишем минимальный код, чтобы тест прошел:
🔁 После того как тест прошел, рефакторим код
Плюсы
◽️Код становится надежнее и полностью покрыт тестами
◽️Рефакторить проще — тесты страхуют
◽️Требования становятся понятнее (каждый тест фиксирует ожидание)
Минусы
◽️Разработка стартует медленнее
◽️Нужно уметь писать хорошие тесты
◽️Сложнее применять к UI или к большим интеграциям
А как у вас с TDD?
Как это работает
▪️Пишем маленький тест, описывающий нужное поведение
▪️Запускаем его — тест падает, ведь кода еще нет
▪️Реализуем минимальный код, чтобы тест прошел
▪️Рефакторим код, при этом тесты должны оставаться зелеными
Пример для понимания
Допустим, нам нужна функция sum, которая складывает два числа.
🔴 Сначала пишем тест:
import 'package:flutter_test/flutter_test.dart';
void main() {
test('sum should return correct result', () {
expect(sum(2, 3), 5); // ожидаем 2 + 3 = 5
});
}
Он упадет, так как функции еще нет.
🟢Пишем минимальный код, чтобы тест прошел:
int sum(int a, int b) {
return a + b;
}
🔁 После того как тест прошел, рефакторим код
Плюсы
◽️Код становится надежнее и полностью покрыт тестами
◽️Рефакторить проще — тесты страхуют
◽️Требования становятся понятнее (каждый тест фиксирует ожидание)
Минусы
◽️Разработка стартует медленнее
◽️Нужно уметь писать хорошие тесты
◽️Сложнее применять к UI или к большим интеграциям
А как у вас с TDD?
🔥5❤4👍2
Media is too big
VIEW IN TELEGRAM
Разыгрываем 2 билета на CrossConf — главную конференцию по кроссплатформенным технологиям в России и СНГ.
В программе — двойной поток по Flutter от спикеров из Магнит Маркета, Лаборатории Касперского, BetBoom, Яндекс Про, Friflex и других технологичных компаний, доклады по Crossplatform и Al, нетворкинг, встреча с самой активной частью Flutter-сообщества и веселая вечеринка
Условия конкурса:
Итоги подведем 13 октября. Удачи
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5❤🔥4👍2
Всем привет! Это Анна, Friflex Flutter Team Lead.
Сегодня поговорим о Pull to Refresh — популярном механизме в интерфейсах мобильных приложений, который позволяет одним жестом обновить информацию на экране.
Для пользователя это простое и понятное движение — содержимое экрана нужно потянуть сверху вниз и отпустить. В момент, когда Pull to Refresh срабатывает, пользователю показывается индикатор загрузки. Как только новые данные получены, содержимое экрана меняется, а индикатор загрузки исчезает.
Этот механизм считается неким «золотым стандартом», почти каждое коммерческое приложение не обходится без него. Почему?
▫️ Жест понятен. Пользователи уже настолько привыкли к такому поведению, что их не нужно дополнительно обучать
▫️ Процесс обновления прозрачен и нагляден. Красивые анимации, индикатор загрузки, плавные жесты — все это дает пользователю ощущение полного контроля и не вызывает вопросов
▫️ Механизм дает пользователю обновлять данные, когда он сам захочет. Это повышает его доверие и лояльность к продукту
▫️ Механизм стал настолько распространенным, что его реализация не вызывает проблем на всех платформах
Как реализовать Pull to Refresh во Flutter-приложении?
▪️Базовый виджет RefreshIndicator
Это самый простой способ. Для реализации достаточно прокручиваемый виджет на экране обернуть в RefreshIndicator, а затем добавить вызов onRefresh.
Готово! Такая реализация легко поддерживается и является стабильной.
Но тут важно учесть, что RefreshIndicator не позволит вам особенным образом кастомизировать индикатор загрузки — это всегда будет CircularProgressIndicator, который можно лишь немного видоизменить.
Также важно, что он работает только в паре с Scrollable-виджетом и может реализовать только базовый жест сверху вниз.
▪️Библиотека pull_to_refresh
Если возможностей RefreshIndicator окажется недостачно, на помощь придет SmartRefresher из библиотеки pull_to_refresh.
Этот виджет позволит не только обработать жест перезагрузки (сверху вниз), но и жест дозагрузки (снизу вверх). Можно реализовать механизм по горизонтали с помощью scrollDirection.
SmartRefresher удобен тем, что его индикатор загрузки можно кастомизировать. Пакет уже дает готовые решения, например, WaterDropHeader. С помощью CustomHeader и CustomFooter вы можете настроить вид под свои нужды.
▪️Полноценная кастомная реализация.
Если оба решения вам не подходят, и вам требуется совершенно нестандартный вид — поможет самописное решение.
Здесь перед началом разработки рекомендую оценить необходимость такого пути. Собственное решение займет очень много сил и времени, а также несет достаточно высокие риски ошибок.
Делитесь своим опытом в комментариях😘
Сегодня поговорим о Pull to Refresh — популярном механизме в интерфейсах мобильных приложений, который позволяет одним жестом обновить информацию на экране.
Для пользователя это простое и понятное движение — содержимое экрана нужно потянуть сверху вниз и отпустить. В момент, когда Pull to Refresh срабатывает, пользователю показывается индикатор загрузки. Как только новые данные получены, содержимое экрана меняется, а индикатор загрузки исчезает.
Этот механизм считается неким «золотым стандартом», почти каждое коммерческое приложение не обходится без него. Почему?
▫️ Жест понятен. Пользователи уже настолько привыкли к такому поведению, что их не нужно дополнительно обучать
▫️ Процесс обновления прозрачен и нагляден. Красивые анимации, индикатор загрузки, плавные жесты — все это дает пользователю ощущение полного контроля и не вызывает вопросов
▫️ Механизм дает пользователю обновлять данные, когда он сам захочет. Это повышает его доверие и лояльность к продукту
▫️ Механизм стал настолько распространенным, что его реализация не вызывает проблем на всех платформах
Как реализовать Pull to Refresh во Flutter-приложении?
▪️Базовый виджет RefreshIndicator
Это самый простой способ. Для реализации достаточно прокручиваемый виджет на экране обернуть в RefreshIndicator, а затем добавить вызов onRefresh.
RefreshIndicator(
onRefresh: () async {
// обновить данные тут
},
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ChildWidget(),
),
)
Готово! Такая реализация легко поддерживается и является стабильной.
Но тут важно учесть, что RefreshIndicator не позволит вам особенным образом кастомизировать индикатор загрузки — это всегда будет CircularProgressIndicator, который можно лишь немного видоизменить.
Также важно, что он работает только в паре с Scrollable-виджетом и может реализовать только базовый жест сверху вниз.
▪️Библиотека pull_to_refresh
Если возможностей RefreshIndicator окажется недостачно, на помощь придет SmartRefresher из библиотеки pull_to_refresh.
Этот виджет позволит не только обработать жест перезагрузки (сверху вниз), но и жест дозагрузки (снизу вверх). Можно реализовать механизм по горизонтали с помощью scrollDirection.
SmartRefresher(
enablePullDown: true, // включает жест сверзу вниз
enablePullUp: true, // включает жест снизу вверх
scrollDirection: Axis.vertica l// указывает направление жестов
onRefresh: _onRefresh, // обработка обновления
controller: _refreshController,
child: ListView(...),
);
SmartRefresher удобен тем, что его индикатор загрузки можно кастомизировать. Пакет уже дает готовые решения, например, WaterDropHeader. С помощью CustomHeader и CustomFooter вы можете настроить вид под свои нужды.
▪️Полноценная кастомная реализация.
Если оба решения вам не подходят, и вам требуется совершенно нестандартный вид — поможет самописное решение.
Здесь перед началом разработки рекомендую оценить необходимость такого пути. Собственное решение займет очень много сил и времени, а также несет достаточно высокие риски ошибок.
Делитесь своим опытом в комментариях
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍5🔥2😍1😐1
This media is not supported in your browser
VIEW IN TELEGRAM
Представьте: вы пишите приложение с bloc, и у вас есть кнопка для загрузки данных. Пользователь может нажимать на эту кнопку бесконечное количество раз. К чему это приведет? Скорее всего — к множественным вызовам методов бэка и ухудшению работы UI.
Или, например, у меня была похожая ситуация с чипами, которые можно было выбирать. Запрос на бэк отправлялся при каждом выборе, не только при окончательном.
Как же это исправить? Можно подключить bloc_concurrency. Он позволяет управлять порядком и способом обработки событий в Bloc. Пакет идет вместе с bloc и предоставляет набор функций (EventTransformer’ов) для контроля того, как Bloc обрабатывает события.
По умолчанию в Bloc события обрабатываются строго одно за другим (то есть, sequential()).
Но мы можем изменить это поведение с помощью разных трансформеров:
✔️ concurrent() — события обрабатываются одновременно. Например, если добавить два события одновременно, оба будут выполняться параллельно
✔️ sequence() — события обрабатываются строго по очереди. Если добавить два события одновременно, сначала будет обработано первое, затем второе и так далее
✔️ droppable() — любые новые события, добавленные во время обработки текущего, будут проигнорированы
✔️ restartable() — обрабатывается только последнее событие, предыдущие отменяются, если они еще не завершены
За счет чего это достигается?
Каждый Bloc в методе on<Event> принимает необязательный параметр transformer, который определяет, как события этого типа обрабатываются.
on<LoadMoreKeysEvent>(
_loadMoreKeys,
transformer: droppable(),
);
Также можно создать свой кастомный трансформер. Например, добавить небольшую задержку для поиска:
on<SetSearchQueryEvent>(
_setSearchQuery,
transformer: debounce(const Duration(milliseconds: 300)),
);
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8🔥6👍4
Недавно вышла первая экспериментальная версия Flutter Extension для Gemini CLI — инструмента, который делает разработку Flutter-приложений с помощью ИИ-агентов проще и быстрее.
Что это такое
Flutter Extension for Gemini CLI объединяет возможности Dart и Flutter MCP-сервера с новыми командами и правилами, которые помогают писать чистый, тестируемый и производительный код.
ИИ-агенты теперь могут:
✔️Генерировать проект с нуля
✔️Создавать и реализовывать план разработки
✔️Анализировать, форматировать и тестировать код
✔️Делать коммиты в Git
Как установить
Вот тут не все просто, как минимум, у меня. Пока устанавливала, поймала 3 разные ошибки, связанные с авторизацией, дартом и переменными окружения. Если у кого-то будут проблемы со скачиванием, напишите в комментариях, помогу чем смогу. А так — вот дока по аутентификации.
Сам cli устанавливается одной командой:
gemini extensions install https://github.com/gemini-cli-extensions/flutter
Основные команды
▪️/create-app — создает новый Flutter-проект, добавляет линтеры и генерирует файлы DESIGN.md и IMPLEMENTATION.md
▪️/modify — cоздает подробный план изменений для существующего проекта
▪️/commit — форматирует код, запускает тесты, анализатор и делает коммит
Как работает /create-app
Команда /create-app — это как «умный» flutter create.
Она:
▫️спрашивает цель приложения
▫️создает файлы с дизайном и планом реализации
▫️делит процесс разработки на 3–5 фаз
▫️после каждой фазы анализирует код
▫️форматирует его и делает коммит
Файл DESIGN.md описывает задачу и архитектуру.
Файл IMPLEMENTATION.md — пошаговый план реализации
/modify — умные изменения
Если нужно доработать существующий код, команда /modify создаст:
▫️новый Git-бранч (по желанию)
▫️файл MODIFICATION_PLAN.md с описанием изменений
▫️план внедрения в несколько фаз
ИИ-агент предложит улучшения и выполнит их поэтапно
/commit — чистый коммит без хлопот
Команда /commit:
▫️запускает dart fix, dart format, dart analyze и тесты
▫️исправляет найденные проблемы
▫️генерирует понятное сообщение коммита
В этом посте описана малая часть возможностей. Подробнее читайте в официальном блоге Flutter — Flutter Blog.
Я сама только недавно поставила это расширение и пока начинаю разбираться. Так что если вы уже попробовали и нашли какие-то прикольные штуки —
обязательно поделитесь в комментариях
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤6👍4
Сегодня поговорим об утечках памяти во Flutter-приложениях. Рассмотрим, что может их вызывать и почему не все объекты очищаются самостоятельно.
В самом простом понимании утечка памяти — это ситуация, когда программа хранит в памяти объект, который уже не нужен. На первый взгляд она может показаться достаточно безобидной, но на самом деле способна привести к серьезным проблемам — медленной работе приложения, сбоям и даже крэшам.
Для снижения утечек памяти в Dart есть специальный механизм —Garbage Collector (GC). Его основная задача — выполнять автоматическую очистку неиспользуемых объектов в программе. GC самостоятельно отслеживает все объекты, которые создаются в процессе работы. Затем очищает те объекты, которые не имеют активные ссылки в программе.
Представим, что у нас есть некоторый класс
User
. Метод createUser()
внутри себя создаст его экземпляр и выполнит с ним некоторые манипуляции. После завершения работы метода createUser()
объект user
больше не будет иметь активных ссылок, и GC легко его очистит. void main() {
createUser(); // Вызываем функцию, которая внутри создает пользователя
print('Пользователь создан, но больше не используется');
// После этого GC может удалить объект User
}
void createUser() {
final user = User(name: 'Anna');
print('Привет, ${user.name}');
// Когда функция завершится, переменная user исчезнет из области видимости
}
Но если создать пользователя на уровень выше, в самом
main()
, GC очистить его не сможет, так как активная ссылка будет существовать. void main() {
final user = User('Alice');
// Даже если мы больше не используем user, ссылка на него все еще существует.
// GC не удалит объект.
}
Больше подробностей о работе GC можете найти в этих статьях
▪️ Flutter: Don’t Fear the Garbage Collector
▪️ Garbage Collection in Dart and Its Implications in Flutter
▪️ How Dart’s Garbage Collector Works (And When It Fails You!)
Как мы выяснили, Garbage Collector не может очистить все объекты, поэтому многие источники утечки памяти разработчику требуется отслеживать самостоятельно.
Наиболее часто встречающиеся:
✔️ контроллеры — например, TextEditingController, PageController
✔️ подписки — StreamSubscription
✔️ таймеры — Timer
✔️ слушатели — при добавлении addListener()
✔️ глобальные и статические объекты
Здесь стоит соблюдать ряд простых правил:
▫️ если создаете контроллер или подписку, обязательно вызывайте методы
dispose()
или cancel()
▫️ таймеры также необходимо отменять с помощью
cancel()
▫️ при добавлении слушателя через
addListener()
не забывайте удалять его через removeListener()
▫️ если в коде глобальные или статические объекты вам больше не нужны, можно явно обнулить их ссылки, присвоив
null
▫️ систематически проверяйте свое приложение на утечки памяти
📎Отследить утечки памяти вам помогут специальные инструменты, например, Dart DevTools (вкладка Memory) и библиотека leak_tracker_flutter_testing.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍3🔥2😍1
Anonymous Poll
14%
Регулярно, при работе над каждой новой фичей
39%
Только перед релизом или при заметном падении производительности
48%
Редко или никогда, полагаюсь на Garbage Collector
Flutter Friendly
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6🤡2😁1
Сегодня сделаем с вами обзор библиотеки по локализации — slang. Многие из вас наверняка слышали про .arb и работали с intl, но про slang, возможно, вы слышите впервые.
Что же это такое?
Это библиотека для генерации локализаций. У нее есть ряд существенных преимуществ по сравнению с intl.
Давайте по порядку!
Генерация
Slang поддерживает больше форматов, чем intl: JSON, YAML, ARB и CSV.
Код генерируется командой:
dart run slang
Также можно использовать build_runner, если добавить дополнительный пакет slang_build_runner.
Перед генерацией необходимо создать конфигурационные файлы — slang.yaml или build.yaml. Они во многом похожи на l10n.yaml, но позволяют настроить больше параметров.
Поддержка placeholders
Здесь все стандартно: plural, gender. Но кроме placeholders так же есть возможность работы с links, richText.
{
"user": {
"messageCount": {
"one": "У тебя 1 сообщение",
"other": "У тебя {count} сообщений"
}
}
}
Получаем
t.user.messageCount(count: 5);
Типобезопасность
Библиотека устойчива к ошибкам: опечатки или пропущенные аргументы невозможны,
так как все ключи и параметры проверяются на этапе компиляции.
Если ключа нет — компилятор не даст собрать проект.
Поддержка Flutter
Пакет slang_flutter предоставляет провайдер для управления локалями в проекте.
Можно легко переключать язык в рантайме, получать переводы через контекст
context.t
и использовать ленивую инициализацию делегатов.CLI-команды
Библиотека предоставляет набор CLI-команд:
dart run slang # генерация файлов локализации
dart run slang analyze # поиск неиспользуемых и отсутствующих переводов
dart run slang normalize # сортировка переводов по базовой локали
dart run slang configure # автообновление CFBundleLocalizations
dart run slang edit move loginPage authPage # переименование или перенос ключей
dart run slang migrate arb src.arb dest.json # миграция из arb в json
Runtime overrides
Slang поддерживает runtime overrides — вы можете менять переводы на лету, без пересборки приложения.
Несколько замечаний:
✔️Переопределения могут быть частичными – обновляются только указанные ключи
✔️Повторное переопределение заменяет предыдущее
✔️Новые переводы анализируются, но не активируются
✔️Плейсхолдеры (
name
) остаются без изменений❕Не стоит сразу бросаться внедрять slang во все проекты. Он может быть избыточен для небольших проектов, у него сложнее конфигурация, чем у intl. У вас
м
огут возникнуть сложности при работе с многомодульными проектами, где нужно объединять переводы из разных пакетов. Также типобезопасность делает невозможным использование динамических ключей, что в некоторых сценариях может ограничивать гибкость.Я лишь представила краткий обзор в этом посте, для подробной информации — посетите библиотеку.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10🔥8👌2