uk
Feedback
Flutter Friendly

Flutter Friendly

Відкрити в Telegram

Канал Friflex о разработке на Flutter. Обновления, плагины, полезные материалы — превращаем знания в реальный опыт, доступный каждому разработчику. 🔗 Наш канал для разработчиков: @friflex_dev 🔗 Канал о продуктовой разработке: @friflex_product

Показати більше
1 015
Підписники
-124 години
-17 днів
-430 день
Архів дописів
Всем привет! С вами Анна, Friflex Flutter Team Lead👋 Одна из самых распространенных болей многих Flutter-проектов — это отсу
Всем привет! С вами Анна, Friflex Flutter Team Lead👋 Одна из самых распространенных болей многих Flutter-проектов — это отсутствие тестов в коде. И хоть тема не новая, поговорим сегодня о том, какие виды тестов бывают, для чего они нужны и почему их наличие может спасти ваш проект. Существуют три вида тестов: 🔸 Unit-тесты Их основная задача —протестировать работоспособность какой-то одной конкретной функции, метода, класса по всем возможным сценариям 🔸 Widget-тесты Этот тип тестов призван проверять именно отдельные виджеты. Widget-тесты позволяют проверить, действительно ли тот или иной виджет корректно располагается на экране и правильно себя ведет при взаимодействии с ним пользователя 🔸 Integration-тесты Интеграционные тесты — самый сложный в настройке, но очень полезный тип тестов. Он позволяет проверить большие участки вашего приложения, протестировать взаимодействие разных модулей и виджетов между собой. Если юнит- и виджет-тесты призваны проверять работу отдельно только одного объекта в проекте, то интеграционные проверяет все в совокупности. Кроме этого, они также позволяют проверить производительность всего приложения. Почему же отсутствие тестов является проблемой? Многие разработчики скажут, что написание тестов — лишняя трата времени, человеческих ресурсов и вообще занятие монотонное и скучное. Но здесь есть пара весомых доводов. ✅Когда проект маленький, простой, не нагружен сложной логикой, его ручное тестирование занимает немного времени и усилий QA-специалистов. Но с ростом приложения, с течением времени многие нюансы той или иной фичи забываются, их ручная проверка становится все менее эффективной. ✅Сюда же можно отнести и человеческий фактор — разработчики, тестировщики могут меняться, а знания — теряться. Здесь на помощь приходят тесты в коде проекта. Стоит всего один раз при разработке того или иного функционала покрыть тестами всевозможные сценарии, и в будущем вероятность пропустить в продакшн сломанный функционал сильно снижается. 💡Совет: интегрируйте в свой CI/CD процесс проверки всех написанных тестов в проекте. Если тесты не проходят, сборке или пул-реквесту лучше не попадать на следующий этап жизненного цикла задачи. Так вы не будете забывать вовремя править тесты в случае изменения логики работы приложения, а также сэкономите время коллег на проверку.

Привет, с вами вновь Катя, Flutter Dev Friflex. Сегодня поговорим про библиотеку equatable, которая помогает упростить сравне
Привет, с вами вновь Катя, Flutter Dev Friflex. Сегодня поговорим про библиотеку equatable, которая помогает упростить сравнение объектов. Установка
Yaml
dependencies:
  equatable: ^2.0.7
Зачем нужна библиотека? В Dart по умолчанию два объекта считаются равными, только если они ссылаются на один и тот же экземпляр в памяти. Но часто требуется сравнивать объекты по их полям. Equatable автоматически реализует методы == (оператор равенства) и hashCode, что избавляет разработчиков от написания шаблонного кода. Без Equatable:
class Person {
  Person(this.name, this.age);

  final String name;
  final int age;
  
  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is Person && 
           other.name == name && 
           other.age == age;
  }

  @override
  int get hashCode => name.hashCode ^ age.hashCode;
}
С Equatable:
import 'package:equatable/equatable.dart';

class Person extends Equatable {
  Person(this.name, this.age);

  final String name;
  final int age;
  
  @override
  List<Object?> get props => [name, age]; // Поля для сравнения
}
Основные возможности ▫️Equatable переопределяет == и hashCode на основе списка полей (props) ▫️Можно использовать Equatable в иерархии классов ▫️Рекомендуется использовать с неизменяемыми (final) полями ▫️Часто применяется в State Management для эффективного сравнения состояний Использование EquatableMixin Если ваш класс уже наследуется от другого класса, но вам нужна функциональность Equatable, можно использовать EquatableMixin:
import 'package:equatable/equatable.dart';

