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

🔗 Наш канал для разработчиков: @friflex_dev
🔗 Канал о продуктовой разработке: @friflex_product
Download Telegram
Всем привет! С вами Анна, Friflex Flutter Team Lead👋

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

Существуют три вида тестов:

🔸 Unit-тесты
Их основная задача —протестировать работоспособность какой-то одной конкретной функции, метода, класса по всем возможным сценариям

🔸 Widget-тесты
Этот тип тестов призван проверять именно отдельные виджеты. Widget-тесты позволяют проверить, действительно ли тот или иной виджет корректно располагается на экране и правильно себя ведет при взаимодействии с ним пользователя

🔸 Integration-тесты
Интеграционные тесты — самый сложный в настройке, но очень полезный тип тестов. Он позволяет проверить большие участки вашего приложения, протестировать взаимодействие разных модулей и виджетов между собой. Если юнит- и виджет-тесты призваны проверять работу отдельно только одного объекта в проекте, то интеграционные проверяет все в совокупности. Кроме этого, они также позволяют проверить производительность всего приложения.

Почему же отсутствие тестов является проблемой? Многие разработчики скажут, что написание тестов — лишняя трата времени, человеческих ресурсов и вообще занятие монотонное и скучное. Но здесь есть пара весомых доводов.

Когда проект маленький, простой, не нагружен сложной логикой, его ручное тестирование занимает немного времени и усилий QA-специалистов. Но с ростом приложения, с течением времени многие нюансы той или иной фичи забываются, их ручная проверка становится все менее эффективной.
Сюда же можно отнести и человеческий фактор — разработчики, тестировщики могут меняться, а знания — теряться.

Здесь на помощь приходят тесты в коде проекта. Стоит всего один раз при разработке того или иного функционала покрыть тестами всевозможные сценарии, и в будущем вероятность пропустить в продакшн сломанный функционал сильно снижается.

💡Совет: интегрируйте в свой CI/CD процесс проверки всех написанных тестов в проекте. Если тесты не проходят, сборке или пул-реквесту лучше не попадать на следующий этап жизненного цикла задачи. Так вы не будете забывать вовремя править тесты в случае изменения логики работы приложения, а также сэкономите время коллег на проверку.
Please open Telegram to view this post
VIEW IN TELEGRAM
6🔥5😍3❤‍🔥1👌1
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, это Роза, Flutter Dev Friflex! 👋

Рано или поздно в вашем проекте может возникнуть задача — получить скриншот конкретного виджета. Как это реализовать?

Существует несколько подходов:
▪️Готовое решение — например, пакет screenshot
▪️Более низкоуровневый подход — через RenderRepaintBoundary
    
💡Интересный факт: внутри себя screenshot использует RenderRepaintBoundary, поэтому понимание его работы будет полезным даже при использовании готового пакета.

В этом посте мы рассмотрим, как превратить любой виджет в изображение, готовое для ваших дальнейших действий.

1. Создаем глобальный ключ
Он нужен, чтобы получить доступ к RenderRepaintBoundary:

final GlobalKey _widgetKey = GlobalKey(); 


2. Оборачиваем нужный виджет в RepaintBoundary
Это гарантирует, что будет захвачен только нужный виджет.

RepaintBoundary(
  key: _widgetKey,
  child: const SomeWidget(),
)


3. Захватываем изображение
Метод, который преобразует виджет в Uint8List:
 
Future<void> _captureAndSave(BuildContext context) async {
  try {
    // Получаем рендер-границу по ключу
    final boundary = _widgetKey.currentContext!.findRenderObject() as RenderRepaintBoundary;

    // Захватываем изображение (можно увеличить pixelRatio для лучшего качества)
    final image = await boundary.toImage(pixelRatio: 3.0);

    // Преобразуем изображение в байты
    final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    final pngBytes = byteData!.buffer.asUint8List();

    // Тут можно сохранить файл.
    ...
  } catch (e) {
    debugPrint('Ошибка при захвате: $e');
  }
}


Вот и все! Теперь у нас есть изображение в виде байтов (Uint8List), с которым можно делать все, что угодно:
✔️сохранить в файл
✔️передать по сети
✔️вложить в PDF
✔️отобразить в UI

 🧠 Немного теории:
RepaintBoundary – важный инструмент для оптимизации производительности. Он создает «графическую границу», отделяя часть дерева виджетов от остального. Это позволяет:
▫️изолировать перерисовки
▫️ повысить производительность
▫️ использовать offscreen rendering
▫️ делать захват изображения (как в нашем примере)

Ставьте ❤️ — и в следующем посте глубже рассмотрим RepaintBoundary: как он работает,  влияет на рендеринг и какие у него есть особенности
15🔥3
Привет, с вами вновь Катя, Flutter Dev Friflex. Сегодня разберем основные способы декомпозиции и лучшие практики.

Декомпозиция виджетов — это процесс разделения сложного виджета на более простые и независимые компоненты.

Зачем это нужно?

▫️ Упрощение кода: когда разделяешь виджеты на более мелкие компоненты, код становится более понятным и легким для восприятия
▫️ Повторное использование: мелкие виджеты можно использовать в разных частях приложения, что снижает дублирование кода
▫️ Улучшение производительности: использование отдельных виджетов позволяет более эффективно управлять состоянием и перерисовкой интерфейса
▫️ Тестируемость: мелкие виджеты легче тестировать, что способствует созданию более надежного кода

Виды декомпозирования
1. Использование билд-методов
Вынесение частей виджета в отдельные методы внутри того же класса.

class ProductCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: [
          _buildTitle(),
          _buildPrice(),
        ],
      ),
    );
  }

  Widget _buildTitle() { ... }
  Widget _buildPrice() { ... }
}


Маленький плюсик:
Мало кода — не требует создания новых классов

Минусы:
▫️Нет переиспользуемости — методы привязаны к конкретному виджету
▫️Сложность тестирования — нельзя протестировать отдельно от родителя
▫️Риск создания «метод-супов» — чрезмерное количество методов ухудшает читаемость
▫️Ухудшает производительность — каждый setState в таком методе будет полностью перерисовывать виджет

2. Разделение на Stateful и Stateless виджеты
Выделение логики с состоянием в отдельные StatefulWidget, а статичной верстки — в StatelessWidget.

// Stateful часть
class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int count = 0;
  
  void increment() => setState(() => count++);

  @override
  Widget build(BuildContext context) {
    return CounterView(
      count: count,
      onIncrement: increment,
    );
  }
}

// Stateless часть
class CounterView extends StatelessWidget {
  // Логика
}

        
Плюсы:
▫️Четкое разделение ответственности — состояние отделено от представления
▫️Упрощенное тестирование — Stateless-часть можно тестировать без состояния
▫️Повышенная производительность — Stateless-виджеты не перестраиваются без необходимости
▫️Гибкость — можно менять состояние без изменения UI и наоборот

Минусы:
Больше кода — требуется создание дополнительных классов

Если заметили, то плюсы и минусы у этих вариантов противоположны.

📎Есть классное короткое видео от Flutter-команды, рекомендую посмотреть его.
👍76🔥1🐳1
Какой вид декомпозирования вы чаще всего используете?
Anonymous Poll
5%
Использование билд-методов
70%
Разделение на Stateful и Stateless виджеты
25%
Оба
Всем привет! Это Анна, Friflex Flutter Team Lead👋

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

Для начала разберемся, чем отличается документация от комментария в коде.

▫️Документация — это характеристика конкретного объекта, класса, метода, описание его назначения, параметров и механики работы. Оформляется в коде через ///. Например, возьмем школьную задачку — надо написать метод, который по значениям катетов прямоугольного треугольника будет рассчитывать значение гипотенузы.

/// Метод для вычисления гипотенузы по известным значениям
/// катетов прямоугольного треугольника
///
/// Принимает:
/// - [firstLeg] - первый катет прямоугольного треугольника
/// - [secondLeg] - второй катет прямоугольного треугольника
double calculateHypotenuse(double firstLeg, double secondLeg) {
final hypotenuse = sqrt(firstLeg * firstLeg + secondLeg * secondLeg);

return hypotenuse;
}


Если мы добавим этот пример в любую IDE, при наведении мышью на метод будет высвечиваться наша добавленная документация. При этом специальные знаки помогают удобно форматировать описание. Например, квадратные скобки позволяют обозначить параметр, а дефисы — организовать маркированный список.

▫️Комментарий — это обычное словесное описание алгоритма действий. Если в коде встречается сложный участок, где логика не совсем очевидна, или имеются какие-то важные сведения, которые разработчик хочет передать своим коллегам и себе в будущее, оформлять их стоит именно как комментарий. Они выделяются с помощью //.

Как показывает практика, и документация, и комметрирование кода — очень удобные инструменты, которые позволяют сделать проект максимально понятным и простым для вхождения новых разработчиков.

Так почему же тема холиварная?
В первую очередь потому что зачастую и документация, и комментарии в проекте не систематизированы, оформляются хаотично, чем только мешают и запутывают. Разработчики из-за этих проблем не видят в них никакой пользы, только лишь дополнительную трату времени.

Как это исправить?
Поможет соответствие ряду правил.

1. Зафиксировать в проекте порядок комментирования и документации кода.
Очень удобно на уровне всей команды заранее определить шаблоны. В будущем это позволит держать весь проект в едином формате.

2. Думать о том, как этот участок кода может восприниматься другими разработчиками или тобой через большой промежуток времени.
В больших проектах, которые разрабатываются и поддерживаются долгое время, не стоит полагаться на свою память или память коллег. Нюансы имеют свойство забываться, разработчики могут менять проекты и место работы, поэтому полезно фиксировать сложные места сразу.

3. Не комментировать лишнее.
Такая ошибка часто встречается, что вызывает негатив к комментированию в целом. Важно покрывать комментариями только то, что действительно нуждается в пояснении. Например, переменные с говорящими названиями или простые условия в блоке if описывать смысла нет.

4. Больше текста — не значит лучше.
Документация и комментарии должны быть емкими, простыми для восприятия. Если текста будет слишком много, вероятность, что его будут пропускать, увеличивается.

💬Делитесь своим опытом и лучшими практиками в комментариях.
🔥114❤‍🔥2👍2
This media is not supported in your browser
VIEW IN TELEGRAM
Все привет, с вами Роза, Flutter Dev Friflex💫

В прошлом посте я поделилась, как сделать скриншот экрана во Flutter, и упомянула RepaintBoundary. Сегодня расскажу о нем подробнее.

Что такое RepaintBoundary?
RepaintBoundary — это виджет, который помогает избежать лишних перерисовок. Он отделяет своего потомка в отдельный слой. Это значит, что если внутри происходит обновление, Flutter перерисует только эту часть, не затрагивая все дерево. Это важно для производительности и плавности интерфейса.

Как это работает?
Обычно если один виджет требует обновления, Flutter перерисовывает все поддерево, даже если остальные элементы не менялись. Это может замедлить работу приложения.

