Рисуем как Пикассо, только на Flutter
Привет, это Катя, Flutter Dev Friflex. Flutter предоставляет мощные инструменты для работы с графикой, один из которых — CustomPainter.
Этот класс позволяет рисовать кастомные фигуры, линии, градиенты и другие элементы, которые невозможно создать стандартными виджетами. В этом посте рассмотрим, как использовать CustomPainter, разберем основные методы и попробуем нарисовать кастомную фигуру.
Основные принципы работы CustomPainter
CustomPainter работает в связке с CustomPaint, который отвечает за рендеринг на экране.
CustomPainter переопределяет два метода:
🔴 paint(Canvas canvas, Size size): содержит код отрисовки на canvas.
🔴 shouldRepaint(CustomPainter oldDelegate): указывает, нужно ли перерисовывать объект при изменении состояния
Создание простого CustomPainter
Рассмотрим, как нарисовать круг с градиентной заливкой:
1️⃣ Наследуемся от CustomPainter, что позволяет переопределить метод paint, в котором выполняется отрисовка
2️⃣ Внутри метода paint создаем Paint — кисть для рисования
3️⃣ Используем shader для градиентной заливки — это задает радиальный градиент (от центра к краям), который переходит от синего к фиолетовому цвету
4️⃣ С canvasdrawCircle рисуем круг в центре с радиусом, равным половине ширины
Теперь используем CustomPaint, чтобы отобразить рисунок:
Улучшение производительности
Чтобы избежать ненужных перерисовок, важно:
🔸 Указывать shouldRepaint как false, если рисование не меняется
🔸 Использовать RepaintBoundary, чтобы ограничить область перерисовки
CustomPainter открывает широкие возможности для создания сложных графических элементов в Flutter. Он полезен для кастомных UI-решений, диаграмм, анимаций и визуализаций. Используйте его, когда стандартные виджеты не дают нужного результата.
Привет, это Катя, Flutter Dev Friflex. Flutter предоставляет мощные инструменты для работы с графикой, один из которых — CustomPainter.
Этот класс позволяет рисовать кастомные фигуры, линии, градиенты и другие элементы, которые невозможно создать стандартными виджетами. В этом посте рассмотрим, как использовать CustomPainter, разберем основные методы и попробуем нарисовать кастомную фигуру.
Основные принципы работы CustomPainter
CustomPainter работает в связке с CustomPaint, который отвечает за рендеринг на экране.
CustomPainter переопределяет два метода:
Создание простого CustomPainter
Рассмотрим, как нарисовать круг с градиентной заливкой:
import 'package:flutter/material.dart';
class GradientCirclePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..shader = RadialGradient(
colors: [Colors.blue, Colors.purple],
).createShader(Rect.fromCircle(
center: Offset(size.width / 2, size.height / 2),
radius: size.width / 2,
));
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.width / 2,
paint,
);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
Теперь используем CustomPaint, чтобы отобразить рисунок:
class GradientCircleWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size(200, 200),
painter: GradientCirclePainter(),
);
}
}
Улучшение производительности
Чтобы избежать ненужных перерисовок, важно:
RepaintBoundary(
child: CustomPaint(
size: Size(200, 200),
painter: GradientCirclePainter(),
),
)
CustomPainter открывает широкие возможности для создания сложных графических элементов в Flutter. Он полезен для кастомных UI-решений, диаграмм, анимаций и визуализаций. Используйте его, когда стандартные виджеты не дают нужного результата.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12✍6❤3👌2🏆1
Привет! С вами снова Анна, Friflex Flutter Team Lead.
На собеседованиях Flutter-разработчиков очень часто звучит вопрос — чем отличаются императивная и декларативная навигация. И как показывает опыт, многие кандидаты не понимают различий в этих двух подходах. Сегодня постараемся разобраться.
Императивная навигация работает по принципу управления набором маршрутов, которые существуют в проекте относительно друг друга. Все роуты организуются в приложении по принципу LIFO.
Проще говоря, в императивном подходе маршруты собираются в единый стек. Для примера их можно представить стопкой тарелок. Когда роут добавляется, он складывается сверху (push). Когда вызывается возврат назад — самый верхний удаляется (pop).
Декларативный подход подразумевает работу с состоянием навигации приложения как частью состояния всего приложения.
Он позволяет более абстрактно управлять навигацией, так как не требуется ручное создание каждого конкретного маршрута и есть добавление/удаление. Достаточно передать приложению информацию о том, какое требуется конечное состояние навигации.
Для лучшего понимания различий можно выделить вопросы для каждого из подходов:
🔴 императивный — как выполнить переход и какие методы нужно вызвать для этого?
🔴 декларативный — что нужно показать и какое текущее состояние навигации?
А вы какой подход чаще всего используете в своих проектах?
На собеседованиях Flutter-разработчиков очень часто звучит вопрос — чем отличаются императивная и декларативная навигация. И как показывает опыт, многие кандидаты не понимают различий в этих двух подходах. Сегодня постараемся разобраться.
Императивная навигация работает по принципу управления набором маршрутов, которые существуют в проекте относительно друг друга. Все роуты организуются в приложении по принципу LIFO.
Проще говоря, в императивном подходе маршруты собираются в единый стек. Для примера их можно представить стопкой тарелок. Когда роут добавляется, он складывается сверху (push). Когда вызывается возврат назад — самый верхний удаляется (pop).
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
Navigator.pop(context);
Декларативный подход подразумевает работу с состоянием навигации приложения как частью состояния всего приложения.
Он позволяет более абстрактно управлять навигацией, так как не требуется ручное создание каждого конкретного маршрута и есть добавление/удаление. Достаточно передать приложению информацию о том, какое требуется конечное состояние навигации.
Для лучшего понимания различий можно выделить вопросы для каждого из подходов:
А вы какой подход чаще всего используете в своих проектах?
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10🔥6👍3❤🔥1
Привет, с вами Роза, Flutter Dev Friflex👋 И сегодня мы немного погрузимся в магию FutureOr!
Представьте, вы создаете некий абстрактный класс с различными методами, но точно не знаете, будет ли реализация асинхронной или синхронной. Конечно, вы можете создать два метода или изощряться с разными подходами, но действительно ли это хорошее решение?
Лучше, если вы объявите метод, как FutureOr.
Звучит пока не очень понятно? Давайте разберемся на примерах.
Предположим, мы разрабатываем сервис, который получает данные. Одна реализация будет синхронной, другая — асинхронной:
Aбстрактный класс
⚙️ Когда же использовать FutureOr?
FutureOr — ваш спаситель, когда вам нужно абстрагироваться от того, является ли результат операции асинхронным или синхронным.
🔧 Как обрабатывать FutureOr?
Самый простой способ — использовать проверку типа с помощью is Future.
Да, такой вариант решения может показаться не самым элегантным. Ведь чрезмерное использование is Future может запутать логику и сделать код менее читаемым. Но в некоторых случаях, особенно при работе с абстракциями, это вполне рабочий и понятный подход.
У меня с работой так же. Иногда мне нужен await, чтобы подумать, а иногда все складывается супер. А у вас?
Представьте, вы создаете некий абстрактный класс с различными методами, но точно не знаете, будет ли реализация асинхронной или синхронной. Конечно, вы можете создать два метода или изощряться с разными подходами, но действительно ли это хорошее решение?
Лучше, если вы объявите метод, как FutureOr.
FutureOr<T>
— это такой хитрый тип в Dart, который говорит: «Эй, результат моего метода может быть либо обычным значением типа T,
либо Future<T>,
если вдруг придется подождать».Звучит пока не очень понятно? Давайте разберемся на примерах.
Предположим, мы разрабатываем сервис, который получает данные. Одна реализация будет синхронной, другая — асинхронной:
import 'dart:async';
abstract class SomeService {
FutureOr<String> fetch();
}
class FirstImplService extends SomeService {
@override
Future<String> fetch() async {
await Future.delayed(Duration(seconds: 2));
return 'Данные из Future';
}
}
class SecondImplService extends SomeService {
@override
String fetch() {
return 'Простые данные';
}
}
Aбстрактный класс
SomeService
объявляет метод fetch() с типом возвращаемого значения FutureOr<String>. Это значит, что fetch() может вернуть либо String, либо Future<String>.FutureOr — ваш спаситель, когда вам нужно абстрагироваться от того, является ли результат операции асинхронным или синхронным.
Самый простой способ — использовать проверку типа с помощью is Future.
Да, такой вариант решения может показаться не самым элегантным. Ведь чрезмерное использование is Future может запутать логику и сделать код менее читаемым. Но в некоторых случаях, особенно при работе с абстракциями, это вполне рабочий и понятный подход.
У меня с работой так же. Иногда мне нужен await, чтобы подумать, а иногда все складывается супер. А у вас?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥12❤4✍1👎1🙏1
Привет, с вами вновь Катя, Flutter Dev Friflex. Сегодня поговорим об extension.
Extension — это инструмент, который позволяет добавлять новые методы, геттеры, сеттеры или операторы в существующие классы, не изменяя их исходный код. Это полезно для улучшения читаемости кода, инкапсуляции логики и повышения переиспользуемости.
Синатксис: основные правила
🔴 Имя Extension — имя расширения (необязательно, но рекомендуется для читаемости)
🔴 Тип — существующий тип, который расширяется (String, List, int и другие)
✅ Пример без Extension
В этом случае мы создаем отдельную функцию для изменения строки:
Минусы:
🔴 Неинтуитивный вызов (capitalize(text)), несвойственный String
🔴 Нужно передавать строку в функцию, что делает код менее читаемым
🔴 Усложнение автокомплита в IDE, так как методы не привязаны к типу
✅ С использованием Extension
Здесь метод capitalize становится частью String:
Преимущества:
🔴 Код становится лаконичным: text.capitalize() вместо capitalize(text)
🔴 Лучшая читаемость и автодополнение
🔴 Логика метода инкапсулирована в extension, а не в отдельной функции
Когда использовать Extension?
➡️ Для расширения стандартных типов — когда нужно добавить удобные методы к String, List, DateTime и другим встроенным классам.
➡️ Для инкапсуляции вспомогательной логики. Если часто используемая функция относится к конкретному типу, лучше оформить ее как метод через extension.
➡️ Для упрощения работы с объектами — позволяет обращаться к данным через удобные геттеры или методы, избегая лишнего кода.
Ограничения Extension
🔸 Нельзя добавлять новые поля в класс
🔸 Нельзя переопределить существующие методы
🔸 Расширения не наследуются, то есть нельзя создать extends для другого extension
🔸 Конфликты: если два расширения имеют одинаковый метод, нужно явно указывать, какое расширение использовать
📎 Если кратко: используйте расширения для инкапсуляции часто используемых методов и упрощения работы с базовыми типами в вашем проекте.
Extension — это инструмент, который позволяет добавлять новые методы, геттеры, сеттеры или операторы в существующие классы, не изменяя их исходный код. Это полезно для улучшения читаемости кода, инкапсуляции логики и повышения переиспользуемости.
Синатксис: основные правила
extension ИмяExtension on Тип {
// Методы, геттеры, сеттеры
}
В этом случае мы создаем отдельную функцию для изменения строки:
String capitalize(String text) {
if (text.isEmpty) return text;
return text[0].toUpperCase() + text.substring(1);
}
void main() {
print(capitalize('flutter')); // Flutter
}
Минусы:
Здесь метод capitalize становится частью String:
extension StringExtension on String {
String capitalize() {
if (isEmpty) return this;
return this[0].toUpperCase() + substring(1);
}
}
void main() {
print('flutter'.capitalize()); // Flutter
}
Преимущества:
Когда использовать Extension?
Ограничения Extension
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍8🍓4❤3👻1
Всем привет! Это Анна, Friflex Flutter Team Lead.
Мобильные приложения, в том числе написанные на Flutter, дают возможность настроить обработку различных ссылок.
🔗 Глубокие ссылки (deeplinks) — это ссылки с кастомной схемой, которые позволяют не только открыть именно ваше приложение на устройстве, но и осуществить переход на вложенные маршруты внутри него. Например, ссылка
🔗 Universal links — это универсальные ссылки iOS приложений, которые имеют формат стандартной веб-ссылки, например,
🔗 App Links — это ссылки для Android, которые работают по принципу, идентичному Universial links на iOS.
После настройки ссылок на проектах часто возникает вопрос — как же выполнить их обработку внутри приложения? Здесь на помощь нам придет библиотека app_links.
Для получения ссылок используется экземпляр класса AppLinks:
С помощью этого объекта можно отследить, с какой конкретной ссылки запустили приложение.
Кроме этого, в момент работы приложения в него из платформы также могут поступать различные ссылки. В таком случае плагин дает возможность получать эти ссылки потоком строк или объектов Uri.
У библиотеки хорошая репутация: почти 1 тысяча лайков и более 800 тысяч скачиваний.
Делитесь в комментариях своим опытом работы с app_links и с ссылками приложения в целом.
Мобильные приложения, в том числе написанные на Flutter, дают возможность настроить обработку различных ссылок.
app://product/id123
позволит открыть ваше приложение сразу на странице продукта с идентификатором id123. https://www.example.com.
В случае, если приложение установлено, ссылки открывают его. Если нет — в браузере открывается веб-сайт, который связан с этой же ссылкой. После настройки ссылок на проектах часто возникает вопрос — как же выполнить их обработку внутри приложения? Здесь на помощь нам придет библиотека app_links.
Для получения ссылок используется экземпляр класса AppLinks:
final _instance = AppLinks();
С помощью этого объекта можно отследить, с какой конкретной ссылки запустили приложение.
Future<void> handleInitialLink() async {
final initialLink = await _instance.getInitialLink();
// обработка начальной ссылки
}
Кроме этого, в момент работы приложения в него из платформы также могут поступать различные ссылки. В таком случае плагин дает возможность получать эти ссылки потоком строк или объектов Uri.
final uriSubscription = _instance.uriLinkStream.listen((uri) {
// обработка ссылки в Uri формате
});
final srtringLinksSubscription = _instance.stringLinkStream.listen((stringLink) {
// обработка ссылки в String формате
});
У библиотеки хорошая репутация: почти 1 тысяча лайков и более 800 тысяч скачиваний.
Делитесь в комментариях своим опытом работы с app_links и с ссылками приложения в целом.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤7🔥5🍓3
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, с вами Роза, Flutter Dev Friflex👋
Рано или поздно разработчики сталкиваются с ситуацией, когда приложение начинает вести себя странно: что-то перестает работать или же появляются непонятные ошибки. В такие моменты логи становятся главной подсказкой.
Они помогают быстро выяснить, что пошло не так, понять причину сбоя и найти решение. Кроме того, логирование полезно для:
🔴 анализа поведения пользователей
🔴 поиска узких мест в производительности
🔴 обеспечения стабильной работы приложения
Чтобы логи действительно приносили пользу, важно правильно их структурировать.
Логи условно можно разделить на пять уровней важности. Они могут отличаться в разных библиотеках, но суть остается такой же.
Чтобы лучше понять, как работают уровни логирования, представьте их как пирамиду, где каждый уровень отвечает за определенную степень важности:
🔸 DEBUG — детализированные данные: переменные, выполнение функций, отладочные сообщения.
🔸 INFO — ключевые этапы работы приложения.
🔸 WARNING — потенциальные проблемы, которые пока не приводят к сбоям, но требуют внимания.
🔸 ERROR — ошибки, влияющие на работу приложения.
🔸 CRITICAL (или FATAL) — критические сбои, после которых приложение не может продолжать работу.
Чем ниже уровень, тем больше информации он содержит.
Чем выше, тем критичнее события.
Каждый уровень включает в себя логи всех предыдущих. Такое разделение позволяет:
✅ фильтровать логи по уровню важности
✅ структурировать данные для быстрого анализа
✅ мгновенно реагировать на серьезные проблемы
Как логировать в Dart/Flutter?
Вы можете воспользоваться инструментами, представленными в самом Dart, написать собственный логгер или же использовать готовые решения.
1️⃣ Простейший способ —
2️⃣ Использование dart:developer
3️⃣ Готовые библиотеки:
Logging — официальная библиотека от Dart. Простая, но мощная
Logger — удобный форматированный вывод, цветовые индикации, фильтрация
Talker — гибкая настройка логов, отличная поддержка Flutter
➡️ Если вам нужно просто быстро вывести сообщение —
➡️ Если хотите базовый контроль уровней логов —
➡️ Для продвинутого логирования лучше использовать
Оптимизация логирования: ленивые вычисления
Логирование может быть дорогостоящей операцией, особенно если записывать сложные данные. Чтобы избежать лишних вычислений, можно использовать ленивую инициализацию (closure).
Так выражение выполнится, только если лог действительно нужен.
Еще немного об инструментах логирования — в табличке в комментариях.
❤️ — обсудим в следующем посте удаленные хранилища логов, безопасное логирование и другие аспекты.
Рано или поздно разработчики сталкиваются с ситуацией, когда приложение начинает вести себя странно: что-то перестает работать или же появляются непонятные ошибки. В такие моменты логи становятся главной подсказкой.
Они помогают быстро выяснить, что пошло не так, понять причину сбоя и найти решение. Кроме того, логирование полезно для:
Чтобы логи действительно приносили пользу, важно правильно их структурировать.
Логи условно можно разделить на пять уровней важности. Они могут отличаться в разных библиотеках, но суть остается такой же.
Чтобы лучше понять, как работают уровни логирования, представьте их как пирамиду, где каждый уровень отвечает за определенную степень важности:
Чем ниже уровень, тем больше информации он содержит.
Чем выше, тем критичнее события.
Каждый уровень включает в себя логи всех предыдущих. Такое разделение позволяет:
Как логировать в Dart/Flutter?
Вы можете воспользоваться инструментами, представленными в самом Dart, написать собственный логгер или же использовать готовые решения.
print()
или debugPrint()
Logging — официальная библиотека от Dart. Простая, но мощная
Logger — удобный форматированный вывод, цветовые индикации, фильтрация
Talker — гибкая настройка логов, отличная поддержка Flutter
print
.dart:developer.log
logging, logger
или talker
Оптимизация логирования: ленивые вычисления
Логирование может быть дорогостоящей операцией, особенно если записывать сложные данные. Чтобы избежать лишних вычислений, можно использовать ленивую инициализацию (closure).
logger.i(() => 'State: \$state');
Так выражение выполнится, только если лог действительно нужен.
Еще немного об инструментах логирования — в табличке в комментариях.
❤️ — обсудим в следующем посте удаленные хранилища логов, безопасное логирование и другие аспекты.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤10🔥7❤🔥5👍1
Привет, это Катя, Flutter Dev Friflex. Сегодня поговорим о фреймворке gRPC и его реализации.
Что это?
gRPC — это фреймворк для удаленного вызова процедур (RPC), разработанный Google. Он использует HTTP/2 для транспорта и Protocol Buffers (protobuf) в качестве языка описания интерфейсов и формата сериализации данных.
Настройка и использование gRPC
1️⃣ Добавляем в pubspec.yaml:
2️⃣ Создаем файл .proto
Сделаем на примере создания чата. Определяем сервис в файле .proto. Например, lib/protos/chat.proto:
3️⃣ Генерируем код
Запускаем команду для генерации Dart-кода:
4️⃣ Создаем клиента
5️⃣ Используем клиента в приложении
На что следует обратить внимание:
🔸 gRPC клиент:
➡️ инициализируется один раз при создании состояния
➡️ обеспечивает двустороннюю коммуникацию
➡️ управляет подпиской на поток сообщений
🔸 Потоковая передача:
➡️ receiveMessages() возвращает Stream<Message>
➡️ listen() подписывается на новые сообщения
🔸 Управление ресурсами:
➡️ gRPC соединение должно закрываться
➡️ Отмена подписок происходит автоматически при dispose()
Продолжение — в комментариях📌
Что это?
gRPC — это фреймворк для удаленного вызова процедур (RPC), разработанный Google. Он использует HTTP/2 для транспорта и Protocol Buffers (protobuf) в качестве языка описания интерфейсов и формата сериализации данных.
Настройка и использование gRPC
dependencies:
grpc: ^4.1.0
protobuf: ^3.1.0
dev_dependencies:
protoc_plugin: ^21.1.2
Сделаем на примере создания чата. Определяем сервис в файле .proto. Например, lib/protos/chat.proto:
syntax = "proto3";
package chat;
service ChatService {
rpc SendMessage (Message) returns (MessageResponse);
rpc ReceiveMessages (Empty) returns (stream Message);
}
message Message {
string text = 1;
string sender = 2;
int64 timestamp = 3;
}
message MessageResponse {
bool success = 1;
string error = 2;
}
Запускаем команду для генерации Dart-кода:
bash
protoc --dart_out=grpc:lib/generated -Ilib/protos lib/protos/chat.proto
class GrpcClient {
late ChatServiceClient client;
/// Инициализация канала соединения
GrpcClient() {
final channel = ClientChannel(
'https://localhost',
port: 50051,
options: const ChannelOptions(
credentials: ChannelCredentials.insecure(),
),
);
client = ChatServiceClient(channel);
}
/// Отправка сообщения
Future<MessageResponse> sendMessage(String text, String sender) async {
final message = Message()
..text = text
..sender = sender
..timestamp = DateTime.now().millisecondsSinceEpoch;
try {
return await client.sendMessage(message);
} catch (e) {
print('Error sending message: $e');
return MessageResponse()..success = false..error = e.toString();
}
}
/// Получение сообщений
Stream<Message> receiveMessages() {
return client.receiveMessages(Empty());
}
}
На что следует обратить внимание:
Продолжение — в комментариях
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10⚡7❤5💯1
This media is not supported in your browser
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤5👌2
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, с вами Роза, Flutter Dev Friflex!
Когда только начинаешь разрабатывать на Flutter, многие возможности языка остаются незамеченными. А с опытом начинаешь глубже разбираться в деталях и повышать свою экспертность. С модификаторами классов у меня было так же: изначально в моем арсенале был лишь abstract... и все, наверное. А со временем я узнала и про sealed, и про base. Делюсь этим и с вами!
Зачем нужны модификаторы?
Модификаторы в Dart помогают управлять наследованием и доступностью классов. Они задают ограничения, предотвращают нежелательное расширение или, наоборот, определяют четкие правила для будущих реализаций.
Основные модификаторы:
🔸 abstract
Если вам не нужна реализация всех методов, а вы хотите создать класс-шаблон, используйте abstract.
🔴 Запрещает создавать экземпляры этого класса напрямую (new AbstractClass() не сработает).
🔴 Часто используется как базовый класс, определяющий интерфейс и частичное поведение для наследников.
🔸 base
Разрешает наследование (extends) и реализацию (implements), но только в пределах текущего пакета.
🔴 За пределами пакета base-класс нельзя реализовать (implements).
🔴 Полезно, если нужно предотвратить реализацию, но оставить возможность наследования.
🔸 interface
Принуждает использовать класс только через implements, запрещая наследование (extends).
Полезно, если хотите создать чистый контракт, без возможности переиспользовать реализацию.
🔸 final
Запрещает любое наследование (extends) или реализацию (implements) класса за пределами текущей библиотеки.
Гарантирует, что класс — конечная точка в иерархии. Его нельзя расширить или изменить поведение через подклассы вне вашего контроля.
🔸 mixin
Позволяет переиспользовать код без наследования.
🔴 Класс с mixin можно добавлять к другим классам через with.
🔴 Миксины не могут иметь конструкторов и не могут быть инстанцированы напрямую.
🔸 sealed
Позволяет создавать закрытый набор подтипов.
🔴 Все подклассы должны быть в той же библиотеке, что и sealed-класс.
🔴 Полезно для switch, так как компилятор проверяет, что все случаи учтены (exhaustiveness).
🔴 Отлично подходит для описания состояний (Loading, Success, Error), событий и других строго определенных иерархий.
Как использовать модификаторы?
Добавьте перед классом нужное ключевое слово. Например:
🔖 Важно! Модификаторы можно комбинировать (abstract base class), создавая тонкие правила для классов. Подробнее — в таблице в комментариях.
📎 Официальная документация по модификаторам
А какие модификаторы используете чаще всего? Делитесь в комментариях! 👀
Когда только начинаешь разрабатывать на Flutter, многие возможности языка остаются незамеченными. А с опытом начинаешь глубже разбираться в деталях и повышать свою экспертность. С модификаторами классов у меня было так же: изначально в моем арсенале был лишь abstract... и все, наверное. А со временем я узнала и про sealed, и про base. Делюсь этим и с вами!
Зачем нужны модификаторы?
Модификаторы в Dart помогают управлять наследованием и доступностью классов. Они задают ограничения, предотвращают нежелательное расширение или, наоборот, определяют четкие правила для будущих реализаций.
Основные модификаторы:
Если вам не нужна реализация всех методов, а вы хотите создать класс-шаблон, используйте abstract.
Разрешает наследование (extends) и реализацию (implements), но только в пределах текущего пакета.
Принуждает использовать класс только через implements, запрещая наследование (extends).
Полезно, если хотите создать чистый контракт, без возможности переиспользовать реализацию.
Запрещает любое наследование (extends) или реализацию (implements) класса за пределами текущей библиотеки.
Гарантирует, что класс — конечная точка в иерархии. Его нельзя расширить или изменить поведение через подклассы вне вашего контроля.
Позволяет переиспользовать код без наследования.
Позволяет создавать закрытый набор подтипов.
Как использовать модификаторы?
Добавьте перед классом нужное ключевое слово. Например:
sealed class GameState {
// ...
}
А какие модификаторы используете чаще всего? Делитесь в комментариях! 👀
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥15❤4👍3💅2⚡1
This media is not supported in your browser
VIEW IN TELEGRAM
Сначала главное: нас уже больше 500. Спасибо за интерес, доверие и любовь к Flutter. А теперь — топ постов, которые вас особенно зацепили:
В апреле будет еще больше полезного! Хорошого всем настроения и кода без ошибок
P.S. Если есть темы, которые вам особенно интересны, пишите — учтем.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6❤5😍3
Привет, это Катя, Flutter Dev Friflex.
В предыдущем своем посте я рассказала о том, как настроить и использовать фреймворк gRPC. Сегодня хочу поделиться особенностями работы с ним.
1️⃣ Потоковая передача данных
gRPC поддерживает несколько типов RPC:
🔴 Унарные (один запрос — один ответ)
🔴 Серверные потоки (один запрос —поток ответов)
🔴 Клиентские потоки (поток запросов — один ответ)
🔴 Двунаправленные потоки (поток запросов — поток ответов)
2️⃣ Обработка ошибок
gRPC использует статусные коды для индикации ошибок. Основные статусы:
OK (0) — успех
CANCELLED (1) — операция отменена
UNKNOWN (2) — неизвестная ошибка
INVALID_ARGUMENT (3) — неверные аргументы
DEADLINE_EXCEEDED (4) — превышено время ожидания
NOT_FOUND (5) — ресурс не найден
PERMISSION_DENIED (7) — нет прав
UNAUTHENTICATED (16) — не аутентифицирован
3️⃣ Аутентификация
gRPC поддерживает несколько механизмов аутентификации:
🔴 SSL/TLS с сертификатами
Пример:
🔴 JWT (JSON Web Tokens)
Пример:
🔴 Basic Auth
Пример:
🔴 OAuth2
Пример:
🔴 API Keys
Пример:
🔴 Интерсепторы
Пример:
🔴 mTLS (Mutual TLS)
Пример:
Преимущества:
✅ Высокая производительность
✅ Кросс-языковая поддержка
✅ Встроенная генерация клиентского и серверного кода
✅ Поддержка потоковой передачи данных
✅ Нативные механизмы аутентификации и шифрования
Ссылки на библиотеки: grpc, probuf, protoc_plugin.
gRPC может показаться сложным на первых этапах, но его преимущества окупаются в средних и крупных проектах, особенно когда важны производительность и типобезопасность. Был ли у вас опыт работы с gRPC?
В предыдущем своем посте я рассказала о том, как настроить и использовать фреймворк gRPC. Сегодня хочу поделиться особенностями работы с ним.
gRPC поддерживает несколько типов RPC:
gRPC использует статусные коды для индикации ошибок. Основные статусы:
OK (0) — успех
CANCELLED (1) — операция отменена
UNKNOWN (2) — неизвестная ошибка
INVALID_ARGUMENT (3) — неверные аргументы
DEADLINE_EXCEEDED (4) — превышено время ожидания
NOT_FOUND (5) — ресурс не найден
PERMISSION_DENIED (7) — нет прав
UNAUTHENTICATED (16) — не аутентифицирован
gRPC поддерживает несколько механизмов аутентификации:
Пример:
ChannelCredentials.secure(certificates: certs)
Пример:
CallOptions(metadata: {'authorization': 'Bearer $token'})
Пример:
'Basic ${base64Encode('login:pass')}'
Пример:
AuthInterceptor(OAuthCredentials())
Пример:
CallOptions(metadata: {'x-api-key': key})
Пример:
Кастомные ClientInterceptor
Пример:
ChannelCredentials.secure(clientCertificates: clientCert)
Преимущества:
Ссылки на библиотеки: grpc, probuf, protoc_plugin.
gRPC может показаться сложным на первых этапах, но его преимущества окупаются в средних и крупных проектах, особенно когда важны производительность и типобезопасность. Был ли у вас опыт работы с gRPC?
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥5👍2👏1🎉1
Привет! На связи Анна, Friflex Flutter Team Lead.
В прошлом своем посте мы разобрались, какие внешние ссылки может обрабатывать приложение, и изучили инструмент, который позволяет в приложении определить поведение при их открытии.
Сегодня подробнее поговорим о том, как интегрировать универсальные или, как они еще известны, динамические ссылки во Flutter-приложение.
Один из способов реализовать поддержку ссылок со схемами
С помощью Firebase Dynamic Links очень просто настраивать ссылки и управлять ими в кроссплатформенных приложениях. Но сейчас эта фича в документации Firebase помечена как deprecated. Уже почти год интегрировать в новые проекты ее нельзя, а поддержка в старых проектах закончится 25 августа 2025.
На замену динамическим ссылкам Firebase приходят Universal links для iOS и App Links для Android. Разберемся, как настроить поддержку для каждой из платформ.
🔖 Настройка ссылок на Android очень проста. Для этого Android Studio предоставляет инструмент App Links Assistant (Tools -> App Links Assistant). Он пошагово дает инструкцию и подсказки, какие действия необходимо выполнить, а также сам генерирует код в необходимых местах.
🔴 в AndroidManifest.xml добавить поддержку схем и хоста, которые будет содержать ссылка
🔴 в MainActivity добавить логику нативной обработки ссылок, поступающих извне приложения
🔴 сформировать файл связанных доменов для связи с конкретным сайтом
В итоге вы должны получить сгенерированный файл assetlinks.json.
🔖 На iOS подобного инструмента нет, но настройка не должна вызвать трудностей.
🔴 Для начала в файле Info.plist добавим флаг FlutterDeepLinkingEnabled с значением true. Важно, если используете в приложении сторонние библиотеки для обработки ссылок (например, app_links), значение флага нужно установить false.
🔴 Затем в Xcode переходим в Runner, в разделе Signing and Capabilities добавляем новую Capability и в списке выбираем Associated Domains. Здесь мы указываем, с каким именно доменом должно быть связано наше приложение. Добавляем строку:
🔴 Теперь необходимо создать файл apple-app-site-association. Название файла не должно содержать расширения. В самом файле в формате json необходимо добавить поле «applinks» и «details» для описания того, какое именно приложение связывается с сайтом и какие пути обрабатывает. Полный формат файла можно найти здесь.
Важно правильно составить appIDs. Строка идентификатора должна быть из двух частей:
Эти данные можно достать из вашей карточки приложения в App Store Connect.
🔴 Теперь последний шаг, общий для обеих платформ — созданные файлы
https://www.example.ru/.well-known/apple-app-site-association
https://www.example.ru/.well-known/assetlinks.json
🔴 После загрузки в том же App Links Assistant нужно подтвердить загрузку, чтобы проверить подключение. Для iOS необходимо выждать 24 часа, чтобы Apple получил доступ к файлу.
✅ Вуаля, теперь ваше приложение будет открываться по ссылке https://www.example.ru!
📎 Посмотреть инструкции для Flutter, для Android и для iOS
В прошлом своем посте мы разобрались, какие внешние ссылки может обрабатывать приложение, и изучили инструмент, который позволяет в приложении определить поведение при их открытии.
Сегодня подробнее поговорим о том, как интегрировать универсальные или, как они еще известны, динамические ссылки во Flutter-приложение.
Один из способов реализовать поддержку ссылок со схемами
https
:// и http
:// — использовать функционал Firebase Dynamic Links. Наверняка многие из вас с ним знакомы или даже использовали в своих проектах. С помощью Firebase Dynamic Links очень просто настраивать ссылки и управлять ими в кроссплатформенных приложениях. Но сейчас эта фича в документации Firebase помечена как deprecated. Уже почти год интегрировать в новые проекты ее нельзя, а поддержка в старых проектах закончится 25 августа 2025.
На замену динамическим ссылкам Firebase приходят Universal links для iOS и App Links для Android. Разберемся, как настроить поддержку для каждой из платформ.
В итоге вы должны получить сгенерированный файл assetlinks.json.
<key>FlutterDeepLinkingEnabled</key>
<true />
applinks:www.example.ru
Важно правильно составить appIDs. Строка идентификатора должна быть из двух частей:
<Application Identifier Prefix>.<Bundle Identifier>
Эти данные можно достать из вашей карточки приложения в App Store Connect.
apple-app-site-association
и assetlinks.json
необходимо загрузить на сайт, поддержку хоста которого вы добавили в приложении. После загрузки файлы должны быть доступны по путям:https://www.example.ru/.well-known/apple-app-site-association
https://www.example.ru/.well-known/assetlinks.json
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12❤6👍3❤🔥1👌1
Привет, с вами Роза, Flutter Dev Friflex 👋
В прошлый раз мы обсудили, почему логирование важно и как оно помогает находить проблемы. Сегодня я хочу поделиться хорошими (и не очень) практиками и ответить на главный вопрос: какими должны быть логи?
Когда только начинаешь логировать, легко запутаться: Что писать? Как часто? Что включать в сообщение?
Конечно, многое зависит от проекта, но есть универсальные советы, которые подойдут почти всем.
Пишите структурированные и понятные логи. Включайте в логи:
🔸 Время события — для понимания последовательности действий
🔸 Имя модуля или класса — помогает быстро найти, где возникла проблема
🔸 Уровень важности — чтобы приоритизировать события
🔸 Сообщение — кратко и по сути
🔸 Стек-трейс — обязательно при ошибках, для диагностики
Не логируйте конфиденциальную информацию. Пароли, номера карт, токены — все это не должно попадать в логи. Если нужно — хешируйте или маскируйте данные.
Не злоупотребляйте.
Логировать каждую мелочь — плохая идея. Логируйте только действительно важные события.
Не пишите логи «просто чтобы были». Пишите так, чтобы при возникновении ошибки вы могли точно понять, в чем проблема и где она произошла.
Не логируйте все подряд: если вам нужно проверить наличие одного ключа — не нужно выводить весь массив.
Используйте ленивую инициализацию при логировании больших данных. Это поможет избежать лишней нагрузки и ускорить работу приложения.
Не используйте `.runtimeType` для имен классов. При обфускации они превращаются в непонятный набор символов. Лучше явно указывать имя класса или использовать собственные идентификаторы.
Копируйте не только ошибки. Фиксируйте также важные события, которые помогут отследить поведение приложения.
Общие советы
🔴 Форматируйте логи с помощью библиотек — это упростит чтение и анализ
🔴 Фильтруйте логи во время отладки — сосредоточьтесь на ключевых событиях.
🔴 Продумайте систему логирования заранее — особенно если проект будет масштабироваться
🔴 Храните логи безопасно — используйте защищенные директории и сервисы
🔴 Очищайте старые логи — настройте автоматическую ротацию логов
🔴 Не оставляйте логи из разработки в проде — перед релизом все нужно почистить
🔴 Используйте облачные сервисы для мониторинга логов — Sentry, Firebase Crashlytics и другие
🔴 Не игнорируйте ошибки в логах — лучше разобраться с ними сразу
Если у вас есть свои лайфхаки или боли, связанные с логированием — делитесь в комментариях💬
Или... все-таки писать везде print? 😅
В прошлый раз мы обсудили, почему логирование важно и как оно помогает находить проблемы. Сегодня я хочу поделиться хорошими (и не очень) практиками и ответить на главный вопрос: какими должны быть логи?
Когда только начинаешь логировать, легко запутаться: Что писать? Как часто? Что включать в сообщение?
Конечно, многое зависит от проекта, но есть универсальные советы, которые подойдут почти всем.
Пишите структурированные и понятные логи. Включайте в логи:
Не логируйте конфиденциальную информацию. Пароли, номера карт, токены — все это не должно попадать в логи. Если нужно — хешируйте или маскируйте данные.
Не злоупотребляйте.
Логировать каждую мелочь — плохая идея. Логируйте только действительно важные события.
Не пишите логи «просто чтобы были». Пишите так, чтобы при возникновении ошибки вы могли точно понять, в чем проблема и где она произошла.
Не логируйте все подряд: если вам нужно проверить наличие одного ключа — не нужно выводить весь массив.
Используйте ленивую инициализацию при логировании больших данных. Это поможет избежать лишней нагрузки и ускорить работу приложения.
Не используйте `.runtimeType` для имен классов. При обфускации они превращаются в непонятный набор символов. Лучше явно указывать имя класса или использовать собственные идентификаторы.
Копируйте не только ошибки. Фиксируйте также важные события, которые помогут отследить поведение приложения.
Общие советы
Если у вас есть свои лайфхаки или боли, связанные с логированием — делитесь в комментариях
Или... все-таки писать везде print? 😅
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍6🔥1
Привет, это Катя, Flutter Dev Friflex. Сегодня расскажу об организации файлов и папок в проекте. Здесь есть несколько основных подходов.
Стандартная структура (по типам файлов)
Плюсы:
▫️Простота понимания
▫️Быстрый старт для небольших проектов
Минусы:
▫️Может превратиться в беспорядок, если проект большой
▫️Сложнее находить связанные файлы
Функциональная структура (по фичам)
Плюсы:
▫️Лучшая масштабируемость
▫️Четкое разделение ответственности
▫️Удобство для командной работы
Минусы:
▫️Сложнее для новичков
▫️Избыточность для маленьких проектов
Гибридная структура
Сочетает оба подхода: начинаете с типа файлов и переходите к фичам по мере роста проекта.
Что входит в директории?
Core-директория содержит общие элементы приложения:
▫️app — основная конфигурация приложения
▫️constants — константы, стили, строки
▫️services — API, хранилища, сервисы
▫️utils — вспомогательные функции, extensions
▫️routes — маршрутизация
Feature-директории содержат:
▫️data — модели, DTO, репозитории
▫️domain — бизнес-логика (BLoC, Cubit, Provider)
presentation - UI (виджеты, страницы)
▫️feature.dart — экспорт всех файлов фичи
Что стоит делать?
⚡️Используйте barrel-файлы (feature.dart) для упрощения импортов.
⚡️Следуйте соглашениям об именовании. В разных командах могут быть свои правила, я покажу на примере, как это заведено у нас:
*_screen.dart для полноценных страниц
*_model.dart для моделей данных
*_event.dart, *_state.dart для BLoC
⚡️Избегайте глубокой вложенности — старайтесь не превышать 3-4 уровня.
⚡️Разделяйте по ответственности, а не по типам, когда проект растет.
Выбор структуры зависит от размера и сложности вашего проекта. Начинайте с простого и рефакторите по мере роста приложения. Главное — соблюдать консистентность и следить, чтобы структура оставалась понятной для всех разработчиков в команде.
А какой подход используете вы?
Стандартная структура (по типам файлов)
lib/
├── models/
├── services/
├── widgets/
├── screens/
├── utils/
└── main.dart
Плюсы:
▫️Простота понимания
▫️Быстрый старт для небольших проектов
Минусы:
▫️Может превратиться в беспорядок, если проект большой
▫️Сложнее находить связанные файлы
Функциональная структура (по фичам)
lib/
├── feature_a/
│ ├── models/
│ ├── widgets/
│ ├── screens/
│ └── bloc/
├── feature_b/
│ ├── models/
│ ├── widgets/
│ ├── screens/
│ └── bloc/
├── core/
│ ├── app/
│ ├── constants/
│ ├── services/
│ └── utils/
└── main.dart
Плюсы:
▫️Лучшая масштабируемость
▫️Четкое разделение ответственности
▫️Удобство для командной работы
Минусы:
▫️Сложнее для новичков
▫️Избыточность для маленьких проектов
Гибридная структура
Сочетает оба подхода: начинаете с типа файлов и переходите к фичам по мере роста проекта.
Что входит в директории?
Core-директория содержит общие элементы приложения:
▫️app — основная конфигурация приложения
▫️constants — константы, стили, строки
▫️services — API, хранилища, сервисы
▫️utils — вспомогательные функции, extensions
▫️routes — маршрутизация
Feature-директории содержат:
▫️data — модели, DTO, репозитории
▫️domain — бизнес-логика (BLoC, Cubit, Provider)
presentation - UI (виджеты, страницы)
▫️feature.dart — экспорт всех файлов фичи
Что стоит делать?
⚡️Используйте barrel-файлы (feature.dart) для упрощения импортов.
// В папке feature_a/feature_a.dart
export 'models/model_a.dart';
export 'widgets/widget_a.dart';
export 'screens/screen_a.dart';
⚡️Следуйте соглашениям об именовании. В разных командах могут быть свои правила, я покажу на примере, как это заведено у нас:
*_screen.dart для полноценных страниц
*_model.dart для моделей данных
*_event.dart, *_state.dart для BLoC
⚡️Избегайте глубокой вложенности — старайтесь не превышать 3-4 уровня.
⚡️Разделяйте по ответственности, а не по типам, когда проект растет.
Выбор структуры зависит от размера и сложности вашего проекта. Начинайте с простого и рефакторите по мере роста приложения. Главное — соблюдать консистентность и следить, чтобы структура оставалась понятной для всех разработчиков в команде.
А какой подход используете вы?
🔥12❤5👍2🥰2
Привет, это Юра Петров, руководитель отдела разработки Friflex👋
Хочу лично пригласить вас на конференцию по кроссплатформенной мобильной разработке, которая пройдет 11 апреля в Москве @omp_ru.
Поговорим про Flutter, PWA и KMP — обо всем, что нужно знать, если вы в теме или хотите в нее влиться. Я расскажу, как мы портировали Flutter-приложения на ОС Аврора: покажу кейсы «Дикси», ЭНЕРГОГАРАНТ, idChess и «Мобильный агент». Продемонстрирую, как делить приложение на отдельные сервисы.
Если вам интересно, как запускать приложения на ОС Аврора с помощью привычных инструментов — приходите!
Где: Москва, пр-т Вернадского, 41, БЦ Академик, 4 этаж
Когда: 11 апреля 2025
Регистрация: timepad
Увидимся🙌
Хочу лично пригласить вас на конференцию по кроссплатформенной мобильной разработке, которая пройдет 11 апреля в Москве @omp_ru.
Поговорим про Flutter, PWA и KMP — обо всем, что нужно знать, если вы в теме или хотите в нее влиться. Я расскажу, как мы портировали Flutter-приложения на ОС Аврора: покажу кейсы «Дикси», ЭНЕРГОГАРАНТ, idChess и «Мобильный агент». Продемонстрирую, как делить приложение на отдельные сервисы.
Если вам интересно, как запускать приложения на ОС Аврора с помощью привычных инструментов — приходите!
Где: Москва, пр-т Вернадского, 41, БЦ Академик, 4 этаж
Когда: 11 апреля 2025
Регистрация: timepad
Увидимся🙌
🔥11❤5
Привет, это Анна, Flutter Team Lead Friflex!
Рано или поздно в жизни каждого Flutter-разработчика появляется необходимость работать с потоками Stream. Официальная документация достаточно просто и доступно объясняет весь базовый функционал стримов. Но бывают кейсы, в которых его может быть не достаточно.
Например, вам нужно подключить подписчика StreamSubscription к широковещательному потоку и получить доступ к последнему событию, сгенерированному до момента подключения. Базовый функционал потоков в Dart не дает такой возможности, так как события не кэшируются, и подписчики получают доступ только к тем из них, которые были сгенерированы потоком после подписки.
Здесь на помощь придет библиотека rxdart. Разберемся с ее возможностями.
Подключение к проекту у библиотеки стандартное — достаточно добавить зависимость в pubspec.yaml.
1. Классы потоков
rxdart дает доступ к множеству дополнительных Stream-классов. Вот некоторые из них:
▫️TimerStream — выдает заданное значение только по окончании заданного промежутка времени
▫️MergeStream — объединяет события нескольких потоков в один
▫️RangeStream — возвращает поток int-значений по указанному диапазону
2. Расширения
Кроме классов библиотека дает возможность использовать у стандартных экземпляров Stream дополнительные функции с помощью расширений:
▫️delay() — делает задержку выдачи событий на заданный период Duration
▫️debounce() — при отсутствии заданного Duration паузы между событиями игнорирует их, дает доступ только к собятиям с паузами
▫️mapTo() — выдает константное значение каждый раз, когда поступает событие
▫️takeLast() — пропускает только те события, которые были сгенерированы после получения какого-то конкретного значения
3. Объекты Subjects
Subjects в rxdart — это те же стандартные объекты StreamController, но с дополнительными функциями. Всего их два:
▫️BehaviorSubject — контроллер, который кэширует последнее полученное значение. В момент подписки на поток, управляемый этим контроллером, подписчик получает первым то событие, которое было сгенерировано последним перед его подключением. Этот объект как раз прекрасно позволяет решить кейс, описанный в начале поста.
▫️ReplaySubject — тоже кеширует события, как и BehaviorSubject. Если вам необходимо сохранять не только последнее событие, а еще и другие, этот объект прекрасно справится с этой задачей.
4. Объект Observable
Observable — аналог Stream, в большинстве случаев работает идентично стандартным Stream. Но команда fluttercommunity.dev предупреждает, что в некоторых ситуациях поведение может сильно отличаться. С этими отличиями перед использованием стоит ознакомится в документации.
Делитесь в комментариях своим опытом использования rxdart и работы с потоками во Flutter-приложениях💬
Рано или поздно в жизни каждого Flutter-разработчика появляется необходимость работать с потоками Stream. Официальная документация достаточно просто и доступно объясняет весь базовый функционал стримов. Но бывают кейсы, в которых его может быть не достаточно.
Например, вам нужно подключить подписчика StreamSubscription к широковещательному потоку и получить доступ к последнему событию, сгенерированному до момента подключения. Базовый функционал потоков в Dart не дает такой возможности, так как события не кэшируются, и подписчики получают доступ только к тем из них, которые были сгенерированы потоком после подписки.
Здесь на помощь придет библиотека rxdart. Разберемся с ее возможностями.
Подключение к проекту у библиотеки стандартное — достаточно добавить зависимость в pubspec.yaml.
1. Классы потоков
rxdart дает доступ к множеству дополнительных Stream-классов. Вот некоторые из них:
▫️TimerStream — выдает заданное значение только по окончании заданного промежутка времени
TimerStream('событие', Duration(minutes: 1))
.listen((i) => print(i)); // выводит 'событие' через 1 минуту
▫️MergeStream — объединяет события нескольких потоков в один
MergeStream([
TimerStream(1, Duration(days: 10)),
Stream.fromIterable([2])
])
.listen(print); // выводит 2, 1
▫️RangeStream — возвращает поток int-значений по указанному диапазону
RangeStream(1, 3).listen((i) => print(i)); // выводит 1, 2, 3
2. Расширения
Кроме классов библиотека дает возможность использовать у стандартных экземпляров Stream дополнительные функции с помощью расширений:
▫️delay() — делает задержку выдачи событий на заданный период Duration
Stream.fromIterable([1, 2, 3, 4])
.delay(Duration(seconds: 1))
.listen(print); // [через секунду] выводит 1, 2, 3, 4 одномоментно
▫️debounce() — при отсутствии заданного Duration паузы между событиями игнорирует их, дает доступ только к собятиям с паузами
Stream.fromIterable([1, 2, 3, 4])
.debounce((_) => TimerStream(true, Duration(seconds: 1)))
.listen(print); // выводит 4
▫️mapTo() — выдает константное значение каждый раз, когда поступает событие
Stream.fromIterable([1, 2, 3, 4])
.mapTo(true)
.listen(print); // выводит true, true, true, true
▫️takeLast() — пропускает только те события, которые были сгенерированы после получения какого-то конкретного значения
Stream.fromIterable([1, 2, 3, 4, 5])
.takeLast(3)
.listen(print); // выводит 3, 4, 5
3. Объекты Subjects
Subjects в rxdart — это те же стандартные объекты StreamController, но с дополнительными функциями. Всего их два:
▫️BehaviorSubject — контроллер, который кэширует последнее полученное значение. В момент подписки на поток, управляемый этим контроллером, подписчик получает первым то событие, которое было сгенерировано последним перед его подключением. Этот объект как раз прекрасно позволяет решить кейс, описанный в начале поста.
▫️ReplaySubject — тоже кеширует события, как и BehaviorSubject. Если вам необходимо сохранять не только последнее событие, а еще и другие, этот объект прекрасно справится с этой задачей.
4. Объект Observable
Observable — аналог Stream, в большинстве случаев работает идентично стандартным Stream. Но команда fluttercommunity.dev предупреждает, что в некоторых ситуациях поведение может сильно отличаться. С этими отличиями перед использованием стоит ознакомится в документации.
Делитесь в комментариях своим опытом использования rxdart и работы с потоками во Flutter-приложениях
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11🔥7😍3
Привет, это Роза, Flutter Dev Friflex 👋
Уверена, многие из вас знакомы с Dart DevTools и уже использовали его для анализа своих Flutter-приложений. Но пробовали ли вы создать собственные расширения?
Недавно у меня была такая задача, и я хочу поделиться своим опытом. Чтобы все было максимально понятно и удобно, я разбила его на несколько частей. Начнем с базы — структуры и настройки.
Первым делом создаем новый пакет для нашего расширения. Вы сможете это сделать при помощи команды flutter create --template=package my_dev_tools_ext, либо же вручную.
Далее подключим пакет devtools_extensions — он содержит весь необходимый набор инструментов для создания собственного расширения: доступ к виртуальной машине, темам, виджетам и другим возможностям DevTools.
Например, вы получаете доступ к менеджерам:
▫️extensionManager — взаимодействие с DevTools
▫️serviceManager — доступ к VM (если подключена)
▫️dtdManager — для связи с Dart Tooling Daemon
Следующим шагом необходимо создать папку devtools в корне вашего пакета и в ней папку build, а также добавить файл конфигурации config.yaml.
Файл config.yaml:
Теперь создаем UI. Кроме devtools_extensions, очень полезным будет пакет `devtools_app_shared`. Он содержит готовые компоненты и утилиты, которые используются в оригинальных DevTools.
Например (для кнопки):
👉extensionManager.showNotification покажет уведомление (в симулированной среде это будет лог в консоли).
Подключение расширения
Добавим обертку DevToolsExtension, которая инициализирует расширение:
Чтобы протестировать расширение, вы можете воспользоваться симулированной средой, запустив команду из корня вашего пакета расширения:
Если все работает корректно, соберите расширение:
После этого в devtools/build/ появится сборка, готовая к публикации или локальному использованию.
Как работают расширения DevTools?
Все просто: расширение — это обычный Dart-пакет. Вы можете встроить его в другой pub-пакет или создать отдельный. Чтобы расширение появилось в интерфейсе DevTools, его нужно подключить как зависимость в проекте, где DevTools используются.
Это только верхушка айсберга. Но уже можно поэкспериментировать и начать знакомство с основными пакетами!
Уверена, многие из вас знакомы с Dart DevTools и уже использовали его для анализа своих Flutter-приложений. Но пробовали ли вы создать собственные расширения?
Недавно у меня была такая задача, и я хочу поделиться своим опытом. Чтобы все было максимально понятно и удобно, я разбила его на несколько частей. Начнем с базы — структуры и настройки.
Первым делом создаем новый пакет для нашего расширения. Вы сможете это сделать при помощи команды flutter create --template=package my_dev_tools_ext, либо же вручную.
Далее подключим пакет devtools_extensions — он содержит весь необходимый набор инструментов для создания собственного расширения: доступ к виртуальной машине, темам, виджетам и другим возможностям DevTools.
Например, вы получаете доступ к менеджерам:
▫️extensionManager — взаимодействие с DevTools
▫️serviceManager — доступ к VM (если подключена)
▫️dtdManager — для связи с Dart Tooling Daemon
Следующим шагом необходимо создать папку devtools в корне вашего пакета и в ней папку build, а также добавить файл конфигурации config.yaml.
my_dev_tools_ext/
extensions/
devtools/
build/
config.yaml
lib/
src/
...
Файл config.yaml:
name: my_dev_tools_ext # Имя пакета-расширения
issueTracker: <ссылка_на_трекер>
version: 0.0.1
materialIconCodePoint: '0xe0b1' # Иконка из Material Icons
requiresConnection: true # Нужно ли подключение к VM (по умолчанию true)
Теперь создаем UI. Кроме devtools_extensions, очень полезным будет пакет `devtools_app_shared`. Он содержит готовые компоненты и утилиты, которые используются в оригинальных DevTools.
Например (для кнопки):
DevToolsButton(
onPressed: () async {
await someService.saveData();
extensionManager.showNotification('Данные успешно сохранены!');
},
icon: Icons.save,
label: 'Сохранить',
)
👉extensionManager.showNotification покажет уведомление (в симулированной среде это будет лог в консоли).
Подключение расширения
Добавим обертку DevToolsExtension, которая инициализирует расширение:
void main() {
runApp(const LocalizationDevToolsExtension());
}
class LocalizationDevToolsExtension extends StatelessWidget {
const LocalizationDevToolsExtension({super.key});
@override
Widget build(BuildContext context) {
return const DevToolsExtension(
child: LocalizationSnapshotterWidget(), // ваш основной виджет
);
}
}
Чтобы протестировать расширение, вы можете воспользоваться симулированной средой, запустив команду из корня вашего пакета расширения:
dart run -d chrome --dart-define=use_simulated_environment=true
Если все работает корректно, соберите расширение:
dart run devtools_extensions build_and_copy --source=. --dest=extension/devtools
После этого в devtools/build/ появится сборка, готовая к публикации или локальному использованию.
Как работают расширения DevTools?
Все просто: расширение — это обычный Dart-пакет. Вы можете встроить его в другой pub-пакет или создать отдельный. Чтобы расширение появилось в интерфейсе DevTools, его нужно подключить как зависимость в проекте, где DevTools используются.
Это только верхушка айсберга. Но уже можно поэкспериментировать и начать знакомство с основными пакетами!
🔥10👍7🥰4
Вторая попытка🤫 Какой state-менеджмент вы используете?
Anonymous Poll
1%
Redux 🧓
5%
MobX 🧠
7%
GetX ⚡
1%
yx_scope 🧸
13%
Riverpod 🧙♂️
73%
Bloc 👩💼
Привет, это Катя, Flutter Dev Friflex. Сейчас расскажу про три решения: Bloc, Riverpod и yx_scope, и еще немного про альтернативные подходы.
Bloc
Bloc — это предсказуемый state-менеджмент, основанный на концепции Unidirectional Data Flow (однонаправленный поток данных).
Основные концепции
Events — действия, которые триггерят изменения
States — иммутабельные объекты, описывающие состояние приложения
Bloc — класс, который обрабатывает Events и эмитит States
Плюсы
◽️Четкое разделение логики и UI
◽️Хорошая документация и большое сообщество
◽️Поддержка Cubit (упрощенная версия Bloc)
Минусы
◽️Высокая шаблонность (много повторяющегося кода)
◽️Избыточность для простых сценариев — если состояние приложения простое, Bloc может быть слишком мощным
Пример использования
Riverpod
Riverpod — это улучшенная версия Provider, созданная тем же автором (Remi Rousselet). Он решает проблемы Provider (например, Null safety и тестируемость).
Основные концепции
Provider — источник данных (может быть StateProvider, FutureProvider или другой)
Consumer — виджет, который читает провайдер
AutoDispose — автоматическая отписка от провайдеров
Плюсы
◽️Нет зависимости от BuildContext
◽️Лучшая поддержка тестирования
◽️Гибкость (можно использовать как DI или state-менеджмент)
Минусы
◽️Неочевидная работа с асинхронностью — AsyncValue требует дополнительной обработки ошибок и загрузки
◽️Меньше документации по сравнению с Bloc
Пример использования
yx_scope
yx_scope — это легковесная библиотека для управления состоянием, вдохновленная ScopedModel и InheritedWidget.
Основные концепции
Scope — контейнер для состояния
InheritedScope — автоматически обновляет виджеты при изменении состояния
Плюсы
◽️Простота использования.
◽️Хорошо подходит для небольших приложений.
◽️Низкая шаблонность (минимум повторяющегося кода).
Минусы
◽️Меньше возможностей, чем у Bloc/Riverpod.
◽️Меньше документации
◽️Плохая масштабируемость — в больших проектах библиотека может стать непредсказуемой
Пример использования
Добавляю в комментарии табличку сравнений state-менеджментов. Давайте обсудим!
Bloc
Bloc — это предсказуемый state-менеджмент, основанный на концепции Unidirectional Data Flow (однонаправленный поток данных).
Основные концепции
Events — действия, которые триггерят изменения
States — иммутабельные объекты, описывающие состояние приложения
Bloc — класс, который обрабатывает Events и эмитит States
Плюсы
◽️Четкое разделение логики и UI
◽️Хорошая документация и большое сообщество
◽️Поддержка Cubit (упрощенная версия Bloc)
Минусы
◽️Высокая шаблонность (много повторяющегося кода)
◽️Избыточность для простых сценариев — если состояние приложения простое, Bloc может быть слишком мощным
Пример использования
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0);
@override
Stream<int> mapEventToState(CounterEvent event) async* {
if (event is Increment) yield state + 1;
if (event is Decrement) yield state - 1;
}
}
Riverpod
Riverpod — это улучшенная версия Provider, созданная тем же автором (Remi Rousselet). Он решает проблемы Provider (например, Null safety и тестируемость).
Основные концепции
Provider — источник данных (может быть StateProvider, FutureProvider или другой)
Consumer — виджет, который читает провайдер
AutoDispose — автоматическая отписка от провайдеров
Плюсы
◽️Нет зависимости от BuildContext
◽️Лучшая поддержка тестирования
◽️Гибкость (можно использовать как DI или state-менеджмент)
Минусы
◽️Неочевидная работа с асинхронностью — AsyncValue требует дополнительной обработки ошибок и загрузки
◽️Меньше документации по сравнению с Bloc
Пример использования
final counterProvider = StateProvider<int>((ref) => 0);
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).state++,
child: Text('Count: $count'),
);
}
}
yx_scope
yx_scope — это легковесная библиотека для управления состоянием, вдохновленная ScopedModel и InheritedWidget.
Основные концепции
Scope — контейнер для состояния
InheritedScope — автоматически обновляет виджеты при изменении состояния
Плюсы
◽️Простота использования.
◽️Хорошо подходит для небольших приложений.
◽️Низкая шаблонность (минимум повторяющегося кода).
Минусы
◽️Меньше возможностей, чем у Bloc/Riverpod.
◽️Меньше документации
◽️Плохая масштабируемость — в больших проектах библиотека может стать непредсказуемой
Пример использования
class CounterScope extends Scope {
var count = 0;
void increment() => notifyListeners(count++);
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) => TextButton(
onPressed: CounterScope.of(context).increment,
child: Text('Count: ${CounterScope.of(context).count}'),
);
}
Добавляю в комментарии табличку сравнений state-менеджментов. Давайте обсудим!
❤10🔥6👍4🤡3
Всем привет! С вами Анна, Friflex Flutter Team Lead.
Любой Flutter-разработчик, создавая свой первый проект задавался хоть раз вопросом — с чего начать, что делать и, главное, как упростить себе работу? Сегодня разберу основные шаги, которые позволят подготовить базу.
1 шаг. Создание проекта
Здесь все очень просто - создать проект можно всего одной командой через терминал. Достаточно выполнить:
Вуаля! Проект с названием new_app создан и готов к работе. С помощью дополнительных опций можно конфигурировать проект. Например, опция
О других вариантах можно прочитать здесь.
2 шаг. Настройка запуска приложения
Все знают: чтобы запустить Flutter-проект, достаточно вызвать метод
Что обязательно стоит предусмотреть перед запуском — это верхнеуровневую обработку ошибок. Она поможет избежать падений приложения, если где-то в коде ошибка не будет локально обработана.
Здесь нужно инициализировать
Основной совет — раннер приложения стоит делать максимально простым, чтобы запуск не был слишком долгим. И постарайтесь предусмотреть любые вероятности возникновения ошибок во время запуска.
3 шаг. Настройка флаворов
Здесь на помощь вам придет библиотека flutter_flavorizr.
Подключаете пакет в зависимости вашего приложения, а дальше дело за малым — по примеру из документации нужно указать, какие именно флаворы необходимы, какое название и bundleId должно иметь приложение и какие плаформы оно будет поддерживать:
Далее остается только запустить кодогенерацию.
Немного подробнее про флаворы можно почитать здесь.
4 шаг. Реализовать DI
Здесь конкретных рекомендаций нет, каждый разработчик самостоятельно выбирает подход — можно сделать самописную реализацию, без кодогенерации и сторонних библиотек, можно интегрировать самые популярные пакеты, например, get_it и injectable.
Flutter тоже дает свое видение DI, можно ознакомиться с ним в документации.
5 шаг. Интегрировать роутер
Именно на этом этапе, когда в приложении нет как таковых экранов, удобно продумать подход к роутингу в проекте. В зависимости от подхода можно использовать как навигацию из коробки, так и сторонние библиотеки.
Наиболее популярные и стабильные — go_router и auto_route. Если есть необходимость и желание попробовать полноценный декларативный подход — вам подойдет octopus.
Готово! Приложение полностью подготовлено к написанию самой первой фичи.
В идеале на этом этапе настроить тему приложения и текстовые стили по дизайну, создать глобальные виджеты или даже целый UI kit, настроить http-клиент для управления запросами. Но все эти пункты зависят от вашего проекта, поэтому в основную последовательность не включаем.
Делитесь, каким было ваше первое приложение?
Любой Flutter-разработчик, создавая свой первый проект задавался хоть раз вопросом — с чего начать, что делать и, главное, как упростить себе работу? Сегодня разберу основные шаги, которые позволят подготовить базу.
1 шаг. Создание проекта
Здесь все очень просто - создать проект можно всего одной командой через терминал. Достаточно выполнить:
flutter create new_app
Вуаля! Проект с названием new_app создан и готов к работе. С помощью дополнительных опций можно конфигурировать проект. Например, опция
--empty
создаст его пустым, без шаблонов. О других вариантах можно прочитать здесь.
2 шаг. Настройка запуска приложения
Все знают: чтобы запустить Flutter-проект, достаточно вызвать метод
main() и
запустить функцию runApp()
с виджетом приложения внутри. Здесь вы также можете выполнять любые настройки, которые потребуются перед запуском вашего приложения — например, устанавливать ориентацию экрана и базовую локализацию. Что обязательно стоит предусмотреть перед запуском — это верхнеуровневую обработку ошибок. Она поможет избежать падений приложения, если где-то в коде ошибка не будет локально обработана.
Здесь нужно инициализировать
FlutterError.onError
и PlatformDispatcher.instance.onError
, указать, как именно приложение должно реагировать на ошибки фреймворка и платформы.Основной совет — раннер приложения стоит делать максимально простым, чтобы запуск не был слишком долгим. И постарайтесь предусмотреть любые вероятности возникновения ошибок во время запуска.
3 шаг. Настройка флаворов
Здесь на помощь вам придет библиотека flutter_flavorizr.
Подключаете пакет в зависимости вашего приложения, а дальше дело за малым — по примеру из документации нужно указать, какие именно флаворы необходимы, какое название и bundleId должно иметь приложение и какие плаформы оно будет поддерживать:
flavorizr:
flavors:
prod:
app:
name: "New App"
android:
applicationId: "com.example.prod"
ios:
bundleId: "com.example.prod"
dev:
app:
name: "New App Dev"
android:
applicationId: "com.example.dev"
ios:
bundleId: "com.example.dev"
Далее остается только запустить кодогенерацию.
flutter pub run flutter_flavorizr
Немного подробнее про флаворы можно почитать здесь.
4 шаг. Реализовать DI
Здесь конкретных рекомендаций нет, каждый разработчик самостоятельно выбирает подход — можно сделать самописную реализацию, без кодогенерации и сторонних библиотек, можно интегрировать самые популярные пакеты, например, get_it и injectable.
Flutter тоже дает свое видение DI, можно ознакомиться с ним в документации.
5 шаг. Интегрировать роутер
Именно на этом этапе, когда в приложении нет как таковых экранов, удобно продумать подход к роутингу в проекте. В зависимости от подхода можно использовать как навигацию из коробки, так и сторонние библиотеки.
Наиболее популярные и стабильные — go_router и auto_route. Если есть необходимость и желание попробовать полноценный декларативный подход — вам подойдет octopus.
Готово! Приложение полностью подготовлено к написанию самой первой фичи.
В идеале на этом этапе настроить тему приложения и текстовые стили по дизайну, создать глобальные виджеты или даже целый UI kit, настроить http-клиент для управления запросами. Но все эти пункты зависят от вашего проекта, поэтому в основную последовательность не включаем.
Делитесь, каким было ваше первое приложение?
❤11🔥6❤🔥1
Всем привет, это Роза, Flutter Dev Friflex! 👋
В прошлый раз мы обсудили, как можно создать собственное расширение для DevTools и какие пакеты для этого пригодятся. Я показала вам пример простенького расширения, оформленного как отдельный Dart-пакет.
Сегодня расскажу, как встроить такое расширение прямо в существующий pub-пакет.
Допустим, у вас уже есть пакет с реализованным функционалом, для которого вы хотите сделать DevTools-расширение. В таком случае нет смысла создавать отдельную зависимость только ради расширения — проще сделать расширение частью этого же пакета.
Например, когда пользователь подключает
💡 Как это реализовать?
Все довольно просто. В вашем Dart-пакете, который предоставляет расширение DevTools, нужно добавить каталог
А структура папки
Вы получите примерно такую структуру:
Но не стоит использовать этот подход, если функциональность расширения и самого пакета никак не связаны — это может запутать архитектуру. В этом случае лучше вынести расширение в отдельный pub-пакет и подключать его как
Если же расширение не планируется к повторному использованию и должно быть автономным, его можно разместить в том же репозитории, что и основной пакет, но как отдельный модуль. Такой подход упростит разработку и при подключении через
🔖 Теперь вы знаете еще больше о создании расширений DevTools! В следующий раз я расскажу, как можно взаимодействовать со сторонним кодом с помощью Eval — до встречи.
В прошлый раз мы обсудили, как можно создать собственное расширение для DevTools и какие пакеты для этого пригодятся. Я показала вам пример простенького расширения, оформленного как отдельный Dart-пакет.
Сегодня расскажу, как встроить такое расширение прямо в существующий pub-пакет.
Допустим, у вас уже есть пакет с реализованным функционалом, для которого вы хотите сделать DevTools-расширение. В таком случае нет смысла создавать отдельную зависимость только ради расширения — проще сделать расширение частью этого же пакета.
Например, когда пользователь подключает
package:some_package
к своему приложению, он автоматически получает доступ к расширению DevTools, встроенному в этот пакет. DevTools при запуске определит наличие расширения и добавит новую вкладку для него.Все довольно просто. В вашем Dart-пакете, который предоставляет расширение DevTools, нужно добавить каталог
extension
на верхнем уровне структуры:some_package/
extension/
lib/
...
А структура папки
extension
будет выглядеть следующим образом:extension/
devtools/
build/
config.yaml
Вы получите примерно такую структуру:
some_app/
packages/
some_package/
extension/
devtools/
build/
...
config.yaml
some_devtools_extension/
lib/
Но не стоит использовать этот подход, если функциональность расширения и самого пакета никак не связаны — это может запутать архитектуру. В этом случае лучше вынести расширение в отдельный pub-пакет и подключать его как
dev_dependency.
Если же расширение не планируется к повторному использованию и должно быть автономным, его можно разместить в том же репозитории, что и основной пакет, но как отдельный модуль. Такой подход упростит разработку и при подключении через
dev_dependency
не повлияет на размер конечного пользовательского приложения.Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤5🔥1