class Person extends SomeOtherClass with EquatableMixin {
  Person(this.name, this.age);
 
  final String name;
  final int age;
  
  @override
  List<Object?> get props => [name, age];
}
Преимущества EquatableMixin ▫️Позволяет добавить сравнение объектов без изменения иерархии наследования ▫️Сохраняет все возможности Equatable, но в виде миксина Пример использования
void main() {
  final person1 = Person("Alice", 30);
  final person2 = Person("Alice", 30);

  print(person1 == person2); // true (без Equatable было бы false)
}
📎 Ссылка на библиотеку ❤️ — если используете Equatable в ваших проетах

Repost from Friflex Dev
🔥Статья нашей Flutter-разработчицы Кати — в шорт-листе «Технотекста» Хабра Норм или стрем использовать сторонние библиотеки
🔥Статья нашей Flutter-разработчицы Кати — в шорт-листе «Технотекста» Хабра Норм или стрем использовать сторонние библиотеки под любую проблему? А выносить виджеты в методы вместо создания отдельных классов? Ответы — в Катиной статье «Антитренды в мобильной разработке на Flutter». Она разобрала решения, которые кажутся удобными, а на деле раздражают и замедляют работу, и предложила, чем их стоит заменить. 🔜 Читать на Хабре 💜 Поддержать автора лайком

Привет! Это Роза, Flutter Dev Friflex! 👋 Когда я только начинала работать, почти не пользовалась горячими клавишами. У меня
Привет! Это Роза, Flutter Dev Friflex! 👋 Когда я только начинала работать, почти не пользовалась горячими клавишами. У меня была мышка. И этого вроде бы хватало, но со временем мне захотелось ускорить свою работу, поэтому я целенаправленно начала внедрять шорткаты в свою разработку. И это было лучшим решением! Хочу поделиться и с вами самыми полезными из них — вдруг пригодятся. 🔴Начнем с навигации и поиска: ⌘ + P — открыть файл по имени ⌘ + Shift + F — поиск по всему проекту ⌘ + G — перейти к строке ⌘ + Shift + O — перейти к функции или символу в файле 🔴Редактирование кода: Control + Space — автодополнение ⌘ + / — закомментировать или раскомментировать строку Option + ↑/↓ — переместить строку вверх или вниз Shift + Option + ↑/↓ — скопировать строку вверх или вниз ⌘ + Shift + K — удалить строку Shift + Option + F — отформатировать документ Option + Click — добавить множественный курсор ⌘ + Shift + L — выделить все вхождения слова ⌘ + F, затем ⌘ + Option + Enter — быстрая замена 🔴Мультикурсор и выделения: Option + Click — добавить курсор в точку клика ⌥⌘ + ↑/↓ — вставить курсор выше / ниже ⌘ + U — отменить последнее действие с курсором ⇧⌥ + I — вставить курсор в конец каждой выбранной строки ⌘ + L — выделить текущую строку ⇧⌘ + L — выделить все вхождения текущего выделения ⌘ + F2 — выделить все вхождения текущего слова Control + ⇧⌘ + →/← — расширить/уменьшить выделение      🔴Блочное выделение: ⇧⌥ + drag — выделение прямоугольником (box selection) ⇧⌥⌘ + ↑/↓/←/→ — выделение вверх/вниз/влево/вправо ⇧⌥⌘ + PgUp/PgDn — постраничное вертикальное выделение 💡Конечно, вы можете настроить сочетания клавиш под себя и увидеть весь их список:   Code → Settings → Keyboard Shortcuts или нажмите ⌘ + K, затем ⌘ + S. ✏️Также полный список есть тут: PDF от VSCode.

👾Фьють — и апреля нет! Но крутые посты остались. Топ-5 за этот месяц: 🔴Как интегрировать динамические ссылки во Flutter-приложение 🔴Как лучше организовать папки и файлы в проекте 🔴С чего начать создание своего собственного расширения 🔴Как создать свой проект 🔴Какой state-менеджмент выбрать для вашего проекта Готовим на май еще больше полезного контента. Пусть ваш код летает, а баги обходят стороной 🚀

