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

🔗 Наш канал для разработчиков: @friflex_dev
🔗 Канал о продуктовой разработке: @friflex_product
Download Telegram
Какая аннотация запрещает наследование класса за пределами его библиотеки?
Final Results
54%
4
This media is not supported in your browser
VIEW IN TELEGRAM
🔗Всем привет, это Роза, Flutter dev Friflex

Пока я готовлю пост про шейдеры, давайте обсудим, как можно создать свой CLI-пакет на Dart. Мы сталкиваемся с CLI каждый день, зачастую даже не задумываясь об этом. Когда вы вводите flutter doctor, чтобы проверить окружение, или запускаете firebase init, чтобы создать проект Firebase, вы взаимодействуете именно с CLI.

CLI (Command Line Interface) — это интерфейс взаимодействия с приложением через консоль: вы передаете команды и аргументы, а приложение что-то выполняет. Если сильно упростить, CLI — это возможность ввести в консоли:

mytool login


И программа выполнит какое-то действие, не открывая UI. 
 
Создать свой CLI на Dart довольно просто. Для этого нужно выполнить команду:

dart create -t console-full my_cli_tool
cd my_cli_tool


После генерации у вас появится базовая структура проекта:

/bin
  my_cli_tool.dart  <-- точка входа
/lib
  ...


Все, что находится в папке bin/, является входной точкой нашего CLI.

Когда структура готова, можно добавить команды. Для обработки аргументов и команд идеально подходит пакет args. Кстати, про эту библиотеку вы можете подробнее прочитать в этом посте.

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

class LoginCommand extends Command {
  @override
  final name = 'login'; // название команды

  @override
  final description = 'Login to service'; // описание, показывается при -h

  @override
  Future<void> run() async {
    // ... логика авторизации
  }
}


Для взаимодействия с терминалом используем потоки: stdout, stderr и stdin.
✔️ stdout — вывод обычной информации в терминал (сообщения, результаты)
✔️ stderr — поток ошибок (то, что пользователю важно видеть, если что-то пошло не так)
✔️ stdin — ввод данных пользователем. Например, stdin.readLineSync() просто ждет, пока пользователь нажмет Enter, и возвращает введенный текст

stdout.write('📧 Enter your email: ');
final email = stdin.readLineSync();


Для более удобной работы с интерактивным вводом (например, чтобы скрыть ввод пароля или добавить выбор стрелками) можно использовать библиотеку dcli.
    
mixin CliMixin {
  String askStringField(
    String prompt, {
    String? defaultValue,
    bool required = true,
    bool hidden = false,
  }) {
    final result = ask(
      prompt,
      hidden: hidden,
      defaultValue: defaultValue,
      required: required,
      validator: const NotEmptyValidator(),
    );

    return result;
  }

  String askSelectField(
    String prompt,
    List<String> options, {
    String? defaultValue,
  }) {
    final selected = menu(
      prompt,
      options: options,
      defaultOption: defaultValue,
    );

    return selected;
  }
}


После успешного логина данные можно сохранить на диск (например, в ~/.my_cli/config.json). В следующих командах CLI автоматически поймет, что пользователь уже авторизован.

И вот наступает самый приятный момент: CLI готов, его можно сделать глобальной системной командой. Для этого в файле pubspec.yaml нужно добавить секцию executables:

executables:
  mytool: my_cli_tool


После этого выполняем:

dart pub global activate --source path 


Теперь в любой директории можно набрать:

mytool login


и команда запустится, будто это встроенная системная утилита. 

Если вы хотите пойти дальше, можно собрать бинарник под macOS, Linux или Windows:

dart compile exe bin/my_cli_tool.dart -o mytool


Таким образом, CLI становится полноценным инструментом, который можно использовать на любых платформах и с приложениями на разных языках программирования: его можно публиковать на pub.dev, подключить через Homebrew или распространять внутри команды.

❤️ — если нужно продолжение про публикацию CLI в Homebrew и сборку бинарников
Please open Telegram to view this post
VIEW IN TELEGRAM
18🔥4👍1
📍Привет! С вами Анна, Friflex Flutter Team Lead.

Поговорим сегодня про assert в Dart. Разберемся, что же это такое, как работает и почему он точно будет вам полезен.

