Когда объект может быть удалён Garbage Collector’ом?
Anonymous Quiz
1%
Сразу после создания
6%
При переходе на новый экран
92%
Когда на объект больше нет ссылок
1%
Только после ручного вызова GC
Какой ключ лучше использовать для элементов списка с уникальным id из объекта item?
Anonymous Quiz
19%
UniqueKey
72%
ValueKey(item.id)
4%
GlobalKey
4%
ObjectKey(item)
Что произойдет, если вы измените метод initState() в StatefulWidget и примените Hot Reload?
Anonymous Quiz
7%
Новый код initState() будет выполнен сразу
22%
Виджет будет полностью пересоздан, initState() вызовется
67%
initState() не вызовется, старое состояние сохранится
4%
Приложение полностью перезапустится
❤3
Сегодня поговорим про push‑уведомления в реальном проекте: как работать с FCM, показывать локальные уведомления в foreground, обрабатывать background‑события, реализовать deep links по клику и корректно запрашивать разрешения.
Так как это первая статья в новом году, поздравляю всех с праздниками! Желаю вам продуктивного и приятного 2026‑го: чтобы баги фиксились легко, а фичи шли в прод. Уже все вернулись к работе или кто‑то все еще лежит на диване?
Что и зачем:
◾️FCM (Firebase Cloud Messaging) — транспорт: доставляет уведомления/дату на устройство. Поддерживает notification (авто‑показ) и data‑messages (передача произвольных данных)
◾️flutter_local_notifications — показывает локальное нотификационное состояние, полезно, когда приложение на foreground (FCM обычно не показывает системное уведомление, если приложение активно)
◾️Deep links — позволяют при клике на уведомление перейти в конкретный экран приложения (через data payload или динамические ссылки)
Код: инициализация и обработка
Пакеты: firebase_core, firebase_messaging, flutter_local_notifications, (go_router / navigatorKey для навигации).
Простой пример:
// main.dart (сокращённо)
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
final FlutterLocalNotificationsPlugin localNotif = FlutterLocalNotificationsPlugin();
Future<void> _bgHandler(RemoteMessage m) async {
await Firebase.initializeApp();
// Лёгкая логика: запись в БД или планирование локальной нотификации
print('BG message ${m.messageId}');
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_bgHandler);
await localNotif.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
iOS: DarwinInitializationSettings(),
),
onDidReceiveNotificationResponse: (resp) {
final p = resp.payload;
if (p != null) navigatorKey.currentState?.pushNamed(p);
},
);
runApp(MyApp());
}
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
class MyApp extends StatefulWidget { @override State createState() => _MyAppState(); }
class _MyAppState extends State<MyApp> {
final FirebaseMessaging fm = FirebaseMessaging.instance;
@override
void initState() {
super.initState();
_requestPermissions();
FirebaseMessaging.onMessage.listen(_onMessage);
FirebaseMessaging.onMessageOpenedApp.listen((m) => _handleData(m.data));
_checkInitialMessage();
}
Future<void> _requestPermissions() async {
if (Platform.isIOS) {
await fm.requestPermission(alert: true, badge: true, sound: true);
} else {
// Android 13+: POST_NOTIFICATIONS нужно в Manifest + runtime request (permission_handler)
await fm.requestPermission();
}
}
void _onMessage(RemoteMessage m) {
// в foreground показываем локальную нотификацию
final t = m.notification?.title ?? m.data['title'];
final b = m.notification?.body ?? m.data['body'];
localNotif.show(m.hashCode, t, b, const NotificationDetails(
android: AndroidNotificationDetails('ch', 'Channel', importance: Importance.max),
), payload: m.data['deeplink']);
}
Future<void> _checkInitialMessage() async {
final m = await fm.getInitialMessage();
if (m != null) _handleData(m.data);
}
void _handleData(Map<String, dynamic> d) {
final deeplink = d['deeplink'] ?? d['url'] ?? d['screen'];
if (deeplink != null) navigatorKey.currentState?.pushNamed(deeplink);
}
@override
Widget build(BuildContext c) => MaterialApp(navigatorKey: navigatorKey, home: Scaffold(body: Center(child: Text('Ready'))));
}
Продолжение — в комментариях 👇
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6⚡3👍1🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Ну-ка признавайтесь, кто всю неделю прокрастинировал вместо работы? А кто виноват? Конечно, новогодние праздники! Сегодня поговорим о том, как же максимально мягко войти в этот привычный, но давно забытый рабочий режим.
1 шаг. Пообщаться с коллегами
Всем нам непросто возвращаться к работе. Поговорите об этом! Немного юмора и обсуждений самых приятных воспоминаний с каникул легко поднимут настроение и настроют на позитивную волну.
2 шаг. Восстановить память
Наверняка, почти никто из вашей команды полноценно не помнит, какие проблемы и вопросы остались с прошлого года. Поэтому перед тем, как приступить к работе, синхронизируйтесь с коллегами.
Посмотрите доску задач, вспомните, какие незакрытые моменты были отложены на новый год. В идеале — провести планирование с командой, сформировать список задач, расставить приоритеты. Как только вы четко увидите список задач, вам будет проще настроиться на работу.
3 шаг. Начните с простого
Если у вас есть возможность самостоятельно определять порядок выполняемых задач, воспользуйтесь ею. Возьмите в первую очередь самые простые и короткие задачи. Сначала закроете одну, потом вторую, а дальше двигаться по инерции будет проще.
4 шаг. Чаще делайте перерывы
Конечно, звучит не очень продуктивно. Но после долгого отдыха мозгу тяжело долго держать фокус. Давайте ему возможность разгрузиться — короткие перерывы по 5-10 минут помогут в этом. Но не слишком злоупотребляйте, есть риск снова уйти в прокрастинацию.
5 шаг. Восстановите привычный режим жизни.
Чаще всего после длительного перерыва страдает не только работа, но и другие сферы жизни. Верните в жизнь свои старые привычки, приостановленные на праздники. Ходили в спортзал? Вернитесь к тренировкам. Занимались английским? Возобновите занятия. Ложились спать в 22:00? Самое время наладить режим.
Делитесь в комментариях своими советами!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥4👍2😍1
This media is not supported in your browser
VIEW IN TELEGRAM
Понимать код, а не копировать
Привет, это Катя, Flutter Dev Friflex. Сегодня — короткая и практичная заметка о том, почему важно понимать, что делает каждое поле и компонент в проекте, регулярно «проваливаться» в пакеты и отслеживать версии, чтобы избежать сюрпризов в проде.
Почему это критично?
▪️Непонимание контракта поля/метода = баги, неожиданные побочные эффекты, уязвимости
▪️Непроверенные апдейты зависимости = сломанные сборки и runtime‑ошибки
▪️Понимание реализации помогает правильно тестировать, оптимизировать и писать корректную миграцию при изменениях
Куда смотреть?
▫️pub.dev: дата релиза, репозиторий, example, issue tracker
▫️GitHub: README, CHANGELOG.md, Releases/Tags, open/closed issues и PR
▫️Исходники: «Go to definition» в IDE, искать @deprecated, читать реализацию конструктора/методов
▫️Тесты и пример в пакете — часто показывают ожидаемое поведение
На что обращать внимание в поле/компоненте?
▪️Контракт: допустимые входы/выходы, nullable/required, default‑значения
▪️Эффекты: I/O, глобальное состояние, запуск таймеров, необходимость dispose()
▪️Производительность: rebuild/rerender cost, аллокации, потокобезопасность
▪️Депрексации и migration notes: @deprecated и changelog
Практики по версиям и апдейтам
▫️Понимай major = breaking changes. Читай CHANGELOG перед апдейтом
▫️Используй pubspec.lock для воспроизводимости билдов.
Команды: flutter pub outdated; flutter pub deps --style=compact
▫️CI: автоматические тесты после обновления зависимостей
Краткий процесс обновления зависимости
▪️Исследовать пакет (README, changelog, issues, example)
▪️В feature‑ветке обновить зависимость и запустить flutter pub get
▪️Прогнать unit & integration тесты локально и в CI
▪️Развернуть на стейдже
▪️Мониторинг (Crashlytics/Sentry)
▪️Быстрый откат при регрессии
Культура в команде
▫️Составить шаблон PR
▫️Назначать владельца критичных библиотек
▫️Регулярный dependency health check (реально желательно делать)
▫️Обучать команду читать исходники и миграционные заметки
Какие еще практики и инструменты вы используете, чтобы безопасно работать со сторонними зависимостями и глубоко понимать чужой код?
Привет, это Катя, Flutter Dev Friflex. Сегодня — короткая и практичная заметка о том, почему важно понимать, что делает каждое поле и компонент в проекте, регулярно «проваливаться» в пакеты и отслеживать версии, чтобы избежать сюрпризов в проде.
Почему это критично?
▪️Непонимание контракта поля/метода = баги, неожиданные побочные эффекты, уязвимости
▪️Непроверенные апдейты зависимости = сломанные сборки и runtime‑ошибки
▪️Понимание реализации помогает правильно тестировать, оптимизировать и писать корректную миграцию при изменениях
Куда смотреть?
▫️pub.dev: дата релиза, репозиторий, example, issue tracker
▫️GitHub: README, CHANGELOG.md, Releases/Tags, open/closed issues и PR
▫️Исходники: «Go to definition» в IDE, искать @deprecated, читать реализацию конструктора/методов
▫️Тесты и пример в пакете — часто показывают ожидаемое поведение
На что обращать внимание в поле/компоненте?
▪️Контракт: допустимые входы/выходы, nullable/required, default‑значения
▪️Эффекты: I/O, глобальное состояние, запуск таймеров, необходимость dispose()
▪️Производительность: rebuild/rerender cost, аллокации, потокобезопасность
▪️Депрексации и migration notes: @deprecated и changelog
Практики по версиям и апдейтам
▫️Понимай major = breaking changes. Читай CHANGELOG перед апдейтом
▫️Используй pubspec.lock для воспроизводимости билдов.
Команды: flutter pub outdated; flutter pub deps --style=compact
▫️CI: автоматические тесты после обновления зависимостей
Краткий процесс обновления зависимости
▪️Исследовать пакет (README, changelog, issues, example)
▪️В feature‑ветке обновить зависимость и запустить flutter pub get
▪️Прогнать unit & integration тесты локально и в CI
▪️Развернуть на стейдже
▪️Мониторинг (Crashlytics/Sentry)
▪️Быстрый откат при регрессии
Культура в команде
▫️Составить шаблон PR
▫️Назначать владельца критичных библиотек
▫️Регулярный dependency health check (реально желательно делать)
▫️Обучать команду читать исходники и миграционные заметки
Какие еще практики и инструменты вы используете, чтобы безопасно работать со сторонними зависимостями и глубоко понимать чужой код?
❤4🔥4🎉1
Один из самых частых запросов бизнеса — отобразить важные документы внутри приложения. Это может быть все, что угодно: от политики конфиденциальности и правил сервиса до персональных документов пользователей. И как показывает практика, чаще всего такие документы приходят в формате pdf.
Сегодня поговорим о том, как удобно отображать pdf-файлы во Flutter-приложении.
На pub.dev можно найти несколько похожих плагинов для показа pdf-файлов в приложении. Но сегодня рассмотрим один из самых популярных и мною любимых плагинов — syncfusion_flutter_pdfviewer.
Первое, что привлекает в этой библиотеке — возможность открыть файл из разных источников, не только из внутреннего хранилища. Рассмотрим разные варианты.
1️⃣
SfPdfViewer.networkЭтот метод позволяет загрузить файл по ссылке, достаточно передать соответствующий url. И что самое удобное — вы легко можете открыть даже защищенный паролем файл, так как метод дает возможность передать
passwordreturn Scaffold(
body: SfPdfViewer.network(
'https://cdn.syncfusion.com/content/PDFViewer/encrypted.pdf',
password: 'syncfusion',
),
);
2️⃣
SfPdfViewer.asset
Здесь можно открыть любой файл, вложенный в assets проектаreturn Scaffold(body: SfPdfViewer.asset('assets/files/test_file.pdf'))
3️⃣
SfPdfViewer.fileЭтот виджет дает возможность отобразить файл из локального хранилища устройства по пути
return Scaffold(
body: SfPdfViewer.file(
File('path_to_your_local_file'),
),
);
4️⃣
SfPdfViewer.memoryА этот позволяет отобразить файл, представленный в байтах
return Scaffold(
body: FutureBuilder(
future: _loadFromAsset('assets/files/test_file.pdf'),
builder: (context, snapshot) {
if (snapshot.hasData) {
return SfPdfViewer.memory(snapshot.data!);
} else {
return const CircularProgressIndicator();
}
},
),
);
...
Future<Uint8List> _loadFromAsset(String path) async {
final data = await rootBundle.load(path);
return data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
}
Еще одна причина попробовать этот плагин — вид файла можно легко настраивать с помощью дополнительных параметров. Например, можно задать начальные значения страницы и зума —
initialPageNumber и initialZoomLevel. А также цвет выделенного текста при поиске — otherSearchTextHighlightColor и currentSearchTextHighlightColorSfPdfViewer.network(
'https://cdn.syncfusion.com/content/PDFViewer/encrypted.pdf',
password: 'syncfusion',
initialPageNumber: 1,
initialZoomLevel: 1.0,
otherSearchTextHighlightColor: Colors.green.withValues(alpha: 0.5),
currentSearchTextHighlightColor: Colors.blue.withValues(alpha: 0.5),
),
Продолжение — в комментариях 👇
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12❤5💯2❤🔥1🆒1
Привет, это Анна, Flutter Team Lead Friflex. В этой статье разобрала, как во Flutter реализовать поддержку deeplink-ов и универсальных ссылок и как управлять ими на уровне платформы.
Please open Telegram to view this post
VIEW IN TELEGRAM
Хабр
Deeplink-и во Flutter: динамические ссылки и app_links
Привет! Меня зовут Анна Ахлестова, я Flutter Team Lead в компании Friflex . Мобильные приложения, в том числе на Flutter, позволяют настраивать обработку различных ссылок. В этой статье я разберу, как...
🔥8❤5💯2
Сегодня хочу поговорить о довольно интересной задачей, с которой я столкнулась в разработке — как сделать так, чтобы на экране одновременно отображалось несколько модальных окон и при этом они не блокировали друг друга.
Суть проблемы
Стандартное поведение модальных окон во Flutter построено так, что каждое новое окно блокирует предыдущее. Когда вы вызываете showDialog или showModalBottomSheet, появляется затемненный фон (barrier). И даже если его сделать прозрачным, то все равно блокировка, которая не дает взаимодействовать с контентом под фоном, остается. Это классическое поведение модальных окон, но что если нужно открыть несколько окон одновременно и работать с каждым из них?
Первый подход: Stack с позиционированием
Самый очевидный и гибкий способ — отказаться от стандартных диалогов и построить свою собственную систему управления окнами. Основная идея заключается в использовании виджета Stack, в котором мы будем размещать наши модальные окна с помощью Positioned.
Можно создать специальный менеджер, который будет оборачивать все приложение или отдельный экран. Этот менеджер хранит список всех открытых окон и отрисовывает их поверх основного контента. Каждое окно — это отдельный виджет с собственными координатами и размером.
Второй подход: Overlay API
Flutter предоставляет более низкоуровневый инструмент для работы со слоями — Overlay. Это та самая система, которую Flutter использует для отображения диалогов, снекбаров и прочих всплывающих элементов.
Идея в том, чтобы создавать OverlayEntry для каждого нового окна и вставлять его в оверлей. Преимущество этого подхода в том, что окна не ограничены размерами родительского виджета — они существуют на уровне всего приложения.
Для реализации нужно получить доступ к Overlay через контекст, создать OverlayEntry с описанием виджета окна и вставить его. При закрытии окна не забываем удалить entry из оверлея и очистить ссылки.
Я сейчас выбираю между этими вариантами. Суть в том, что:
◾️Stack-подход проще в реализации и отладке, вся логика находится в одном месте, легко управлять состоянием через обычный StatefulWidget
◾️Overlay-подход более мощный и гибкий, но требует большего внимания к управлению жизненным циклом
А вы сталкивались с необходимостью показывать несколько модальных окон одновременно? Какой вариант использовали — Stack, Overlay или, может быть, какое-то готовое решение?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤4🔥3🤡1
This media is not supported in your browser
VIEW IN TELEGRAM
Сегодня окунемся в базу, которая часто у новичков вызывает вопросы и непонимание — чем отличаются сравнения identical() и ==.
Для простоты понимания сначала вспомним, что объекты в коде (переменные) и объекты в памяти — это разные вещи.
Например, вы создаете класс
Person.
class Person {
Person(this.name);
final String name;
@override
bool operator ==(Object other) => other is Person && other.name == name;
@override
int get hashCode => name.hashCode;
}
Теперь вы создаете его экземпляр и помещаете его в переменную
person.
final person = Person('Anna');
Здесь создаваемый экземпляр
Person('Anna') записывается объектом в память программы. А переменная person является ссылкой на объект Person('Anna'). Если же мы создадим два экземпляра Person('Anna'), то они будут занимать два отдельных места в памяти. И теперь очень просто разделить два понятия:
Оператор == используется для проверки того, представляют ли две переменные одно и то же значение. А метод identical() — для проверки, ссылаются ли переменные на один и тот же объект в памяти.
Разберем на примере:
void main() {
final person1 = Person('Anna');
final person2 = Person('Anna');
print(person1 == person2); // выведет true
print(identical(person1, person2)); // выведет false
}
Здесь наглядно видно, что значения у переменных
person1 и person2 одинаковые, но объекты, на которые они ссылаются — разные. ❕Есть одно маленькое, но важное уточнение — мы не используем константный конструктор. На что же влияет
const?При использовании const включается оптимизация памяти — канонизация (canonicalization). Если в программе встречаются два одинаковых вызова конструктора с
const, второй экземпляр создаваться не будет. В памяти будет существовать лишь один объект. Именно поэтому, если класс Person будет иметь константный конструктор
const Person(this.name), то наш пример будет работать иначе.
void main() {
final person1 = const Person('Anna');
final person2 = const Person('Anna');
print(person1 == person2); // выведет true
print(identical(person1, person2)); // тоже выведет true
}
❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
❤21👍4🔥4🤡2😍2
Сегодня я хочу рассказать вам о библиотеке dart_amqp — полнофункциональном клиенте для работы с протоколом AMQP (Advanced Message Queue Protocol). Эта библиотека позволяет приложениям взаимодействовать с брокерами сообщений, такими как RabbitMQ.
Что такое dart_amqp?
dart_amqp — это клиентская библиотека для работы с AMQP-серверами, которая предоставляет удобный API для создания распределенных систем обмена сообщениями. Она поддерживает все основные возможности протокола AMQP, включая очереди, обменники, подтверждения сообщений и транзакции.
Создание клиента
▪️Настройка подключения
Для тонкой настройки подключения используется класс ConnectionSettings, который позволяет переопределить параметры по умолчанию:
Client client = Client(
settings: ConnectionSettings(
host: "127.0.0.1",
port: 5672,
virtualHost: "/",
authProvider: PlainAuthenticationProvider("guest", "guest"),
maxConnectionAttempts: 1,
reconnectWaitTime: Duration(milliseconds: 1500),
),
);
▪️Подключение к серверу
Клиент подключается к серверу лениво, но можно установить соединение явно:
await client.connect();
Аутентификация
Библиотека поставляется с двумя провайдерами аутентификации:
▫️PlainAuthenticationProvider — для простой аутентификации по логину и паролю
▫️AmqPlainAuthenticationProvider — альтернативный вариант Plain-аутентификации
▫️Можно создать собственный провайдер, реализовав интерфейс Authenticator
Работа с TLS
Для защищенных соединений можно передать SecurityContext:
Client client = Client(
settings: ConnectionSettings(
tlsContext: SecurityContext()
..setTrustedCertificates('path/to/cert.pem'),
onBadCertificate: (certificate) => false,
),
);
Heartbeat
Heartbeat позволяет клиенту и серверу отслеживать активность соединения. Если обе стороны указывают ненулевой период (> 1 секунды), механизм активируется автоматически:
Client client = Client(
settings: ConnectionSettings(
tuningSettings: TuningSettings(
heartbeatPeriod: const Duration(seconds: 60),
),
),
);
await client.connect();
print(client.tuningSettings.heartbeatPeriod);
Если сервер не отвечает в течение согласованного периода, выбрасывается исключение HeartbeatFailedException.
Работа с каналами
Каналы (Channels) — это виртуальные соединения внутри одного TCP-подключения:
Channel channel = await client.channel();
queue() — объявить именованную очередь
exchange() — объявить обменник для маршрутизации сообщений
select() — начать транзакцию
commit() — зафиксировать транзакцию
rollback() — откатить транзакцию
Работа с очередями
// Создание очереди
Queue queue = await channel.queue("my_queue");
// Публикация сообщения
queue.publish("Flutter Friendly");
// Потребление сообщений
Consumer consumer = await queue.consume();
consumer.listen((AmqpMessage message) {
print("Получено: ${message.payloadAsString}");
message.ack(); // Подтвердить обработку
});
Exchanges
Обменники позволяют маршрутизировать сообщения к нескольким получателям.
Exchange exchange = await channel.exchange(
"my_exchange",
ExchangeType.FANOUT,
);
// Публикация через обменник
exchange.publish("Broadcast message", "routing_key");
// Привязка потребителя
Consumer consumer = await exchange.bindPrivateQueueConsumer(
["routing_key"],
);
consumer.listen((AmqpMessage message) {
print("Сообщение из exchange: ${message.payloadAsString}");
});
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤4❤🔥3
This media is not supported in your browser
VIEW IN TELEGRAM
Слышали про
covariant? Как показывает практика, многие о нем слышали, но не до конца понимают, для чего он нужен. Разберемся!C
ovariant — это ключевое слово, которое используется для параметров методов. Оно разрешает переопределяющему методу в наследнике сузить тип параметра, указать более конкретный. Разберем на примере. У нас есть класс
Vehicle. Создадим классы более конкретного транспорта — Car и Bike.
class Vehicle {}
class Car extends Vehicle {}
class Bike extends Vehicle {}
Теперь создадим класс
Human, который будет реализовывать метод drive c определенным переданным объектом Vehicle.
class Human {
void drive(Vehicle vehicle) {}
}
В таком случае, когда мы захотим создать наследника
Human — Driver, мы не сможем уточнить класс транспорта в параметрах drive. Нам придется сделать так:
class Driver extends Human {
@override
void drive(Vehicle vehicle) {} // правильный вариант
@override
void drive(Car vehicle) // вызовет ошибку переопределения
}
И здесь нам на помощь придет
covariant. Чтобы он работал, метод drive класса Human нужно видоизменить.
class Human {
void drive(covariant Vehicle vehicle) {}
}
Теперь в реализации
Driver легко можно уточнить, какой конкретный тип требуется передать при вызове метода.
class Driver extends Human {
@override
void drive(Car vehicle) {}
}
В таком случае метод
drive у Driver принимает только Car, и попытка передать Bike приведет к ошибке типов.
void main() {
final car = Car();
final bike = Bike();
final driver = Driver();
driver.drive(bike); // ошибка argument_type_not_assignable
driver.drive(car); // верное использование
}
❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
❤40🔥8😍1
В прошлом посте мы разобрали основы работы с библиотекой dart_amqp 0.3.1: подключение к серверу, создание каналов, работу с очередями и обменниками. Сегодня я покажу, как работать с несколькими RabbitMQ инстансами одновременно в проекте.
AmqpMessage
Класс AmqpMessage — это обертка над входящими сообщениями, которая предоставляет удобные методы для работы с данными.
consumer.listen((AmqpMessage message) {
// Сырые байты
Uint8List rawBytes = message.payload;
// UTF8 строка
String text = message.payloadAsString;
print('Текст: $text');
// JSON
Map<String, dynamic> json = message.payloadAsJson;
print('User ID: ${json['userId']}');
// Метаданные
print('Exchange: ${message.exchangeName}');
print('Routing key: ${message.routingKey}');
});Publisher Confirms
Publisher Confirms — это механизм подтверждения от брокера о том, что сообщение было принято и обработано.
Важно: ACK от брокера означает, что сообщение сохранено на сервере, но не гарантирует, что его получил потребитель
Зачем нужно несколько RabbitMQ соединений?
В микросервисной архитектуре часто возникает необходимость подключаться к разным RabbitMQ серверам:
✔️Разные окружения (dev, staging, production)
✔️Географически распределенные кластеры
✔️Разделение ответственности между сервисами
✔️Миграция между серверами
Паттерн Registry для управления соединениями
Вместо создания множества отдельных клиентов, используйте паттерн Registry — централизованное хранилище соединений с разными RabbitMQ инстансами:
class RabbitRegistry {
RabbitRegistry();
final Map<String, RabbitService> _registry = {};
/// Инициализация соединений для всех хостов
void saveAll(List<String> hosts) {
_removeAllExcept(hosts.toSet());
for (final host in hosts) {
_registry[host] ??= RabbitService(
host: host,
)..initialize();
}
}
/// Получить соединение по хосту
RabbitService read(String host, {bool canCreate = false}) {
if (canCreate) {
return _registry.putIfAbsent(
host,
() => RabbitService(
host: host,
)..initialize(),
);
}
final rabbit = _registry[host];
if (rabbit == null) {
throw StateError(
'RabbitService для "$host" не найден. '
'Инициализируйте его через saveAll().',
);
}
return rabbit;
}
/// Удалить соединение
void remove(String host) {
_registry.remove(host)?.dispose();
}
/// Закрыть все соединения
void dispose() {
for (final service in _registry.values) {
service.dispose();
}
_registry.clear();
}
}Как это работает
1️⃣ Инициализация реестра при старте приложения
При запуске приложения создаем экземпляр RabbitRegistry и передаем ему список хостов RabbitMQ-серверов из конфигурации. Реестр автоматически создаст и инициализирует соединения для каждого хоста.
2️⃣ Использование конкретного соединения
Когда нужно опубликовать сообщение на конкретный сервер, просто запрашиваем соединение по имени хоста через метод read(). Получаем готовый клиент, создаем канал, объявляем очередь и публикуем данные — все как обычно, но с правильным RabbitMQ инстансом.
3️⃣ Динамическое добавление нового хоста
Если в процессе работы приложения нужно подключиться к новому серверу, который не был инициализирован при старте, используем флаг canCreate: true при вызове read(). Реестр автоматически создаст и инициализирует новое соединение.
4️⃣ Обновление списка хостов
При изменении конфигурации (например, добавился новый сервер или убрали старый) просто вызываем saveAll() с обновленным списком. Реестр умный — он добавит новые соединения и автоматически закроет те, которых больше нет в списке.
Преимущества такого подхода
▪️Централизованное управление — все соединения в одном месте
▪️Автоматическая очистка — старые соединения закрываются при обновлении списка
▪️Ленивая инициализация — создание соединения только при необходимости
▪️Безопасность — защита от использования неинициализированных соединений
▪️Масштабируемость — легко добавлять новые хосты
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🔥5👍2
Anonymous Poll
16%
Одно глобальное соединение на все приложение
6%
Создаем новое соединение для каждой операции
12%
Используем паттерн Registry
67%
Что такое RabbitMQ?
Сегодня поговорим про механизмы ограничения доступа объектов в Dart. Простыми словами — какие есть варианты подсветить, что объект не должен использоваться извне.
1️⃣ Начнем с банального — использования «
_»
final String _privateData;
void _doExample() {}
Символ «_» в начале названия говорит о том, что объект может использоваться только в рамках текущей библиотеки.
Кроме того, что он самый известный, он — фактически единственный действительно ограничивающий. Проект не сможет быть скомпилирован, пока приватный объект будет использоваться вне допустимого участка кода.
2️⃣
@protectedАннотация
@protected говорит о том, что объект доступен только внутри класса и в классах наследниках. Здесь очень важно понимать, что аннотация никак не ограничивает компиляцию и работу программы. Единственное, что вы получите при неправильном использовании защищенного объекта — замечание анализатора
invalid_use_of_protected_member
// файл parent.dart
class Parent {
@protected
void doProtected() {}
}
...
// файл child.dart
class Child extends Parent {
void doExample() {
doProtected(); // допустимо
}
}
void main() {
final parent = Parent();
parent.doProtected(); // вызывает предупреждение анализатора
}
3️⃣
@visibleForTesting
Еще одна ограничивающая аннотация. Она как бы говорит — «да, я публичный, но только для того, чтобы быть доступным в тестах». Будет точно полезно для тех, кто покрывает проект тестами
// файл lib/parent.dart
class Parent {
@visibleForTesting
void doExample() {}
}
...
// файл lib/main.dart
void main() {
final parent = Parent();
parent.doExample(); // вызывает предупреждение анализатора invalid_use_of_visible_for_testing_member
}
...
// файл test/parent_test.dart
void main() {
test('Тестируем @visibleForTesting', () {
final test = Parent();
test.doExample(); // допустимо
});
}
Важно понимать — хотя реальное ограничение всего одно, аннотации тоже не стоит списывать со счетов. Они подсвечивают намерение разработчика, обозначают границы ответственности и помогают поддерживать архитектурную дисциплину.
❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍7🔥3🤩1🤡1