RepaintBoundary создает «границу», чтобы обновлялась только нужная часть. Все, что находится внутри него, будет рендериться отдельно и не затронет остальной интерфейс.

ListView.builder(
  itemCount: 20,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('Загрузка элемента $index'),
      trailing: RepaintBoundary(
        child: const SizedBox(
          width: 24,
          height: 24,
          child: CircularProgressIndicator(strokeWidth: 2),
        ),
      ),
    );
  },
)
// Мы изолируем `CircularProgressIndicator`, чтобы его анимация не вызывала перерисовку всей строки


💡 Кстати, ListView, GridView и другие scroll-виджеты уже автоматически добавляют RepaintBoundary к своим элементам, если они создаются через builder.

Где стоит использовать:
▫️Анимации и загрузки, чтобы они не затрагивали остальной интерфейс
▫️Списки с часто обновляемыми элементами
▫️Графики и визуализации с динамическими данными
▫️ Сложные экраны с множеством отдельных зон
 ▫️Глубокие деревья виджетов, где важна локальная оптимизация
    
Когда лучше не использовать:
▫️Простые статичные элементы — лишние границы только увеличат дерево
▫️Виджеты без состояния — они и так не нуждаются в перерисовке

💡Важно: RepaintBoundary действительно помогает улучшить производительность, но его чрезмерное использование может, наоборот, навредить. Каждый такой виджет увеличивает нагрузку на память и процессор.

Как отладить перерисовки?
Во Flutter есть инструмент, который показывает, какие части экрана обновляются чаще всего — debugRepaintRainbowEnabled.

import 'package:flutter/rendering.dart';

void main() {
  debugRepaintRainbowEnabled = true;
  runApp(MyApp());
}


С этим флагом перерисовываемые области будут мигать радужными цветами. Это помогает быстро найти «узкие места» в производительности.

📌 Рекомендую наглядный пример от Michael Lazebny тут.

❤️ — если пост был полезным
15🔥10👍31
Hola, это Сергей, Flutter Team Lead в Friflex🧑‍💻

21 мая захожу в этот канал с мини-рейдом — будем в лайве разбирать секцию Flutter на Google I/O 2025, надеюсь покажут что-то интересное:

🔴 Что там придумали с фреймворком
🔴 Что будет с Dart
🔴 И что-то еще... (но это не точно)

📍 Где? Здесь, в @flutterfriendly
📆 Когда? 21 мая, 21:00
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥98👌2
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, с вами вновь Катя, Flutter Dev Friflex. Сегодня разберем ключевые слова, которые помогают управлять видимостью и использованием библиотек, а также упрощают работу с импортированными компонентами.

▫️import
Используется для подключения внешних библиотек или файлов. С его помощью можно получить доступ к классам, функциям и переменным, определенным в других файлах.

import 'package:my_package/my_file.dart';


▫️export
Позволяет сделать доступными определенные элементы из одного файла для других файлов через единую точку подключения. Это полезно для создания библиотек и API.

export 'src/my_class.dart';


▫️as
Позволяет задать алиас (псевдоним) для импортированной библиотеки. Это помогает, когда импортируются несколько библиотек с одинаковыми именами.

import 'package:libraryA/my_class.dart' as libA; 
import 'package:libraryB/my_class.dart' as libB;  

void main() {   
    var a = libA.MyClass();   
    var b = libB.MyClass(); 
    }


▫️show
Позволяет импортировать только определенные элементы из библиотеки. Это помогает избежать конфликтов имен и уменьшить количество загружаемых элементов.

import 'package:my_package/my_file.dart' show MyClass, MyFunction;


▫️hide
Позволяет исключить определенные элементы из импорта. Это также помогает избежать конфликтов имен.

import 'package:my_package/my_file.dart' hide MyFunction;


▫️part of
Указывает, что текущий файл является частью другой библиотеки (основного файла). Это позволяет разбивать код на несколько файлов, но работать с ними, как с единой библиотекой.

// В основном файле (my_library.dart):
library my_library;

part 'part_file.dart';

// В part_file.dart:
part of 'my_library.dart';


🔥 — если используете все эти ключевые слова в своих проектах
🔥26👍41
Смотрим Google IO С:

Буду обновлять пост по мере новостей

Хороший дяденька менеджер рассказал, что Flutter #1 среди фреймворков для приложений

Целых 843 контрибьютора на GitHub за непонятно какой период :D

Рассказали про новые null операторы в Dart 3.8 и сокращение через точку в будущем релизе у енамов

Показали и рассказали про Property Editor (Интересно сколько людей будут им пользоваться :?)

Великолепные обновления паб дев в виде темной темы и счетчика загрузок

Рассказали про кросс компиляцию (Не упомянули аврору, немного растроен C: )

А ТЕПЕРЬ РЕАЛЬНО ПОЛЕЗНАЯ ВЕЩЬ: чуть-чуть ускорили dart cli

Грег рассказал про нативный интероп и объеденения платформеного и ui треда