Assert — это оператор в языке Dart для проверки утверждений в процессе отладки вашего кода. Он помогает разработчику проверить, что определенные условия выполняются корректно. В случае, если это не так, программа выдает ошибку AssertionError.

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

assert(<условие>, '<сообщение об ошибке>');


Применять assert можно как в методах, так и в конструкторах для проверки входных параметров.

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

void setProductsAmount(int amount) {
assert(amount >= 0, 'Количество товаров не может быть отрицательным!');
print('Количество товаров установлено: $amount');
}

void main() {
setProductsAmount(25); // Код выполняется без ошибок
setProductsAmount(-5); // Ошибка в debug режиме: Количество товаров не может быть отрицательным!
}


Возьмем еще один более сложный пример. Нам нужно создать класс данных карточки. Карточки могут быть двух типов — простая и с изображением. При этом карточка с изображением обязательно должна получить ссылку на него. Чтобы ограничить создание карточки с картинкой без ссылки на изображение, достаточно в конструктор добавить соответствующий assert.

enum CardType { simple, withImage }

class Card {
const Card({
required this.type,
required this.title,
required this.subtitle,
this.imageUrl,
}) : assert(
type != CardType.withImage || imageUrl != null,
'Для карточки типа withImage необходимо указать imageUrl!',
);

final CardType type;
final String title;
final String subtitle;
final String? imageUrl;
}

void main() {
try {
final card = Card(
type: CardType.withImage,
title: 'Карточка с картинкой',
subtitle: 'Это карточка с картинкой',
);
print('Карточка с картинкой успешно создана!');
} catch (error) {
print(error);
}
// Этот код выведет в консоль сообщение об ошибке - Для карточки типа withImage необходимо указать imageUrl!
}


Здесь важно учесть, что assert работают только в debug режиме, поэтому валидировать с помощью них действия пользователя или внешние данные нельзя. Их можно и нужно применять в тех случаях, в которых сам разработчик может допустить ошибку на этапе выполнения задачи.

Еще больше полезного можно найти здесь:
◾️ Assert Statements in Dart
◾️ Error handling, Asserts

❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
12🔥7🥰2
💭Всем привет, меня зовут Роза, Flutter Dev Friflex

Продолжаю серию постов про создание CLI-инструментов на Dart. В прошлый раз мы разобрались, что Dart позволяет упаковать CLI в нативный бинарный файл, и пользователю не нужно устанавливать Dart SDK, чтобы пользоваться утилитой — достаточно скачать файл и запустить его.

Но этот процесс можно улучшить!🚀

Мы можем добавить установку CLI через Homebrew, чтобы пользователи могли установить ваш инструмент одной командой:

brew install mytool


Давайте разберемся, как это сделать.

Если вы хотя бы раз набирали brew install, значит, вы уже пользовались Homebrew. Это менеджер пакетов для macOS и Linux. Но мало кто знает, что для распространения своего CLI не обязательно попадать в официальный Homebrew-core.

Достаточно создать Homebrew Tap — обычный GitHub-репозиторий с формулой Homebrew. Это своего рода ваш собственный mini-магазин пакетов для brew, доступных по команде:

brew tap yourname/yourtap
brew install mytool


Tap удобен, когда:
✔️ вы делаете внутренний CLI в компании
✔️ часто обновляете инструмент
✔️ хотите распространять бинарники сразу после релиза
✔️ не хотите проходить ревью в Homebrew-core

А теперь давайте разберемся на практике. На самом деле все сводится к созданию двух репозиториев на GitHub и одной формуле на Ruby.

Шаг 1. Создайте репозиторий с вашим инструментом.
Если это Bash-скрипт — убедитесь, что он исполняемый:

#!/usr/bin/env bash
chmod +x mytool.sh


Если вы делаете CLI на Dart, собираем бинарники:

mkdir -p dist
dart compile exe bin/my_cli_tool.dart -o dist/mytool-macos
dart compile exe bin/my_cli_tool.dart -o dist/mytool-linux


Затем создайте GitHub Release и загрузите бинарники (mytool-macos, mytool-linux) как assets. Homebrew будет скачивать их оттуда.

Шаг 2. Создаем Homebrew Tap. Для этого необходимо создать отдельный репозиторий:

myusername/homebrew-mytool


