Flutter Friendly
1.04K subscribers
191 photos
75 videos
1 file
153 links
Канал Friflex о разработке на Flutter. Обновления, плагины, полезные материалы — превращаем знания в реальный опыт, доступный каждому разработчику.

🔗 Наш канал для разработчиков: @friflex_dev
🔗 Канал о продуктовой разработке: @friflex_product
Download Telegram
💎Привет, это Катя, Flutter Dev Friflex.

Сегодня хочу поговорить о довольно интересной задачей, с которой я столкнулась в разработке — как сделать так, чтобы на экране одновременно отображалось несколько модальных окон и при этом они не блокировали друг друга.

Суть проблемы
Стандартное поведение модальных окон во 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
👍84🔥3🤡1
This media is not supported in your browser
VIEW IN TELEGRAM
👋Привет! Это Анна, Flutter Team Lead Friflex

Сегодня окунемся в базу, которая часто у новичков вызывает вопросы и непонимание — чем отличаются сравнения 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
🪙Привет, это Катя, Flutter Dev Friflex

Сегодня я хочу рассказать вам о библиотеке 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}");
});


Если вам интересна эта тема, то в следующей статье я объясню работу с AmqpMessage, Publisher Confirms, обработку ошибок и реальные примеры работы с несколькими RabbitMQ инстансами одновременно.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥114❤‍🔥3
This media is not supported in your browser
VIEW IN TELEGRAM
💠Привет! Это Анна, Flutter Team Lead Friflex

Слышали про covariant? Как показывает практика, многие о нем слышали, но не до конца понимают, для чего он нужен. Разберемся!

Covariant — это ключевое слово, которое используется для параметров методов. Оно разрешает переопределяющему методу в наследнике сузить тип параметра, указать более конкретный.

Разберем на примере. У нас есть класс Vehicle. Создадим классы более конкретного транспорта — Car и Bike.


class Vehicle {}
class Car extends Vehicle {}
class Bike extends Vehicle {}


Теперь создадим класс Human, который будет реализовывать метод drive c определенным переданным объектом Vehicle.


class Human {
void drive(Vehicle vehicle) {}
}


В таком случае, когда мы захотим создать наследника HumanDriver, мы не сможем уточнить класс транспорта в параметрах 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
28🔥6😍1