Про то как они пытаются поддержать cupertino (им кто то пользуется ?:( ) пакет и введение суперэлипсов C:

Рассказали про поддержку рукописного ввода на android и поддержку Edge to Edge (которая будет работать только на самом последнем андройде 🙁)

Показали красивую циферку в 92% пакетов которые могут в webAssembly (не смотря на то, что 99% пакетов uiные C:)

ПОЛЕЗНАЯ ВЕЩЬ: hot-reload в вебе скрытое под фича флаг (до конца года) C:

Складывается ощущение что два последних Google I/O чисто реклама Gemini, впендюривают его везде (

Ну и так как больше нечего представить, то просто показывают юзкейсы:

- Flutter под webOS (ОС на теливизорах от LG)
- Canonical и вклад в flutter
- Флаттер на тостере !?!?
- Флаттер в спорт. инвентаре

Это был тролинг года, опять рассказывают про AI 😭


Было бы здорово еслиб они сказали, что Gemini умеет хорошо верстать по скриншотам или помогать в чем-то таком и как они его красиво натренировали, но нет, теперь вы можете просто кидать запросики в Google API из приложения (за которое вы будете платить), а так же писать бизнес логику
А по факту он полезен только в документации кода

Кратко содержание: Мы прикольные и нам помогают, AI,AI,AI, native Interop, ускорили dart cli, hot-realod в web, Юзкейс с тостером, AI, AI
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥145🥰4👏1🐳1
This media is not supported in your browser
VIEW IN TELEGRAM
Привет! На связи Анна, Flutter Team Lead😉

Совсем недавно мы говорили про тесты во Flutter-приложениях, разобрались в видах и ситуациях использования. Сегодня поговорим об одном из самых известных инструментов для тестирования — библиотеке mocktail.

Часто в процессе написания тестов появляется необходимость имитировать работу того или иного экземпляра класса с целью проверить его работу в разных сценариях. В таком случае на помощь придет mocktail. Разберемся подробнее в возможностях этой библиотеки.

✔️ Создание Mock и Fake
Mock и Fake — понятия, которые существуют не только в тестировании Flutter-приложений. Это так называемые тестовые двойники — классы, предназначенные для упрощения процесса тестирования и изоляции тестируемого объекта от внешних зависимостей.

Оба понятия довольно схожи, но имеются небольшие различия. Моки имитируют поведения реальных объектов, а фейки — копируют функциональность реального объекта, но с некоторым упрощением.

Подробнее понять сущность тестовых двойников поможет эта статья.

Библиотека mocktail дает доступ к Mock- и Fake-классам. Чтобы создать моки или фейки, достаточно наследоваться от них.

class FakeExample extends Fake implements Example {}
class MockExample extends Mock implements Example {}


✔️ Определение поведения метода — when()
Метод when() позволяет к тестовому объекту определить конкретное поведение.

class Example {
String read() => 'ready!';
String write(String data) => 'data recorded!';
}

final mockExample = MockExample();

test(
'testing mockExample.read()',
() {
when(mockExample.read).thenReturn('successfully read');
expect(mockExample.read(), 'successfully read!');
},
);


✔️ Проверка вызовов метода — verify()
Метод verify() позволяет проверить, был ли вообще вызов того или иного метода, с какими параметрами и сколько раз.

Например, так мы можем проверить, что в нашем примере метод read() был вызван 1 раз.

verify(mockExample.read).called(1);


А так можно получить список параметров, с которыми был вызван метод write().

final list = verify(() => mockExample.write(captureAny())).captured;


✔️ Сбросить все настроенные поведения — reset()
Тут все просто — если вам нужно убрать все те настройки поведени, которые были заданы ранее, поможет метод reset().

reset(mockExample);


Как показывает практика, mocktail являтся достаточно гибким инструментом. С его помощью можно протестировать даже сложные сценарии.

➡️ Документация библиотеки содержит еще много интересных примеров, рекомендую ознакомиться.
Please open Telegram to view this post
VIEW IN TELEGRAM
6🔥3❤‍🔥1
Привет, это Роза, Flutter Dev Friflex👋

Если ты хоть раз писал CLI-пакет на Dart, то точно задумывался, как обрабатывать аргументы в main().
Можно, конечно, написать свой парсер, но проще воспользоваться готовым решением, например, библиотекой args. Она превращает сырые аргументы из командной строки в понятный набор параметров и значений.

Как это работает?
Внутри args реализован простой парсер на основе Map, куда добавляются опции и флаги.  
При запуске команды с аргументами библиотека определяет, что передано, и возвращает готовую структуру — ArgResults.

Как работать с args?
Первым делом добавь зависимость в pubspec.yaml, затем — создай парсер:

final parser = ArgParser();


Также ты можешь задать дополнительные параметры при инициализации:
✔️ allowTrailingOptions (по умолчанию true) — позволяет указывать флаги и параметры после позиционных аргументов
✔️ usageLineLength — задает максимальную длину строки при выводе usage

Добавление параметров
🔹 Используй addOption, если параметр принимает значение
parser.addOption('output');


🔹 Используй addFlag, если параметр — просто переключатель
parser.addOption('env', defaultsTo: 'dev');


По умолчанию у флагов работает форма --no-flag. Если это не нужно, добавь negatable: false.

Дополнительные настройки
▫️Сокращения с помощью abbr:
parser.addFlag('screenshot', abbr: 's');


▫️ Значение по умолчанию:
parser.addOption('env', defaultsTo: 'dev');


Примеры использования
Команды с аргументами можно вызывать так:

dart run command_name --verbose=false
dart run command_name --output=somevalue


Если заданы сокращения, то можно:

dart run command_name -v=false
dart run command_name -o=somevalue


Валидация и обратные вызовы
▫️Хочешь ограничить допустимые значения? Используй allowed:

parser.addOption('mode', allowed: ['dev', 'prod']);


▫️ Если передано недопустимое значение — будет выброшено исключение ArgParserException
    
▫️ Для дополнительной логики можно также передать callback
    
Обязательные параметры
Если параметр помечен как mandatory: true, но не передан — при его извлечении Dart выбросит ArgumentError.

Вывод команд
Но и наконец, чтобы пользователи знали, какие аргументы доступны и как их использовать, добавь флаг --help и выведи справку через parser.usage:

parser.addFlag(
  'help',
  abbr: 'h',
  help: 'Show help information for available commands',
  negatable: false,
);

if (results['help'] as bool) {
  print('Command help:\n');
  print(parser.usage);
  exit(0);
}


📌 Рекомендую завершать выполнение программы после вывода справки, чтобы избежать лишнего кода, если help — единственный аргумент.

В посте рассмотрела не все возможности пакета: обязательно загляни в документацию на pub.dev, чтобы узнать больше.

❤️ — если пост был полезным
8👍4🔥3
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, это Катя, Flutter Dev Friflex👋

Сегодня разберемся что такое pubspec.yaml, зачем он нужен и что содержит.

Что это?
Файл pubspec.yaml — это настройка Flutter-приложения, без которого оно не соберется.

Что внутри этого файла?

1. Название и описание

Yaml
name: my_app  # Название приложения (только английскими буквами, без пробелов)
description: Моё первое приложение  # Описание (можно на любом языке)
version: 1.0.0+1  # Версия (1.0.0 — основная, +1 — номер сборки)


2. Какие версии Dart и Flutter нужны

Yaml
environment:
  sdk: ">=2.12.0 <3.0.0"  # Dart версии от 2.12.0 до 3.0.0
  flutter: ">=2.0.0"       # Flutter версии от 2.0.0


3. Библиотеки (дополнительные функции)

Yaml
dependencies:
  flutter:
    sdk: flutter  # Обязательная строка для Flutter-приложений
  http: ^0.13.3   # Библиотека для работы с интернетом
  provider: ^6.0.0  # Библиотека для управления состоянием приложения


После добавления библиотек нужно запустить flutter pub get, чтобы их скачать.

4. Библиотеки только для разработки

Yaml
dev_dependencies:
  flutter_test:
    sdk: flutter  # Нужно для тестирования
  build_runner: ^2.1.0  # Помогает генерировать код


5. Картинки, шрифты и другие файлы

Yaml
flutter:
  uses-material-design: true  # Включает стандартные иконки Google
  assets:
    - assets/images/  # Папка с картинками
    - assets/logo.png  # Конкретный файл
  fonts:
    - family: Roboto  # Шрифт Roboto
      fonts:
        - asset: assets/fonts/Roboto-Regular.ttf


Как обновлять библиотеки?

▫️Добавили новую библиотеку в pubspec.yaml?
✔️Запустите flutter pub get (чтобы скачать)

▫️Хотите обновить все библиотеки?
 ✔️Запустите flutter pub upgrade

▫️Хотите проверить, какие библиотеки устарели?
✔️Запустите flutter pub outdated

Важно:
▪️Если добавили картинку или шрифт, но забыли прописать его в pubspec.yaml — приложение не увидит этот файл
▪️Если указали не ту версию библиотеки — приложение может не запуститься

Этот файл — как инструкция для Flutter, где все должно быть четко прописано🚀
🔥117👍2
Привет, с вами Анна, Friflex Flutter Team Lead.

С каждым месяцем разработка и портирование Flutter-приложений на ОС Аврора становится все более популярным запросом у заказчиков. Этот повышенный спрос рождает у разработчиков все больше интереса к операционной системе и работе с ней.

На Youtube-канале Friflex
мой коллега Юрий Петров @mobile_developing выпустил серию видео, которые помогут вам подготовиться к разработке приложений для ОС Аврора, а также подскажут, как просто и быстро портировать Flutter-приложение. Особенно рекомендую видео:
▫️Полный гайд по установке и настройке Flutter Aurora
▫️Пример портирования Wonderous на Аврора.


Кто уже разрабатывал Flutter-приложения под ОС Аврора знают, что первым шагом необходимо добавить папку с нативным кодом, которая обеспечит поддержку системы. Для этого в терминале достаточно выполнить команду:

flutter-aurora create --platforms=aurora --org={название_организации} .


После успешного выполнения команды в корне проекта будет создана папка aurora. И сегодня мы подробно изучим ее содержимое.

▪️Папка desktop и файл .desktop
Файл .desktop является основной конфигурацией вашего приложения. Именно здесь вы можете менять название самого приложения, иконку, запрашиваемые разрешения. Выглядит содержимое файла следующим образом⬇️

Основные поля, которые могут вам потребоваться, пометила комментариями с описанием.

[Desktop Entry]
Type=Application
Name=Example // Название приложения (только на английском)
Name[ru]=Приложение-пример // Название приложения на русском
Comment=Мобильное приложение для сети магазинов Example. // Описание приложения
Icon=ru.example.mobile.aurora.example_mobile // Иконка приложения
Exec=/usr/bin/ru.example.mobile.aurora.example_mobile
X-Nemo-Application-Type=silica-qt5

[X-Application]
Permissions=DeviceInfo;Camera;Internet;UserDirs;SecureStorage // Запрашиваемые разрешения
OrganizationName=ru.example.mobile.aurora // Название организации, которое было указано при создании папки aurora
ApplicationName=example_mobile


Здесь важно обратить внимание на поле Permissions — именно сюда необходимо добавлять все разрешения, которые требует функционал вашего приложения. Все эти разрешения будут запрошены одномоментно при запуске приложения. Если пользователь не согласится предоставить к ним доступ, приложение не запустится.

▪️Папка flutter
Сюда автоматически генерируются файлы для подключения плагинов. Все сгенерированные здесь файлы не рекомендую менять вручную.

▪️Папка icons
Здесь хранятся иконки приложения в png-формате в 4-х разрешениях — 86x86, 108x108, 128x128, 172x172. Если вы хотите заменить иконку, вам достаточно заменить картинки-заглушки своими изображениями. Важно — разрешения и формат должны сохраниться.

▪️Папка rpm
Здесь хранится еще один важный файл конфигурации .spec. Этот файл нечто сродни всем нам знакомому pubspec.yaml. Здесь мы указываем основную информацию о приложении:

Name: ru.example.mobile.aurora.example_mobile // Название бандла
Summary: Мобильное приложение для сети магазинов Example // Описание продукта, которое будет отображено при запуске
Version: 1.0.1 // Версия приложения
Release: 20 // Версия сборки


Важно — версия приложения на Авроре не подтягивается из pubspec.yaml, как на Android или iOS. Ее требуется поднимать именно здесь.

Также в этом файле при подключении плагинов по необходимости можно указывать зависимости под ключом BuildRequires:

BuildRequires: pkgconfig(streamcamera)
BuildRequires: pkgconfig(sqlite3)
BuildRequires: pkgconfig(runtime-manager-qt5)


А вы уже сталкивались с разработкой приложений для ОС Аврора?
7🔥4👍2🥰1
Привет, это Роза, Flutter Dev Friflex! 👋

Представь ситуацию: тебе нужно выполнить определенный код только после того, как интерфейс полностью будет отрисован. Как быть? Конечно же, использовать addPostFrameCallback! Давайте разберем его.

Немного теории и фактов
▫️addPostFrameCallback – это метод класса WidgetsBinding, который наследуется от SchedulerBinding во Flutter
▫️ Он позволяет зарегистрировать функцию обратного вызова, которая появится после завершения отрисовки текущего кадра, но до начала следующего
▫️ Обратный вызов выполняется один раз и не может быть отменен
▫️ В качестве параметра обратный вызов получает Duration с указанием времени его вызова

Когда это может быть полезно?
▪️Показ диалогового окна после полной визуализации дерева виджетов
▪️Прокрутка к нужной позиции после построения UI
▪️Программная прокрутка к определенной позиции после построения интерфейса
▪️Запуск анимаций, зависящих от уже построенного UI

Ты можешь подумать: «Я же могу использовать StatefulWidget и реализовать все в initState без всяких addPostFrameCallback! В чем же польза?»

На самом деле, добавлять такую логику в initState — не самая лучшая идея. Вот почему:
▫️ initState вызывается до первой отрисовки виджета. Если ты попытаешься получить context или размеры виджетов в initState, ты можешь словить ошибку или просто получить нулевые значения, потому что виджеты еще не отрисованы и не имеют реальных размеров и позиций
▫️Если ты вызовешь setState() в initState, когда build() еще не закончился, ты получишь ошибку, похожую на > setState() or markNeedsBuild() called during build
▫️addPostFrameCallback, в отличие от этого, гарантирует, что все уже отрисовано и можно спокойно работать с контекстом

И осталось самое главное — разобраться, как использовать. Представим, нам нужно показать уведомление после того, как экран будет полностью отрисован.

class _SomeScreenState extends State<SomeScreen> {
  @override
  void initState() {
    super.initState();

    // Показываем snackbar после полной отрисовки интерфейса
    WidgetsBinding.instance.addPostFrameCallback((_) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Some text')),
      );
    });
  }

  // build method


На заметку:
✔️ Не используй addPostFrameCallback для тяжелых операций — они могут замедлить отрисовку следующего кадра
✔️ Если тебе нужно выполнять действия на каждом кадре, то addPostFrameCallback не подойдет
✔️ А если просто нужно отложить выполнение кода без привязки к UI — можно использовать Future.delayed или Future.microtask.

На этом все! А сейчас предлагаю выполнить addPostFrameCallback по версии Telegram и поставить ❤️
17👍6🔥3👌1
Привет, это Катя, Flutter Dev Friflex. Сегодня обсудим AutomaticKeepAliveClientMixin.

Когда создаем приложение с вкладками или списками, может возникнуть такая ситуация: пользователь переключается между вкладками, а состояние старой вкладки сбрасывается. Такое может раздражать, особенно если это список, который прокрутили до 155 номера, видео или текст.

Проблема
Допустим, у нас есть 3 вкладки, и каждая из них — это StatefulWidget с вложенными виджетами. Когда мы перелистываем между ними, Flutter по умолчанию «выгружает» неактивную вкладку, чтобы экономить память.

Что происходит:
Проскроллили вниз на 1-й вкладке ➡️ переключились на 2-ю ➡️
вернулись обратно на 1-ю ➡️
все обнулилось — нас кинуло в самое начало.

Решение
AutomaticKeepAliveClientMixin — это миксин, который говорит Flutter: «Пожалуйста, не выгружай меня, даже если я временно не на экране». Он работает вместе с PageStorage — специальным механизмом Flutter для хранения состояний виджетов.

Как использовать
На коленке наклепали такой виджет, который каждый раз будет сбрасывать свое состояние:

class MyTab extends StatefulWidget {
  @override
  _MyTabState createState() => _MyTabState();
}

class _MyTabState extends State<MyTab> {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 100,
      itemBuilder: (_, index) => ListTile(title: Text('Item $index')),
    );
  }
}