Привет, это Анна, Friflex Flutter Team Lead!👋 Один из базовых приемов для повышения уровня безопасности вашего Flutter-прило
+1
Привет, это Анна, Friflex Flutter Team Lead!👋 Один из базовых приемов для повышения уровня безопасности вашего Flutter-приложения — это обфускация кода. Сегодня разберем, что это, как использовать и какие есть нюансы. Обфускация простыми словами — некоторое запутывание кода с помощью определенного набора символов. При обфускации код сборки становится нечитаемым для человека. Названия всех методов и классов подменяются другими символами. Какую проблему решает? Обфускация сильно затрудняет процесс понимания кода, полученного в процессе реверс-инжиниринга. Посмотрим, как это работает. Возьмем простое приложение, тот шаблон, который генерируется автоматически при создании Flutter-проекта. Соберем две сборки, одну с обфускацией, другую — без. Затем проведем процесс реверс-инжиниринга обеих сборок с помощью инструмента Blutter. Результат — на карточке👆 Вывод — код сборки с обфускацией прочитать почти невозможно. Обфусцировать код Flutter-приложения очень просто – достаточно запустить команду сборки следующим образом:
flutter build <build-target> \ 
   --obfuscate \ 
   --split-debug-info=/<symbols-directory>
Здесь очень важно использовать опцию --split-debug-info, она по указанному пути выгрузит символы обфускации, с помощью которых можно будет в дальнейшем расшифровывать важные логи, например, стектрейсы ошибок. ❕При обфускации нельзя в коде использовать сравнение строковых представлений runtime-типов. Попробуйте для примера сделать подобную реализацию в сборке с обфускацией и без.
class User {}

Text(user.runtimeType.toString());

Text ("${user.runtimeType.toString() == "User"");
Без обфускации на экране появятся значения User и true. С обфускацией в первом виджете будет набор рандомных символов, во втором — false. 🔥 — если используете обфускацию в своих проектах.

Привет, это Катя, Flutter Dev Friflex. При разработке на Flutter важно не просто уметь писать рабочий код, но и понимать, как
Привет, это Катя, Flutter Dev Friflex. При разработке на Flutter важно не просто уметь писать рабочий код, но и понимать, как работает язык Dart, на котором он основан. Одна из базовых тем — ключевые слова final, const и var. Они отвечают за то, как переменные создаются и ведут себя в процессе выполнения программы. Давайте разберем и повторим базу😁 var (переменная) Используется для объявления переменной, значение которой может изменяться со временем. Тип переменной определяется автоматически при присвоении значения. Но после первого присвоения типа, переменная не может быть использована с другим типом данных. Пример:
void main() {   
   var name = 'Alice';  // Тип определяется как String   
   print(name);  // Alice      
   name = 'Bob';  // Допустимо, так как тип остаётся String   
   print(name);  // Bob 
}
Если тип данных переменной var известен при создании, Dart сам выводит тип переменной (например, String, int и т.д.). Переменная, объявленная с var, может изменять свое значение, но не тип. Пример попытки изменить тип переменной:
var age = 30; age = "thirty";  // Ошибка: String нельзя присвоить переменной типа int.
​​ final (константа во время выполнения) Используется для переменных, значение которых можно установить только один раз. После инициализации переменной значение нельзя изменить. В отличие от const, переменная с final может быть инициализирована значением, которое становится известно только во время выполнения программы. Пример:
void main() {   
    final name = 'Charlie';  // name будет неизменяемым   
    print(name);  // Charlie    
    name = 'Dave';  // Ошибка, нельзя изменить значение переменной 
}
final используется для значений, которые известны только во время выполнения программы. Пример использования переменной, которая зависит от выполнения программы:
void main() {   
    final currentTime = DateTime.now();  // Значение определяется во время выполнения   
    print(currentTime); 
}
В этом примере время будет определено при выполнении программы и больше не изменится. const (константа времени компиляции) Это неизменяемая константа, значение которой должно быть известно на этапе компиляции. Это означает, что значения const должны быть определены заранее и не могут изменяться или вычисляться во время выполнения программы. Пример:
void main() {   
    const pi = 3.1415;   
    print(pi);  // 3.1415   
    pi = 3.14;  // Ошибка, нельзя изменить значение константы 
}
◽️Переменные, объявленные с const, являются неизменяемыми и создаются во время компиляции программы. ◽️Если переменная const имеет сложный тип (например, список), то она становится полностью неизменяемой (не только ссылка, но и сам объект). Пример со списком:
void main() {   
    const numbers = [1, 2, 3];   
    // numbers[0] = 10;  // Ошибка, элементы списка изменить нельзя
    print(numbers); 
}
Различия между final и const ▫️final позволяет установить значение переменной один раз, но это значение может быть вычислено во время выполнения программы. ▫️const требует, чтобы значение было известно на этапе компиляции. ▫️const можно использовать для создания неизменяемых объектов, которые становятся доступными еще до выполнения программы, в отличие от final. Пример разницы:
final currentTime = DateTime.now();  // Работает, так как значение вычисляется во время выполнения. 
const timeConst = DateTime.now();  // Ошибка: const переменные не могут быть вычислены во время выполнения.
Итоговое сравнение: ▪️var — изменяемая переменная, тип выводится автоматически. ▪️final — неизменяемая переменная, значение можно присвоить один раз, но это может произойти во время выполнения. ▪️const — неизменяемая переменная, значение которой должно быть известно во время компиляции.