Внутри создайте папку Formula/ и файл Formula/mytool.rb:

class Mytool < Formula
  desc "CLI tool for automation"
  homepage "https://github.com/myusername/my_cli_tool"
  version "1.0.0"

  if OS.mac?
    url "https://github.com/myusername/my_cli_tool/releases/download/v1.0.0/mytool-macos"
    sha256 "SHA256_OF_MACOS_FILE"
  elsif OS.linux?
    url "https://github.com/myusername/my_cli_tool/releases/download/v1.0.0/mytool-linux"
    sha256 "SHA256_OF_LINUX_FILE"
  end

  def install
    bin.install Dir["mytool-*"].first => "mytool"
  end
end


SHA256 можно получить командой:

shasum -a 256 dist/mytool-macos


Этот хеш гарантирует, что Homebrew загружает именно тот файл, который вы опубликовали, и что он не был поврежден или подменен.

И на этом все! Теперь можно подключить tap и установить CLI:

brew tap myusername/mytool
brew install mytool


Важные моменты:
▪️SHA256 должен совпадать: Homebrew проверяет целостность файлов, поэтому после каждого релиза обязательно пересчитывайте SHA
▪️Выпуск обновлений: для новой версии создайте тег, пересоберите бинарники, обновите SHA и отредактируйте формулу в tap
▪️ Проверяйте локально: перед публикацией убедитесь, что установка работает:

brew install --build-from-source ./Formula/mytool.rb


▪️ Репозиторий должен начинаться с homebrew-, а имя формулы совпадать с названием CLI
▪️ Tap должен быть публичным. Homebrew не умеет устанавливать пакеты из приватных репозиториев

На этом все — ваш CLI готов к установке через brew🌟
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥92👍1
This media is not supported in your browser
VIEW IN TELEGRAM
💬Привет, с вами Катя, Flutter Dev Friflex

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

SOLID — это аббревиатура из пяти принципов объектно-ориентированного программирования. Разберем каждую букву отдельно, начиная с S (Single Responsibility Principle)

Что такое Single Responsibility Principle
Принцип единственной ответственности гласит: каждый класс должен иметь только одну причину для изменения. Проще говоря — один класс решает одну задачу.

Применение к классам

Нарушение SRP


class UserManager {
  // Работа с БД
  User getUser(String id) => User(id: id, name: 'John');
  void saveUser(User user) { /* сохранение */ }
  
  // Валидация
  bool validateEmail(String email) => email.contains('@');
  
  // Уведомления
  void sendWelcomeEmail(User user) { /* отправка */ }
  
  // Отчеты
  String generateReport(User user) => 'Отчет: ${user.name}';
}


Класс выполняет четыре разные задачи: работа с БД, валидация, уведомления и отчеты. У него слишком много причин для изменения.

Правильное применение SRP

// Работа с базой данных
class UserRepository {
  User getUser(String id) => User(id: id, name: 'John');
  void saveUser(User user) { /* сохранение */ }
  void deleteUser(String id) { /* удаление */ }
}

// Валидация данных
class UserValidator {
  bool validateEmail(String email) {}
  
  bool validatePassword(String password) {}
}

// Отправка уведомлений
class NotificationService {
  void sendWelcomeEmail(User user) {}
}

// Генерация отчетов
class ReportGenerator {
  String generateUserReport(User user) {}
}


Теперь каждый класс имеет только одну ответственность и одну причину для изменения.

Применение к методам
Принцип работает и на уровне методов — каждый метод должен
выполнять одну задачу.

Нарушение на уровне метода

void processOrder(Order order) {
  // Валидация
  if (order.items.isEmpty) throw Exception('Корзина пуста');
  
  // Расчет
  double total = 0;
  for (var item in order.items) {
    total += item.price * item.quantity;
  }
  
  // Сохранение
  database.save(order);
  
  // Уведомление
  emailService.send(order.userEmail, 'Заказ оформлен');
}


Правильное применение

class OrderService {
  final OrderValidator validator;
  final PriceCalculator calculator;
  final OrderRepository repository;
  final NotificationService notifications;
  
  void processOrder(Order order) {
    _validateOrder(order);
    final total = _calculateTotal(order);
    _saveOrder(order, total);
    _notifyUser(order);
  }
  