Теперь добавляем AutomaticKeepAliveClientMixin, чтобы этого не происходило:

class _MyTabState extends State<MyTab> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true; // СУПЕР ВАЖНО

  @override
  Widget build(BuildContext context) {
    super.build(context); // ОБЯЗАТЕЛЬНО ВЫЗЫВАТЬ
    return ListView.builder(
      itemCount: 100,
      itemBuilder: (_, index) => ListTile(title: Text('Item $index')),
    );
  }
}


Миксин работает только в тех местах, где Flutter может использовать KeepAlive, например, в TabBarView, PageView, ListView.builder, внутри PageStorage.

Где использовать
▫️
Вкладки (TabBarView), чтобы запоминать положение и состояние
▫️Страницы в PageView, например, в onboarding-экранах
▫️Динамические списки, где хотим сохранить scroll-положение и состояние каждого элемента

Как быстро проверить, что KeepAlive работает
Работает, если initState called будет вызван только один раз:

@override
void initState() {
  super.initState();
  print('initState called'); 
}


📎Документы по миксину — здесь

🔥 — если использовали AutomaticKeepAliveClientMixin на практике
👍176🔥5
Всем привет! Это Анна, Friflex Flutter Team Lead👋

Каждый Flutter-разработчик в своей работе хотя бы раз сталкивался с необходимостью получить какую-то информацию из платформы или использовать определенный ее функционал. И чаще всего для таких целей используются уже готовые решения в виде плагинов. Сегодня же мы поговорим о том, как настроить вручную общение с платформой, и разберем на примере того, как это делают плагины.

