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

🔗 Наш канал для разработчиков: @friflex_dev
🔗 Канал о продуктовой разработке: @friflex_product
Download Telegram
This media is not supported in your browser
VIEW IN TELEGRAM
👍Привет, это Катя, Friflex Flutter Dev

Сегодня расскажу о сравнении коллекций и почему встроенный оператор == работает не так, как ожидают многие разработчики

Базовое сравнение
Встроенный оператор == у коллекций (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️⃣ Моральная подготовка
Собеседование — это всегда стресс для кандидата. Честно скажу, я пока не встречала ни одного человека, который не переживал бы перед ним. И здесь важно понять, что волнение — это абсолютно нормальная реакция на этот этап. От волнения можно сбиться, запутаться, что-то забыть, и это норма!

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

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

Давайте в комментариях соберем лучшие советы для всех, кто проходит и кто проводит собеседования👇
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥108😁3🥰1
💭Привет, с вами Катя, Flutter Dev Friflex! Сегодня расскажу об аннотациях

Что это?
Аннотации
— это специальные метки (метаданные), которые можно добавлять к классам, методам, переменным и другим элементам кода. Они не изменяют сам код напрямую, но дают дополнительную информацию компилятору, инструментам или фреймворкам.

Пример:

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
Какая аннотация указывает, что метод переопределяет родительский?
Anonymous Quiz
98%
5
Какая аннотация используется для классов, все поля которых должны быть final?
Anonymous Quiz
4
Какая аннотация запрещает наследование класса за пределами его библиотеки?
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🔥3👍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
10🔥6🥰2