  void _validateOrder(Order order) => validator.validate(order);
  double _calculateTotal(Order order) => calculator.calculate(order);
  void _saveOrder(Order order, double total) => repository.save(order);
  void _notifyUser(Order order) => notifications.send(order);
}


Каждый метод выполняет одну конкретную задачу.

Преимущества SRP
✔️
Упрощение тестирования — каждый класс тестируется изолированно​
✔️Легкость поддержки — изменения в одной части не влияют на другие​
✔️Лучшая читаемость — код становится понятнее​
✔️Переиспользование — специализированные классы легче использовать повторно

В следующих постах я расскажу о других принципах SOLID.

🔄Делитесь в комментариях своими примерами применения SOLID в ваших проектах! Интересно узнать, с какими сложностями вы сталкивались и как принципы SOLID помогли их решить
Please open Telegram to view this post
VIEW IN TELEGRAM
👍176🔥5
🔴Привет! Это Анна, Flutter Team Lead Friflex

Роза уже рассказывала о модификаторах классов. Сегодня поговорим о еще одной базовой базе для любого Flutter-разработчика — модификаторах переменных в Dart.

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

var
Этот модификатор является одним из наиболее часто используемых. Он заставляет переменную получить и зафиксировать тип данных при первом присвоении.

var data = 'Привет! Это строка'; // в момент присвоения data получит тип      String
data = 'А теперь это другая строка!'; // поменять значение на другое такого же типа можно
data = 123; // а вот на значение другого типа нельзя!


final
Еще один распространенный модификатор. Он говорит о том, что переменной можно присвоить значение только один раз. Последующие изменения невозможны.

final data = DateTime.now(); // задаем начальное значение 
data = DateTime.now().add(const Duration(days: 1)); // из-за final при объявлении заменить значение не получится!


const
Это ключевое слово фиксирует значение, присваиваемое переменной. Как и в случае с final, изменить его не получится. Но есть важное отличие от final — const-переменная инициализируется в момент компиляции программы, поэтому присвоить ей значение, получаемое в рантайме, не получится.

const data = 'Привет! Это строка'; // задаем значение известное при компиляции 
data = 'А теперь это другая строка!'; // обновить значение нельзя!
const data2 = DateTime.now(); // как и задать значение, которое будет получено только в рантайме!


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

abstract class AbstractExample {
static String value = 'Это значение static переменной';
}

void main() {
print(AbstractExample.value); // выведет в консоль сообщение – Это значение static переменной
AbstractExample.value = 'А это новое значение!';
print(AbstractExample.value); // выведет в консоль сообщение – А это новое значение!
}


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

late String name;

void printName() {
print(name); // получим ошибку, так как значение еще не задано!
}

void printNameSafe() {
name = 'Anna';
print(name); // будет выведено в консоль — Anna
}


Самое интересное, модификаторы переменных можно и нужно комбинировать! Это позволит вам сделать код максимально безопасным, исключить возможность ненужных обновлений данных и оптимизировать вашу программу.

Например:
late final — дает возможность инициализировать переменную позже, но запрещает изменение
static const — создает константу класса в момент компиляции

Но тут важно, что некоторые модификаторы друг другу противоречат, поэтому комбинировать их не получится. Например, const и late.

❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
21🔥5👎2🎅2🏆1
This media is not supported in your browser
VIEW IN TELEGRAM
🛫Врываемся в вечер понедельника с обновленным корпоративным шаблоном Friflex Flutter Starter

Что улучшили:
Flutter 3.38.1+ обновлен до последней стабильной версии
Dot Shorthands — новая фича Dart 3.10 для более чистого кода
Обновлены правила анализатора — улучшенная проверка кода
Инструкции для AI-ассистентов — добавлены инструкции для Copilot и Cursor
Упрощена инициализация репозиториев — переработана архитектура DI
Обновлена документация — актуальная информация по всем модулям
Модуль обновлений — добавлена поддержка hard & soft обновлений приложения
Обновлены все зависимости до последних версий
Улучшена система тем и UI Kit

Подробнее о Flutter Starter писали здесь

Начните свой проект прямо сейчас! И ждем pull requests с улучшениями и предложениями!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍53
Всем привет, меня зовут Роза, Flutter Dev Friflex