Привет, это Роза, Flutter Dev Friflex, и я продолжаю серию постов про расширения DevTools. В прошлый раз рассказала, как встроить расширение прямо в существующий pub-пакет. Сегодня разберем, как взаимодействовать с кодом приложения извне и динамически выполнять Dart-код. Поговорим об EvalOnDartLibrary. EvalOnDartLibrary — это класс из пакета devtools_app_shared, который позволяет выполнять Dart-код прямо из вашего расширения DevTools.   Благодаря нему можно: ▫️управлять состоянием приложения ▫️вызывать методы ▫️получать значения из рантайма Как это возможно? 1. Инициализация
Future<void> initEval() async {
  await serviceManager.onServiceAvailable; // Убедимся, что vmService доступен

  _controllerEval = EvalOnDartLibrary(
    'package:some_package/src/controller.dart', // Путь к библиотеке, где находится нужный код
    serviceManager.service!,                   // Передаем vmService
    serviceManager: serviceManager,
  );

  evalDisposable = Disposable(); // Обязательно создаем Disposable
}
Пояснения: ◽️ 'package:...' — путь до нужного кода ◽️ serviceManager.service! — экземпляр VmService ◽️ Disposable — защищает от утечек памяти при закрытии DevTools 2. Выполнение кода EvalOnDartLibrary предоставляет три метода: ▪️asyncEval — асинхронное выполнение кода ▪️ eval — синхронное выполнение ▪️ evalInstance — получение экземпляра объекта (для дальнейшей работы с его полями и методами) ▪️ safeEval — безопасная обертка над eval, которая дополнительно обрабатывает ошибки выполнения      Ограничение: у eval и asyncEval есть лимит на размер возвращаемых данных. Рекомендуется: 1. Сначала вызвать метод через asyncEval или eval 2. Потом получить значение через evalInstance.
Future<String?> _getValue() async {
  await _controllerEval.asyncEval(
    'await SomeController.instance.calculateValue()', // Запуск асинхронной функции
    isAlive: evalDisposable,
  );

  final result = await _controllerEval.evalInstance(
    'SomeController.instance.sum.value', // Получение значения после выполнения
    isAlive: evalDisposable,
  ); // Результатом является объект типа Instanse

  return result?.valueAsString; // Возвращаем строковое значение
}

Не забывайте передавать isAlive: evalDisposable — без этого могут быть утечки памяти! Что такое Instance? Метод evalInstance возвращает Instance — ссылку на результат выполнения кода. Вы можете его преобразовать в  строку через valueAsString. Но убедитесь, что значение действительно можно привести к строке (иначе может быть ошибка).   Пример:
final result = await _controllerEval.evalInstance(
  'SomeController.instance.sum.value.toString()',
  isAlive: evalDisposable,
);

final value = result?.valueAsString;
Мини-памятка ✔️Используйте Disposable для управления жизненным циклом ✔️ Следите за ограничением на размер результата ✔️ Передавайте только валидные Dart-выражения ✔️ При изменениях в проекте обновляйте пути в EvalOnDartLibrary Для меня это был неплохой опыт с DevTools — я даже не знала, что так можно. А еще через serviceManager можно получить код приложения через id изолята (но там свои нюансы). На этом мы заканчиваем нашу мини-серию по расширениям DevTools! Я рассказала далеко не все, но теперь вам будет гораздо проще ориентироваться🚀 Подробнее о методах EvalOnDartLibrary — в комментариях👇

