This media is not supported in your browser
VIEW IN TELEGRAM
Наверняка каждый Flutter-разработчик знает, что такое Isolate и как он работает. Немного освежим теорию в памяти.
Dart — однопоточный язык программирования. Когда мы создаем Flutter-приложения, все вычисления и операции выполняются по умолчанию в основном и единственном потоке — изоляте (Isolate).
При необходимости разработчик может запустить дополнительный изолят. Например, когда требуется произвести операции, которые могут занять достаточно времени, ресурсов и повлиять на производительность приложения. Сюда можно отнести какие-то сложные вычисления, а также парсинг огромного объема данных. Благодаря вынесению этих функций в отдельный изолят, основной поток не блокируется, интерфейс приложения остается отзывчивым.
В Dart изоляты можно запускать тремя основными способами:
1️⃣
Isolate.run() — метод запустит новый изолят, в котором будет выполнена переданная функция. По завершении операций из изолята вернутся ожидаемые данные, а поток самостоятельно завершится2️⃣
Isolate.spawn() — также запускает новый изолят, в который необходимо передавать функцию для выполнения. При этом в отличие от изолята, запущенного через run(), дает возможность непрерывно обмениваться сообщениями с основным потоком3️⃣
Isolate.spawnUri() — работает аналогично spawn() за одним исключением. В изолят можно отправить прямую ссылку на Dart-программу. Уверена, простые примеры вы легко сможете найти в интернете или реализовать сами. А сейчас в качестве примера рассмотрим более сложную задачу.
Представим, что мы получаем большой список карточек товаров с бэкенда. Мы знаем, что некоторые данные в карточках могут быть невалидны и привести к ошибке парсинга. В таком случае нам нужно выполнить парсинг списка данных в отдельном изоляте, при этом ошибка парсинга одной карточки не должна прервать весь процесс — нам нужно лишь пропустить этот объект и залогировать сведения о проблеме.
В решении этой задачи нам поможет
Isolate.spawn() и порты получения данных ReceivePort(). Шаг 1. Создаем top-level метод парсинга данных в изоляте
_parseInIsolate(). Метод должен принимать обязательно два порта отправки данных — порт отправки результата парсинга и порт отправки ошибок. Именно в этом методе мы закладываем логику обработки ошибки. В случае перехвата ошибки, отправляем ее в порт ошибок errorSendPortFuture<void> _parseInIsolate(
({
SendPort dataSendPort,
SendPort errorSendPort,
List<Map<String, dynamic>> rawDataList,
}) params,
) async {
final result = <ResultEntity>[];
for (final data in params.rawDataList) {
try {
// здесь выполняем парсинг каждого элемента
result.add(...);
} on Object catch (error, stackTrace) {
// в случае ошибки парсинга конкретного объекта
// отправляем ошибку обратно в главный изолят
params.errorSendPort.send((error: error, stackTrace: stackTrace));
continue;
}
}
// в порт данных отправляем готовые объекты
params.dataSendPort.send(result);
}
Шаг 2. Создаем метод получения данных fetchData(). В нем выполняем запрос на бэкенд. Дальнейшие шаги будут дополнять этот метод
Future<List<ResultEntity>> fetchData() async {
final rawDataList = await _httpClient.get('v1/examples/data');
}
3 шаг. После получения данных создаем два порта ReceivePort(). Один порт будет отвечать за передачу обработанных объектов, второй — за передачу данных о локальных ошибках парсинга
final dataPort = ReceivePort();
final errorPort = ReceivePort();
Шаг 4. Открываем новый изолят и запускаем в нем метод
_parseInIsolate(). Обязательно передаем в него два порта отправки и данные, которые надо обработать
final isolate = await Isolate.spawn(
_parseInIsolate,
(
dataSendPort: dataPort.sendPort,
errorSendPort: errorPort.sendPort,
rawDataList: rawDataList,
),
);
Продолжение в комментариях
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥6👌1
Концентрация Flutter-разработчиков на них зашкаливает. Делимся яркими моментами с конференции CrossConf.
Героини и авторы постов @flutterfriendly тоже там были: Катя и Анна модерировали потоки по Flutter. Роза выступала с докладом.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12🔥2
Сегодня хочу рассказать про шейдеры и показать, как их использовать во Flutter-проектах. Прежде чем мы начнем что-то подключать и запускать, давайте разберемся, что это вообще такое.
Шейдер — это программа, которая выполняется на графическом процессоре и отвечает за вычисление цвета каждого пикселя. Именно благодаря шейдерам мы можем получать освещение, тени, блики, искажения, волны, шумы, стеклянные поверхности и множество других эффектов. Для их написания используется язык GLSL. Он напоминает Си и специально создан для задач реального времени, например, в играх и анимации.
Шейдеры могут быть полезны, когда хочется настоящего размытия, а не его имитации, или при создании динамичных эффектов вроде волн, огня, дождя, стекла и других, которые делают интерфейс более уникальным.
Во Flutter можно использовать как готовые шейдеры, так и писать их самостоятельно. Сегодня мы разберем ситуацию, когда шейдер у нас уже есть. В следующем посте попробуем создать его с нуля или адаптировать готовый под наши потребности.
1️⃣ Чтобы шейдер стал частью приложения, достаточно подключить его в pubspec.yaml:
flutter:
shaders:
- shaders/my_shader.frag
Здесь важно именно расширение .frag. Flutter пока поддерживает только фрагментные шейдеры.
2️⃣ После этого загружаем шейдер:
late FragmentProgram program;
Future<void> loadMyShader() async {
program = await FragmentProgram.fromAsset('shaders/my_shader.frag');
}
Объект FragmentProgram можно использовать для создания одного или нескольких экземпляров FragmentShader.
3️⃣ Далее создаем FragmentShader и передаем нужные параметры. Например, размер canvas или время для анимации. Сам рендеринг удобно выполнять через CustomPainter:
class ShaderPainter extends CustomPainter {
final FragmentShader shader;
final double time;
ShaderPainter(FragmentShader fragmentShader, this.time)
: shader = fragmentShader;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint();
shader.setFloat(0, size.width);
shader.setFloat(1, size.height);
shader.setFloat(2, time);
paint.shader = shader;
canvas.drawRect(Offset.zero & size, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}Важно помнить про несколько нюансов:
▪️Шейдеры во Flutter пока ограничены только фрагментными программами. Их размер и сложность влияют на вес сборки и производительность, поэтому лучше не переусердствовать
▪️Желательно кэшировать FragmentProgram, чтобы не загружать его повторно
▪️И, конечно, следить за количеством uniform-переменных
Кстати, Uniform-переменные — это глобальные параметры шейдера, которые задаются приложением. Они одинаковые для всех пикселей и позволяют управлять эффектом из Flutter-кода. Например, если в шейдере есть uTime, то мы можем передавать туда анимационное значение:
shader.setFloat(0, controller.value);
Контроллер меняет число каждый кадр, шейдер пересчитывает волну, и ваш UI плавно анимируется, нагружая GPU, а не CPU.
❕Ключевой момент: индекс в shader.setFloat(index, value) обязательно должен совпадать с порядком объявления uniform-переменных в GLSL-коде.
Сегодня мы прошли только базу,а дальше можно экспериментировать бесконечно. Если вам интересно углубиться, загляните в документацию.
❤️ — если хотите посты, в которых мы разберем создание своего собственного шейдера и подключение его к анимации
Please open Telegram to view this post
VIEW IN TELEGRAM
❤14👍2🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
Сегодня расскажу о сравнении коллекций и почему встроенный оператор == работает не так, как ожидают многие разработчики
Базовое сравнение
Встроенный оператор == у коллекций (List, Set, Map) проверяет ссылки на объект, а не содержимое. То есть два списка с одинаковыми элементами будут не равны, если это разные объекты в памяти:
void main() {
var a = [1, 2, 3];
var b = [1, 2, 3];
print(a == b); // false, разные объекты
print(identical(a, b)); // false
}Правильное сравнение содержимого
Для сравнения по значениям используют пакет collection. Он предоставляет специализированные классы для разных типов коллекций.
ListEquality
Сравнение списков поэлементно в строгом порядке:
import 'package:collection/collection.dart';
void main() {
var a = [1, 2, 3];
var b = [1, 2, 3];
print(const ListEquality().equals(a, b)); // true
}
SetEquality
Сравнение множеств без учета порядка элементов:
import 'package:collection/collection.dart';
void main() {
var a = {1, 2, 3};
var b = {3, 2, 1};
print(const SetEquality().equals(a, b)); // true
}
MapEquality
Сравнение словарей по ключам и значениям:
import 'package:collection/collection.dart';
void main() {
var a = {"x": 1, "y": 2};
var b = {"y": 2, "x": 1};
print(const MapEquality().equals(a, b)); // true
}
Глубокое сравнение
Если коллекции вложенные (List<Map<String, Set<int>>>), то используют DeepCollectionEquality. Он рекурсивно сравнивает элементы на любом уровне вложенности:
import 'package:collection/collection.dart';
void main() {
var a = [
{"nums": {1, 2}}
];
var b = [
{"nums": {2, 1}}
];
print(const DeepCollectionEquality().equals(a, b)); // true
}
Сравнение с кастомной логикой
Можно задать свою функцию сравнения для элементов коллекций:
import 'package:collection/collection.dart';
void main() {
var a = ["Hello", "world"];
var b = ["hello", "WORLD"];
var eq = ListEquality(StringEquality(ignoreCase: true));
print(eq.equals(a, b)); // true
}
❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
❤24👍6🔥4
This media is not supported in your browser
VIEW IN TELEGRAM
У вас собеседование через 5 минут!
🔤 🔤 🔤 ! Испугались? Привет! Это Анна, Flutter Team Lead Friflex. Сегодня мы опустим все вопросы технических реализаций и поговорим о насущном, о том, что вселяет страх и ужас начинающим специалистам — техническом собеседовании на позицию Flutter-разработчика.
Для начала отмечу, что в зависимости от компании и от уровня кандидата собеседования могут проходить немного по-разному. Здесь будет некоторая усредненная информация, которая поможет подготовиться именно начинающим разработчикам.
1️⃣ Описание опыта
Даже если у вас в резюме уже указан ваш опыт работы или его отсутствие, будьте готовы, что вопрос о нем все равно прозвучит на собеседовании. Интервьюеру всегда важно послушать, что и как вы говорите о своем опыте. В резюме можно написать все, что угодно, но именно живой рассказ помогает понять, правда это или нет.
Здесь рекомендую перед собеседованием вспомнить, с чем вы уже имели дело. Чаще всего спрашивают о технологиях и библиотеках, с которыми вы сталкивались, о самых интересных и самых сложных задачах за последнее время. Важно отвечать достаточно развернуто: что именно давалось с трудом, как решили, что нового узнали в процессе. Не бойтесь говорить о pet-проектах, если коммерческого опыта пока нет.
2️⃣ Технические вопросы
Техническое собеседование всегда подразумевает их наличие. Здесь рекомендую заранее изучить списки вопросов под свой уровень в интернете. А также саму вакансию — почти всегда в вакансии указывается стек технологий, по которому с большей долей вероятности вы получите вопрос на собеседовании. От себя для новичков могу посоветовать изучить следующие темы:
Dart
▫️ Null-safety
▫️ Event Loop
▫️ параллельность
▫️ dart:async, Future, Stream, Isolate
▫️ модификаторы классов и переменных, конструкторы классов
▫️ коллекции и операции с ними
▫️ обработка ошибок
Flutter
▫️ Stateless- и Stateful-виджеты, жизненный цикл Stateful-виджета
▫️ Layout-виджеты, отличия Flexible/Expanded
▫️ InheritedWidget
▫️ BuildContext, Widget, Element
▫️ навигация без библиотек
▫️ Navigator, отличия императивной и декларативной (верхнеуровневой)
▫️ анимации
▫️ виджеты прокрутки (SingleChildScrollView, ListView, CustomScrollView)
▫️ тесты
Конечно, это только малая часть. Если у вас есть, чем дополнить этот список — wellcome в комментарии!
3️⃣ Моральная подготовка
Собеседование — это всегда стресс для кандидата. Честно скажу, я пока не встречала ни одного человека, который не переживал бы перед ним. И здесь важно понять, что волнение — это абсолютно нормальная реакция на этот этап. От волнения можно сбиться, запутаться, что-то забыть, и это норма!
Поверьте, те, кто проводят собеседование, прекрасно понимают, под каким психологическим давлением вы находитесь. Если вы чувствуете, что вам нужна пара минут, чтобы перевести дух, прийти в себя и хорошо подумать над вопросом — не стесняйтесь об этом сказать. Все мы люди, вас никто не осудит.
И в первую очередь настраивайте себя на позитивный исход. Даже если вы не получите заветный оффер, вы получите гораздо больше — бесценный опыт.
Давайте в комментариях соберем лучшие советы для всех, кто проходит и кто проводит собеседования👇
Для начала отмечу, что в зависимости от компании и от уровня кандидата собеседования могут проходить немного по-разному. Здесь будет некоторая усредненная информация, которая поможет подготовиться именно начинающим разработчикам.
1️⃣ Описание опыта
Даже если у вас в резюме уже указан ваш опыт работы или его отсутствие, будьте готовы, что вопрос о нем все равно прозвучит на собеседовании. Интервьюеру всегда важно послушать, что и как вы говорите о своем опыте. В резюме можно написать все, что угодно, но именно живой рассказ помогает понять, правда это или нет.
Здесь рекомендую перед собеседованием вспомнить, с чем вы уже имели дело. Чаще всего спрашивают о технологиях и библиотеках, с которыми вы сталкивались, о самых интересных и самых сложных задачах за последнее время. Важно отвечать достаточно развернуто: что именно давалось с трудом, как решили, что нового узнали в процессе. Не бойтесь говорить о pet-проектах, если коммерческого опыта пока нет.
2️⃣ Технические вопросы
Техническое собеседование всегда подразумевает их наличие. Здесь рекомендую заранее изучить списки вопросов под свой уровень в интернете. А также саму вакансию — почти всегда в вакансии указывается стек технологий, по которому с большей долей вероятности вы получите вопрос на собеседовании. От себя для новичков могу посоветовать изучить следующие темы:
Dart
▫️ Null-safety
▫️ Event Loop
▫️ параллельность
▫️ dart:async, Future, Stream, Isolate
▫️ модификаторы классов и переменных, конструкторы классов
▫️ коллекции и операции с ними
▫️ обработка ошибок
Flutter
▫️ Stateless- и Stateful-виджеты, жизненный цикл Stateful-виджета
▫️ Layout-виджеты, отличия Flexible/Expanded
▫️ InheritedWidget
▫️ BuildContext, Widget, Element
▫️ навигация без библиотек
▫️ Navigator, отличия императивной и декларативной (верхнеуровневой)
▫️ анимации
▫️ виджеты прокрутки (SingleChildScrollView, ListView, CustomScrollView)
▫️ тесты
Конечно, это только малая часть. Если у вас есть, чем дополнить этот список — wellcome в комментарии!
3️⃣ Моральная подготовка
Собеседование — это всегда стресс для кандидата. Честно скажу, я пока не встречала ни одного человека, который не переживал бы перед ним. И здесь важно понять, что волнение — это абсолютно нормальная реакция на этот этап. От волнения можно сбиться, запутаться, что-то забыть, и это норма!
Поверьте, те, кто проводят собеседование, прекрасно понимают, под каким психологическим давлением вы находитесь. Если вы чувствуете, что вам нужна пара минут, чтобы перевести дух, прийти в себя и хорошо подумать над вопросом — не стесняйтесь об этом сказать. Все мы люди, вас никто не осудит.
И в первую очередь настраивайте себя на позитивный исход. Даже если вы не получите заветный оффер, вы получите гораздо больше — бесценный опыт.
Давайте в комментариях соберем лучшие советы для всех, кто проходит и кто проводит собеседования👇
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9❤7😁3🥰1
Что это?
Аннотации — это специальные метки (метаданные), которые можно добавлять к классам, методам, переменным и другим элементам кода. Они не изменяют сам код напрямую, но дают дополнительную информацию компилятору, инструментам или фреймворкам.
Пример:
class Person {
final String name;
const Person(this.name);
}
@deprecated // помечает метод как устаревший
void oldMethod() {
print("Этот метод больше не рекомендуется использовать");
}
@override // указывает, что метод переопределяет родительский
String toString() => "Person";📍Встроенные
@override — помечает метод, который переопределяет родительский@deprecated — помечает элемент как устаревший@pragma — специальные инструкции для VM (например, @pragma('vm:entry-point'))📍Из пакета meta
@immutable — класс неизменяемый (все поля final)@required — аннотация для обязательных параметров (устарела с введением модификатора required, который теперь указывается прямо в объявлении параметра)@protected — метод/поле должно использоваться только внутри самого класса или подклассов@visibleForTesting — элемент предназначен только для тестов@visibleForOverriding — метод/поле предназначено для переопределения@mustCallSuper — при переопределении метода нужно обязательно вызвать super@nonVirtual — метод не может быть предопределен@sealed — класс нельзя наследовать за пределами своей библиотеки@doNotStore — нельзя хранить ссылку на объект (например, BuildContext)@factory — метод/конструктор должен возвращать новый объект каждый раз📍Для генерации кода (через build_runner)
@JsonSerializable — для генерации JSON-сериализации (json_serializable)@JsonKey — настройка маппинга конкретного поля в JSON@HiveType / @HiveField — для сериализации в Hive@Freezed и связанные (@freezed) — генерация immutable-классов и union typesВ прошлом посте Аня рассказывала о собеседованиях, так что давайте проверим ваши знания про аннотации. Чур не подсматривать!🫣
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3🔥1👏1
❤4
Какая аннотация используется для классов, все поля которых должны быть final?
Anonymous Quiz
6%
2%
88%
4%
❤4
Для чего используется аннотация @visibleForTesting?
Anonymous Quiz
6%
Для генерации тестов автоматически
90%
Для обозначения элементов, предназначенных только для тестов
1%
Для скрытия кода от тестирования
3%
Для запуска тестов в production
❤4
Какая аннотация запрещает наследование класса за пределами его библиотеки?
Final Results
38%
5%
54%
3%
❤4
This media is not supported in your browser
VIEW IN TELEGRAM
Пока я готовлю пост про шейдеры, давайте обсудим, как можно создать свой 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
❤14🔥2👍1