В мире, где со всех углов люди кричат про AI, сложно оставаться в стороне и не использовать этот инструмент. Давайте представим, что в ваш проект нужно «затащить» LLM. Как это сделать? Сегодня расскажу, как подключить Flutter/Dart-приложение к локальному серверу Ollama с помощью пакета ollama_dart.

Ollama — это инструмент для запуска языковых моделей локально, на своем компьютере. Кстати, вот отличная статья о нем на Хабре. Он дает:
▫️ приватность
▫️ полный контроль
▫️ отсутствие зависимости от облачных API

Допустим, сервер Ollama уже запущен. Как теперь связать его с Flutter/Dart-приложением? Сделать это легко с помощью библиотеки ollama_dart, которая позволяет:
✔️ отправлять одиночные запросы
✔️ работать со стримингом ответов
✔️ использовать разные типы запросов: completion, chat, embeddings
✔️ выбирать любые доступные модели

Чтобы подключить Ollama, достаточно импортировать пакет и инициализировать клиента. По умолчанию он стучится на `https://localhost:11434/api`, но вы можете указать свой URL и добавить заголовки:

import 'package:ollama_dart/ollama_dart.dart';

final client = OllamaClient(baseUrl: 'some_url', headers: {});


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

final generated = await client.generateCompletion(
  request: GenerateCompletionRequest(
    model: 'mistral:latest',
    prompt: 'Why is the sky blue?',
  ),
);
print(generated.response);
// The sky appears blue because of a phenomenon called Rayleigh scattering...


Если вам нужно получать ответы по мере генерации, можно использовать generateChatCompletionStream.
Представьте, что у нас есть чат-бот, и мы хотим вести с ним полноценный диалог, при этом видеть ответ модели по мере того, как она его формирует.

На примере ниже создается поток (`stream`) для чат-комплешена. Сначала мы задаем системную роль, которая описывает поведение модели, а затем отправляем сообщение пользователя:

final stream = client.generateChatCompletionStream(
  request: GenerateChatCompletionRequest(
    model: 'mistral:latest',
    messages: [
      Message(
        role: MessageRole.system,
        content: 'You are a helpful assistant.',
      ),
      Message(
        role: MessageRole.user,
        content: 'List the numbers from 1 to 9 in order.',
      ),
    ],
  ),
);

await for (final chunk in stream) { 
   print(chunk.message?.content); 
}


При работе с моделями можно выбирать разные версии, например llama3 или mistral:latest, а также настраивать параметры генерации, такие как temperature для степени креативности, numPredict — для ограничения длины ответа и topP — для управления вероятностным сэмплированием (и это еще не весь список доступных опций).  Например:

final generated = await client.generateCompletion(
  request: const GenerateCompletionRequest(
    model: 'mistral:latest',
    prompt: 'Сочини мне историю про кита',
    options: RequestOptions(
      temperature: 0.7,
      numPredict: 150,
    ),
  ),
);



Чтобы работа с LLM через Ollama была максимально продуктивной, стоит:
▪️ детализировать промт, указывая tone of voice, роль модели и примеры ответа
▪️ экспериментировать с разными моделями – результаты могут сильно отличаться
▪️ использовать стриминг для больших текстов, чтобы экономить память и быстрее получать частичные ответы

Вы можете использовать ollama_dart для создания локальных чат-ботов, для генерации текста или интеграции LLM в существующие приложения.

📎На этом все, чтобы узнать больше, советую заглянуть в официальную библиотеку ollama_dart и прочитать про Ollama
Please open Telegram to view this post
VIEW IN TELEGRAM
7👍5🔥3
🪙Привет, с вами Катя, Flutter Dev Friflex

В прошлом посте я рассказывала о принципе единственной ответственности (S в SOLID). Сегодня разбираем следующую букву — O (Open/Closed Principle), принцип открытости/закрытости.

Что такое Open/Closed Principle?
Принцип открытости/закрытости гласит: программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации. Это означает, что вы должны иметь возможность добавлять новую функциональность без изменения существующего кода.

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

Пример
Нарушение OCP


class DiscountCalculator {
  double calculate(double price, String customerType) {
    if (customerType == 'regular') {
      return price * 0.95;
    } else if (customerType == 'premium') {
      return price * 0.90;
    } else if (customerType == 'vip') {
      return price * 0.80;
    }
    return price;
  }
}