Проверка на внимательность. Какой ключ будете использовать для управления декларативной навигацией в приложении?
Anonymous voting

Всем привет! На связи Анна, Friflex Flutter Team Lead👋 🔑Как часто вы используете ключи виджетов в ваших приложениях? Сегодн
Всем привет! На связи Анна, Friflex Flutter Team Lead👋 🔑Как часто вы используете ключи виджетов в ваших приложениях? Сегодня поговорим, какие ключи бывают, чем отличаются и как могут быть полезны на практике. Каждый виджет во Flutter имеет nullable поле key. Сюда мы можем передавать различные имплементации класса Key. Под капотом у Flutter — дерево виджетов (Widget tree) и дерево элементов (Element tree). Здесь кратко разберемся в отличиях. Виджет можно описать как определенное визуальное представление объекта интерфейса. На экране может быть много его одинаковых экземпляров, они могут использоваться в разных местах. Каждое такое место характеризуется элементом. При этом виджеты могут меняться местами, а элементы остаются, лишь заменяя текущую связь на связь с другим виджетом. Чтобы глубже разобраться в том, как работает дерево виджетов и дерево элементов, рекомендую статью на Хабре. И тут возникает вполне понятный вопрос — при чем же тут ключи? Ключ создает связь конкретного виджета с элементом. С его помощью можно легко управлять рендерингом виджета, сохранять его состояние. Ключи бывают двух видов — глобальные и локальные. К глобальным относится GlobalKey, у локальных есть три вариации — ValueKey, ObjectKey и UniqueKey. Локальные ключи сохраняют состояние виджета на своем одном уровне дерева, только в рамках текущего контекста. ▫️UniqueKey — не принимает никакого значения, он уникален сам по себе. ▫️ValueKey и ObjectKey — уникальны за счет своего значения. Отличаются механизмом сравнения под капотом (ValueKey сравнивает значение value, а ObjectKey выполняет сравнение по ссылке). ▫️Глобальные ключи GlobalKey могут управлять состоянием по всему дереву, поэтому дают доступ к текущему контексту виджета. Перед добавлением ключа важно определиться с целью. Например, для управления декларативной навигацией в приложении отлично подойдет GlobalKey. А если идентифицировать виджеты нужно только в рамках одной страницы, на помощь придут локальные виджеты. Делитесь в комментариях своим опытом использования ключей🙌

Привет, это Катя, Flutter Dev Friflex. Во время разработки мобильных приложений иногда возникает необходимость выполнять команды терминала прямо из кода. Это может пригодиться, например, для автоматизации задач, работы с внешними CLI-инструментами или интеграции с системами сборки. Dart предоставляет способ выполнения таких команд через класс Process. 💡Что такое Process? Класс dart:io → Process позволяет запускать внешние процессы и взаимодействовать с ними: передавать аргументы, получать стандартный вывод (stdout), ошибки (stderr) и код завершения. Как использовать? Рассмотрим на примере, в котором будем выводить результат flutter --version в консоль.
import 'dart:io';