Основной механизм общения нативного и Dart-кода во Flutter-приложениях — Platform Channels. Это общий термин для названия всех видов каналов.

Есть три вида платформенных каналов:
▫️ MethodChannel — канал для одноразовых асинхронных вызовов методов платформы. Работает по принципу запрос-ответ. Через MethodChannel методом invokeMethod Flutter делает запрос в платформу, которая затем возвращает некоторый результат
▫️ EventChannel — канал для непрерывного общения с платформой. Работает в формате потоков событий от платформы во Flutter
▫️ BasicMessageChannel — канал для простого асинхронного обмена сообщениями с платформой

При создании каналов каждому необходимо задавать свой уникальный строковый идентификатор. С помощью него платформа и Flutter «стыкуются» — образуется некий мост, по которому идет передача данных.

На примере всеми известного плагина permission_handler рассмотрим, как настраивается общение с платформой.

В Dart-коде создается экземпляр MethodChannel.

const MethodChannel _methodChannel = MethodChannel('flutter.baseflow.com/permissions/methods');

Затем этот канал используется в методах для запроса платформенных данных. Например, метод checkPermissionStatus запрашивает статус текущего разрешения.

 @override
Future<PermissionStatus> checkPermissionStatus(Permission permission) async {
final status = await _methodChannel.invokeMethod(
'checkPermissionStatus', permission.value);

return decodePermissionStatus(status);
}