Метод нужно менять при добавлении новых правил.

Правильное применение OCP

abstract class DiscountStrategy {
  double apply(double price);
}

class RegularDiscount implements DiscountStrategy {
  @override
  double apply(double price) => price * 0.95;
}

class PremiumDiscount implements DiscountStrategy {
  @override
  double apply(double price) => price * 0.90;
}

class VipDiscount implements DiscountStrategy {
  @override
  double apply(double price) => price * 0.80;
}

class DiscountCalculator {
  double calculate(double price, DiscountStrategy strategy) {
    return strategy.apply(price);
  }
}


Открыты для расширения, закрыты для модификации

Преимущества OCP
✔️
Меньше ошибок при внесении изменений
✔️Не требуется регрессионное тестирование существующего кода
✔️Более простой процесс расширения функциональности
✔️Повышение стабильности и надежности системы

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

В следующем посте я расскажу о принципе подстановки Барбары Лисков (L в SOLID).

Подписывайтесь, чтобы не пропустить
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥4🤔2
This media is not supported in your browser
VIEW IN TELEGRAM
🛫Привет! Это Анна, Flutter Team Lead Friflex

Hot Restart и Hot Reload — такие знакомые каждому Flutter-разработчику понятия. Без них сейчас разработку уже и сложно представить — они делают ее значительно быстрее и проще.

Несмотря на популярность этих понятий, начинающие разработчики часто их путают, не до конца понимают в чем их отличия и как они работают под капотом. Разберемся!

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

Hot Reload не перезапускает программу с нуля. Он лишь «подмешивает» измененный код в уже запущенный процесс. Состояние приложения сохраняется.

Под капотом работает так:
1. Вы вносите изменения в код и запускаете Hot Reload
2. Flutter изучает, какие именно файлы вы изменили
3. Затем перекомпилируются только важные части: библиотеки с измененным кодом, основная библиотека и все библиотеки, связанные с измененными
4. Обновленный код преобразуется в специальный kernel-файл и поступает в Dart VM
5. Dart VM подтягивает новые версии библиотек, при этом сохраняя текущее состояние приложения
6. Flutter вызывает перестройку тех виджетов, которые были затронуты изменениями

Еще больше внутрянки можно узнать в статье Станислава Чернышева.

В итоге вы видите измененный интерфейс, функции работают по-новому, но состояние не сброшено.

При использовании Hot Reload очень важно помнить, что не все изменения могут примениться корректно. Он точно вам не подойдет, если:
✔️ вы меняете структуру класса (добавляете/удаляете поля, меняете их типы, меняете конструкторы)
✔️ вы заменяете глобальные переменные и синглтоны (например, экземпляры глобальных сервисов, которые могут повлиять на всю работу приложения)
✔️ вы обновляете код, выполняемый до runApp()

Это только часть случаев, поэтому вам важно самостоятельно внимательно отслеживать, как ведет себя ваше приложение при ряде Hot Reload

🔄 Hot Restart полностью перезапускает приложение. При этом сбрасываются абсолютно все состояния и данные. Проще этот механизм представить перезапуском main() метода. Но важно, что сборка заново не пересобирается.

Под капотом работает так:
1. Вы вносите изменения в код и запускаете Hot Restart
2. Dart VM очищает все сохраненные состояния, все значения переменных, пересоздает все объекты заново
3. Заново запускается метод main()
4. Приложение запускается как будто с нуля, но не затрагивается нативный код и движок Flutter

Визуально вам кажется, что приложение было закрыто и собирается заново, но это не так. И это важно понимать, потому что Hot Restart не может вам помочь, если вы:
✔️ обновили нативный код
✔️ внесли изменения в pubspec.yaml, подключили новые плагины или обновили старые, добавили новые assets
✔️ изменили платформенные зависимости (Gradle/Pods)
✔️ поменяли работу платформенных каналов (например, изменили названия)
✔️ обновили версии Dart или Flutter

Во всех этих перечисленных случаях вам потребуется полная остановка процесса и повторный запуск.

Если сравнивать по времени выполнения:
◾️ Hot Reload считается самым быстрым
◾️ Hot Restart быстрый, но медленнее, чем Hot Reload
◾️ Full Restart самый медленный

❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
12🔥4🤩2