void main() async {
  final result = await Process.run('flutter', ['--version']);

  if (result.exitCode == 0) {
    print('Flutter version: ${result.stdout}');
  } else {
    print(Error: ${result.stderr}');
  }
}
Что происходит: 🔴Process.run — запускает процесс и возвращает результат после его завершения 🔴'flutter' — команда, которую мы хотим выполнить 🔴['--version'] — список аргументов для команды 🔴result.stdout — стандартный вывод 🔴result.stderr — ошибка 🔴result.exitCode — код завершения (0 — успех, иначе ошибка) Методы Process ➡️run(executable, arguments) — запускает процесс, дожидается его завершения и возвращает результат ➡️start(executable, arguments) — запускает процесс и возвращает объект Process, не дожидаясь завершения ➡️killPid(pid) — завершает любой процесс по его PID, даже если нет объекта Process ➡️kill() — завершает процесс, запущенный через start. ➡️getter exitCode — возвращает код выхода Важно помнить ✅Команда должна быть доступна в окружении (PATH) ✅Всегда оборачивайте вызовы в try-catch на случай ошибок ✅Будьте осторожны с пользовательским вводом: не передавайте его напрямую в Process без фильтрации Мой опыт Я использовала Process в проекте, где нужно было обрабатывать видео с помощью ffmpeg. Команды нарезки и склейки видео выполнялись прямо из Flutter-приложения, и Process стал отличным способом обернуть это взаимодействие с CLI. Вывод Process во Flutter (точнее, в Dart) — это мощный инструмент для расширения возможностей приложения: от автоматизации до взаимодействия с внешними утилитами. ❓Вы когда-нибудь использовали Process в Flutter или Dart? Для чего он вам понадобился?

Всем привет, это Роза, Flutter Dev Friflex! 👋 В прошлый раз мы обсудили, как можно создать собственное расширение для DevToo
Всем привет, это Роза, Flutter Dev Friflex! 👋   В прошлый раз мы обсудили, как можно создать собственное расширение для 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 не повлияет на размер конечного пользовательского приложения. 🔖Теперь вы знаете еще больше о создании расширений DevTools! В следующий раз я расскажу, как можно взаимодействовать со сторонним кодом с помощью Eval — до встречи.

Всем привет! С вами Анна, Friflex Flutter Team Lead. Любой Flutter-разработчик, создавая свой первый проект задавался хоть ра
Всем привет! С вами Анна, Friflex Flutter Team Lead. Любой 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-клиент для управления запросами. Но все эти пункты зависят от вашего проекта, поэтому в основную последовательность не включаем. Делитесь, каким было ваше первое приложение?

Привет, это Катя, Flutter Dev Friflex. Сейчас расскажу про три решения: Bloc, Riverpod и yx_scope, и еще немного про альтерна
Привет, это Катя, Flutter Dev Friflex. Сейчас расскажу про три решения: Bloc, Riverpod и yx_scope, и еще немного про альтернативные подходы. 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-менеджментов. Давайте обсудим!

Вторая попытка🤫 Какой state-менеджмент вы используете?
Anonymous voting

Привет, разработчики! Есть вопрос. Какой state-менеджмент используете?
Anonymous voting

Привет, это Роза, Flutter Dev Friflex 👋 Уверена, многие из вас знакомы с Dart DevTools и уже использовали его для анализа св
Привет, это Роза, 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.
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 используются. Это только верхушка айсберга. Но уже можно поэкспериментировать и начать знакомство с основными пакетами!

Привет, это Анна, Flutter Team Lead Friflex! Рано или поздно в жизни каждого Flutter-разработчика появляется необходимость ра
Привет, это Анна, Flutter Team Lead Friflex! Рано или поздно в жизни каждого 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-приложениях💬

Привет, это Юра Петров, руководитель отдела разработки Friflex👋 Хочу лично пригласить вас на конференцию по кроссплатформенн
Привет, это Юра Петров, руководитель отдела разработки Friflex👋 Хочу лично пригласить вас на конференцию по кроссплатформенной мобильной разработке, которая пройдет 11 апреля в Москве @omp_ru. Поговорим про Flutter, PWA и KMP — обо всем, что нужно знать, если вы в теме или хотите в нее влиться. Я расскажу, как мы портировали Flutter-приложения на ОС Аврора: покажу кейсы «Дикси», ЭНЕРГОГАРАНТ, idChess и «Мобильный агент». Продемонстрирую, как делить приложение на отдельные сервисы. Если вам интересно, как запускать приложения на ОС Аврора с помощью привычных инструментов — приходите! Где: Москва, пр-т Вернадского, 41, БЦ Академик, 4 этаж Когда: 11 апреля 2025 Регистрация: timepad Увидимся🙌

Привет, это Катя, Flutter Dev Friflex. Сегодня расскажу об организации файлов и папок в проекте. Здесь есть несколько основны
Привет, это Катя, Flutter Dev Friflex. Сегодня расскажу об организации файлов и папок в проекте. Здесь есть несколько основных подходов. Стандартная структура (по типам файлов)
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 уровня. ⚡️Разделяйте по ответственности, а не по типам, когда проект растет. Выбор структуры зависит от размера и сложности вашего проекта. Начинайте с простого и рефакторите по мере роста приложения. Главное — соблюдать консистентность и следить, чтобы структура оставалась понятной для всех разработчиков в команде. А какой подход используете вы?