В нативном коде Android тоже создается экземпляр канала, к нему подключается обработчик вызовов.

private void startListening(Context applicationContext, BinaryMessenger messenger) {
methodChannel = new MethodChannel(
messenger,
"flutter.baseflow.com/permissions/methods");

methodCallHandler = new MethodCallHandlerImpl(
applicationContext,
new AppSettingsManager(),
this.permissionManager,
new ServiceManager()
);

methodChannel.setMethodCallHandler(methodCallHandler);
}


В самом нативном обработчике уже задается обработка вызываемых методов. Например, так обрабатывается запрос checkPermissionStatus

@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) {
switch (call.method) {
...
case "checkPermissionStatus": {
@PermissionConstants.PermissionGroup final int permission = Integer.parseInt(call.arguments.toString());
permissionManager.checkPermissionStatus(
permission,
result::success);
break;
}
...
}
}


❤️ — если пост был полезен
13🔥2🤯1
Какой тип Platform Channel вам приходится использовать чаще всего?
Anonymous Poll
91%
MethodChannel
7%
EventChannel
1%
BasicMessageChannel
Привет, это Роза, Flutter Dev Friflex! 👋

В любой разработке рано или поздно придется столкнуться с такими аббревиатурами, как KISS, DRY и YAGNI. Чтобы в следующий раз точно знать, что они означают и как их расшифровывать, давай разберемся, что к чему.

Зачем все это?
Ты нарушаешь базовые архитектурные принципы, если в твоем коде:
▫️Повторяются фрагменты — одна и та же логика дублируется в нескольких местах
▫️Сложная логика — код трудно читать и поддерживать
▫️Есть «код на будущее» — ты добавляешь функции и классы, которые сейчас не нужны, но «вдруг пригодятся»

Такой код быстро превращается в ад, и его сложно поддерживать. Именно для этого и существуют три основных правила:
☑️ KISS (Keep It Simple, Stupid) — делай проще
☑️ DRY (Don't Repeat Yourself) — не повторяйся
☑️ YAGNI (You Aren’t Gonna Need It) — тебе это не понадобится

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

KISS: Keep It Simple, Stupid — Будь проще!
Принцип KISS говорит: не усложняй! Простые решения всегда выигрывают, потому что их легче поддерживать. Чем проще код, тем легче его читать, понимать и изменять в будущем. Сложные архитектуры, перегруженные конструкции и «гениальные» решения часто оборачиваются проблемами.

В чем суть KISS:
▫️ Ясность — код должен быть понятным с первого взгляда
▫️ Эффективность — никаких избыточных решений
▫️ Гибкость — простые вещи проще адаптировать
▫️ Удобство — такой код приятно читать и трогать
▫️ Меньше багов — чем проще, тем меньше мест для ошибок

KISS — это стремление найти простое, но рабочее решение. Не примитивное, а минималистичное и логичное.

DRY: Don't Repeat Yourself — Не повторяйся!

DRY — это про избавление от дублирования. Если один и тот же кусок кода повторяется — его нужно вынести в отдельную функцию, класс или модуль. Это не только уменьшает размер кода, но и ускоряет поддержку: меняешь в одном месте — все работает.

Как применять DRY:
▫️ Выделяй повторяющуюся логику в переиспользуемые блоки
▫️ Избегай копипаста — он приводит к ошибкам
▫️ Держи один источник истины для каждой логики или сущности
   
DRY помогает держать код чистым, логичным и легко изменяемым.

YAGNI: You Aren’t Gonna Need It — Тебе это не понадобится!

YAGNI напоминает: не пиши код «на будущее», если в нем нет реальной необходимости прямо сейчас. То, что может вдруг пригодиться, скорее всего не пригодится вовсе. А если и пригодится, то в другой форме.

Что говорит YAGNI:
▫️ Делай только то, что нужно прямо сейчас
▫️ Не добавляй фичи «на будущее», если на них нет задачи
▫️ Сфокусируйся на реальных требованиях, а не на гипотетических сценариях
    
Чем меньше «запасного» кода — тем чище проект, и ,соответственно, меньше багов сейчас и в будущем.

Если ты уже пользуешься этими принципами, ставь ❤️
13🔥41🥴1
Привет, это Катя, Flutter Dev Friflex. Сегодня расскажу про архитектурный подход MVP — один из способов организации кода в приложениях.

Что такое MVP
MVP расшифровывается как Model — View — Presenter и разделяет логику приложения на три компонента:

🔴Model (Модель) — отвечает за данные и бизнес-логику. Она получает или сохраняет информацию, например, из API или локальной базы данных
🔴View (Представление) — пассивный слой, отображающий данные на экране. View не содержит логики — она лишь сообщает презентеру о действиях пользователя и отображает то, что пришло от него
🔴Presenter (Презентер) — посредник между моделью и представлением. Он обрабатывает пользовательские действия, запрашивает данные из модели, форматирует их и передает обратно в View

Поток коммуникации
➡️Пользователь взаимодействует с View (например, нажимает кнопку)
➡️View передает событие презентеру
➡️Презентер запрашивает или обновляет данные в модели
➡️Получив данные, презентер подготавливает их и отправляет обратно во View для отображения

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

Пример
Представим экран входа в приложение:
🔸View: показывает два текстовых поля (логин и пароль) и кнопку «Войти». Передает ввод пользователю презентеру при нажатии на кнопку
🔸Presenter: получает логин и пароль, проверяет их (например, что они не пустые), отправляет их в модель
🔸Model: выполняет запрос к серверу, возвращает результат (успешный вход или ошибка)
🔸Presenter: получает результат, сообщает View показать следующий экран или отобразить сообщение об ошибке

Почему это удобно?
🔴Презентер содержит бизнес-логику и отделен от UI
🔴View можно переиспользовать или подменять, не затрагивая логику
🔴Легко писать юнит-тесты, особенно для презентера и модели
🔴Каждый View связан с одним презентером (но при необходимости может использовать несколько)

На блок-схеме можно посмотреть, как компоненты взаимодействуют между собой⬆️
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥62👎1
Привет! С вами вновь Анна, Friflex Flutter Team Lead👋

Flutter-проекты поддерживают три основных режима сборки — debug, profile и release. Сегодня поговорим о каждом из них и разберемся, чем они отличаются и когда их лучше использовать.

◾️Debug-режим
Дословно переводится как «режим отладки». Этот режим самый популярный в работе разработчиков.

Именно debug-режим позволяет запустить приложение на симуляторах, эмуляторах и физических устройствах под полным контролем выполнения кода. В нем легко можно отслеживать значения переменных, останавливать выполнение по breakpoints (точкам останова).

Кроме этого, режим отладки поддерживает hot reload, что позволяет вам без пересборки приложения протестировать измененный код.
Еще одна очень удобная способность debug-режима — вы можете в рантайме подключать дополнительные инструменты, такие как DevTools и Flutter inspector.

◾️Profile-режим
Этот режим — что-то среднее между debug- и release-режимами. Визуально на физическом устройстве profile сборка не особо отличается от release, но в ней сохраняется возможность подключить некоторые иструменты, например, DevTools.

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

Если ваше приложение работает медленно, требуется проверить и оптимизировать сложные анимации, сборка в режиме profile — ваш лучший помощник.

Но здесь важно помнить, что profile-режим не поддерживается эмуляторами и симуляторами. Такие сборки стоит запускать на физических устройствах, чтобы отследить реальную производительность.

◾️Release-режим
Это режим выпуска. Сборки именно в этом режиме загружаются в сторы, поступают конечному пользователю и передаются тестировщикам, если не было запроса на другой режим.

Сборка в release-режиме максимально оптимизирована, в отличие от debug- и profile-сборок. Подключить дополнительные инструменты к такой сборке невозможно, вся отладочная информация полностью очищается, прервать выполнение программы нельзя.

Как запустить сборку в разных режимах?
Все очень просто! Стандартная команда flutter run по умолчанию запускает сборку в debug-режиме. Чтобы запустить в profile-режиме, стоит добавить опцию --profile, в release-режиме — --release.

flutter run 
flutter run --profile
flutter run --release


📎Еще больше деталей — в официальной документации Flutter.
8🔥5❤‍🔥1