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

🔗 Наш канал для разработчиков: @friflex_dev
🔗 Канал о продуктовой разработке: @friflex_product
Download Telegram
Продаем мак и покупаем винду?

Привет, с вами опять Катя, Flutter Dev Friflex. Слышали уже, что Apple открыла исходный код Swift Build — нового мощного движка сборки? Говорят, скоро разработчики смогут собирать Flutter-приложения для iOS на Windows. Давайте разберемся, что такое Swift Build и как он повлияет на разработку кроссплатформенных приложений.

Что это такое
Swift Build — это инструмент, который упрощает процесс сборки приложений на Swift. Он позволяет разработчикам управлять зависимостями, компилировать код и настраивать различные параметры сборки. Открытие исходного кода Swift Build означает, что разработчики могут изучать, изменять и адаптировать его под свои нужды.

Основные возможности Swift Build
🔴Управление зависимостями: легкое добавление и обновление библиотек.

🔴Настройка сборки: гибкие настройки для различных конфигураций и платформ.

🔴Оптимизация сборки: ускорение процесса сборки за счет эффективного управления ресурсами.

Возможности, которые открывает Swift Build
🔴Сборка на Windows: если Swift Build станет доступным для Windows, разработчики смогут собирать iOS-приложения без Mac.

🔴Упрощение процесса разработки: это упростит рабочий процесс для разработчиков, которые предпочитают Windows, и снизит барьер для входа в разработку под iOS.

🔴Расширение аудитории: больше разработчиков смогут создавать приложения для iOS, что приведет к увеличению числа кроссплатформенных приложений.

Реальность или мечта
Пока что сборка iOS-приложений на Windows остается скорее перспективой, чем реальностью. Несмотря на то, что Apple сделала Swift Build открытым, для полноценной сборки iOS-приложений все еще требуется Xcode, который доступен только на macOS.

Но сообщество активно обсуждает возможность адаптации Swift Build для работы на Windows, что может стать революцией в мире кроссплатформенной разработки.

🔥 — продаю мак
❤️ — люблю мак
Please open Telegram to view this post
VIEW IN TELEGRAM
24🔥15👍6
Всем привет! Это Анна, Friflex Flutter Team Lead. Сегодня делюсь с вами своим личным топ-5 самых полезных расширений для VSCode.

🔜 Flutter Widget Snippets
Сниппеты в VSCode — очень удобный инструмент для быстрого создания шаблонных и часто повторяющихся участков кода. Это расширение позволяет быстро создать большинство самых популярных виджетов во Flutter (например, Stateless/StatefulWidget, Layout).

Еще расширение дает доступ к Dart-сниппетам, с помощью которых можно быстро создавать классы, методы и переменные. Со сниппетами писать код можно гораздо быстрее. Но начинающим разработчикам рекомендую для начала изучить сам синтаксис, привыкнуть к разработке без сторонних подсказок, чтобы в дальнейшем не стать заложником дополнительных инструментов.

🔜 bloc
Для тех, кто в своих Flutter проектах использует библиотеку bloc, это расширение может стать незаменимым помощником.
Оно предоставляет набор сниппетов, с которыми в коде проще и быстрее добавлять виджеты в UI, создавать сами блоки и кубиты, а еще управлять их методами.

Расширение добавляет возможность быстро оборачивать виджеты в другие виджеты библиотеки через контекстное меню, а также создавать целые директории с первично настроенными связями между классами Bloc, State и Event. Находка для тех, кто не любит тратить время на организацию файлов и классов в них!

🔜 Flutter Tree
Еще одно полезное расширение для продвинутых Flutter-разработчиков.
С его помощью удобно добавлять вложенность виджетов: не теряешь время на написание параметров, расстановку скобок.

Например, если вам нужно создать контейнер со строкой виджетов внутри, это можно сделать простой записью Container>Row[Expanded>Text,ElevatedButton>Text]. В конце нажимаете Enter, и Flutter Tree все делает за вас — на выходе получаете сформированное дерево по вашему запросу.

🔜 Code Spell Checker
Это расширение пригодится не только Flutter-разработчикам, но и всем, кто пользуется VSCode. Оно помогает проверять написание слов в вашем коде. Будь то документация или название объектов в коде, расширение подчеркнет те слова, которые написаны некорректно и предложит варианты замены. Если слово написано верно, его можно вынести в исключения как для конкретного проекта, так и для вашего пользователя в общем. Поддерживает огромное количество языков, в том числе и русский.

🔜 Commit Message Editor
Почти все серьезные проекты требуют вести историю изменений в репозитории согласно соглашению о коммитах. Каждый коммит должен не только осмысленно отражать суть изменений, но и фиксировать тип изменений, измененные файлы и их критичность.

Чтобы оформлять коммиты по этим правилам, рекомендую Commit Message Editor. Расширение позволяет оформить коммит с помощью формы. Когда вы ее заполните, текст сам отформатируется и подставится в строку сообщения коммита.
Please open Telegram to view this post
VIEW IN TELEGRAM
9🔥5👍2🤩1
Привет, это Роза, Flutter Dev Friflex! 👋  

В Flutter существует огромное количество виджетов для управления размерами. Давайте сделаем быстрый дайджест и пройдемся по некоторым из них. Поехали!

🔴FittedBox
— Автоматически подстраивает дочерний виджет под доступное пространство, сохраняя пропорции.  
— Если контент слишком большой, уменьшает его, чтобы всё поместилось.  

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

🔴ConstrainedBox
— Задаёт минимальные или максимальные размеры для дочернего элемента.  
— Накладывает ограничения, даже если содержимое больше или меньше заданных параметров.  

🔴LimitedBox
— Ограничивает размеры элемента, но только если они не заданы явно.  
— Устанавливает максимальный размер, если дочерний элемент не ограничен другими условиями.  

🔴AspectRatio
— Сохраняет определённое соотношение сторон (например, 16:9).  
– Автоматически подстраивает размеры элемента, сохраняя пропорции.   

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

🔴IntrinsicWidth / IntrinsicHeight
— Позволяет элементу занимать минимально возможное пространство.  
— Вычисляет минимальный размер, необходимый для отображения дочернего элемента.  

🔴Flex, Expanded и Flexible
— Адаптивно распределяет пространство в Row или Column.  
Expanded — заполняет всё доступное пространство.  
Flexible — занимает пространство по возможности, но не жёстко.  

🔴Align
— Позволяет расположить элемент внутри родителя (например, по центру или в углу).  
— Задает позицию элемента относительно родительского контейнера.  

🔴LayoutBuilder  
— Динамически определяет доступный размер и позволяет перестроить UI.  
— Полезно для адаптации интерфейса под разные размеры экранов.   

❗️Важно
Используйте эти виджеты с умом. Некоторые из них могут повлиять на производительность и рендеринг. Тестируйте каждый случай!

💬 Делитесь в комментариях, какие виджеты используете чаще всего. Там же бонусом полезная табличка.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1711🔥5
Привет, с вами Катя, Flutter Dev Friflex. 

Сегодня я расскажу о новом расширении в Visual Studio Code для Swift. 

🔴Цель обновления
Сделать возможной разработку Swift-приложений на всех платформах. Это позволит разработчикам создавать приложения на Swift без ограничений по операционной системе.

🔴О переходе
Для тех, кто уже использует VS Code, переход будет плавным. Все существующие функции и инструменты останутся доступными, что делает адаптацию к новым возможностям легкой и удобной. Старый релиз должен автоматически установить новое расширение и отключить себя, а в дальнейшем все языковые функции будут предоставляться новым расширением.

📎Ссылка на установку расширения.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥84👌2
This media is not supported in your browser
VIEW IN TELEGRAM
10🔥6
Сегодня #Friflex_team заряжает знаниями на FlutterConf:

🔸12:40 — Юрий (руководитель отдела разработки) и Анна (Flutter TeamLead) в докладе «Router во Flutter. Когда думал, что все легко» расскажут про концепцию декларативного роутера.
📍Зал 1

🔸15:50 — Flutter-разработчики Роза и Екатерина в докладе «Как не наступать на одни и те же грабли: систематизация ошибок в разработке на Flutter» поделятся, как систематизировать ошибки, с которыми часто сталкиваются разработчики и предложат подходы по их минимизации.
📍Зал 2

Если вы здесь, то приходите послушать наши доклады и пообщаться. Присоединиться можно и онлайн.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍54
Media is too big
VIEW IN TELEGRAM
Flutter или …

На FlutterConf пообщались с Анной, Flutter TeamLead в Friflex, и поставили ее перед непростым выбором.

Что ответила Анна🖱
Please open Telegram to view this post
VIEW IN TELEGRAM
15🔥11👍5🍓1
Привет! Это Роза, Flutter Dev Friflex! 👋

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

1️⃣ Иконки из шрифтов (Icon Fonts) — встроенные иконки, такие как Material Icons.
2️⃣ SVG-иконки — обработка с помощью пакета flutter_svg
3️⃣ Векторная графика — использование vector_graphics для более быстрого рендеринга.

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

🔸Иконки из шрифтов (Icon Fonts)
Иконки из шрифтов — это символы в формате шрифтов, например, Material Icons:
Icon(Icons.home, size: 32, color: Colors.blue)


Можно использовать кастомные шрифты с иконками. Например, с помощью IcoMoon:

1. Генерируем файл .ttf с иконками.
2. Подключаем в pubspec.yaml:

flutter:
      fonts:
        - family: CustomIcons
          fonts:
            - asset: assets/fonts/CustomIcons.ttf


3. Используем:
    
const Text(
'\uE900',
style: TextStyle(fontFamily: 'CustomIcons', fontSize: 32),
),

    


🔸SVG-иконки с flutter_svg:
Для кастомных иконок в векторном формате используем пакет flutter_svg:
import 'package:flutter_svg/flutter_svg.dart';

SvgPicture.asset(
  'assets/icons/home.svg',
  width: 32,
  height: 32,
  colorFilter: ColorFilter.mode(Colors.blue, BlendMode.srcIn),
)


🔸Векторная графика (Vector Graphics)
Для повышения производительности конвертируем SVG в формат vector_graphics. Он рендерится быстрее.

import 'package:vector_graphics/vector_graphics.dart';

VectorGraphic(
  assetName: 'assets/icons/home.vec',
  width: 32,
  height: 32,
)


Что выбрать?

Icon Fonts — для стандартных иконок с высокой производительностью.
Flutter SVG — для кастомных и анимированных иконок.
Vector Graphics — для максимальной скорости рендеринга.

Какой метод используете вы? Делитесь в комментариях! 🚀
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍85🤩1
Привет! С вами вновь Анна, Friflex Flutter Team Lead.

🛒Что делать, если в Flutter-приложение нужно добавить возможность покупок виртуальных товаров и услуг? Сегодня подробно разберемся с нюансами в трех самых популярных сторах — Google Play, AppStore и RuStore.

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

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

Но в оплате через Google Play есть нюанс — даже при корректной интеграции в ваше приложение и настройке в кабинете разработчика проведение платежей в России, к сожалению, недоступно.

В AppStore подход немного лояльнее. Клиенты имеют возможность оплачивать покупки. Для этого в аккаунте Apple достаточно подключить как источник оплаты мобильный телефон. Важно: в России Apple поддерживает только номера операторов МТС и Билайн.
📎Подробнее о доступных способах оплаты тут.

Кроме этого для аккаунтов разработчиков, зарегистрированных в РФ, есть возможность провести цифровые покупки через внешние ссылки. Для этого необходимо подать в App Store Connect специальную заявку, после одобрения которой появится возможность настроить StoreKit External Purchase Link Entitlement в приложении. Комиссия за такие платежи — 27%.
📎Делюсь полезным туториалом по настройке и заполнению заявки.

Меньше всего проблем сейчас встречается в оплатах через RuStore. Здесь нет никаких ограничений на источники платежей и регион, откуда производится оплата. RuStore позволяет оплатить продукт различными способами, например, картой онлайн, через сервисы T-Pay, SberPay и даже СБП, что сейчас очень удобно для пользователей.

👍В следующем посте разберем на практике, как настроить в приложении in-app покупки c вызовом нативных окон от сторов.
Делитесь своим опытом в комментариях.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥128❤‍🔥4👍3👌2🆒1
✍️Привет, снова с вами Катя, Flutter Dev Friflex

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

Основные изменения в Flutter 3.29

1️⃣ Обновлены и добавлены новые разделы в документации:
🔴Архитектурный обзор
Обновленная страница предоставляет более детальное представление о внутренней архитектуре Flutter, что поможет разработчикам лучше понять, как работает фреймворк.
🔴Flutter для разработчиков Jetpack Compose: Для тех, кто приходит из мира Android-разработки и знаком с Jetpack Compose, добавлен новый раздел, который поможет адаптироваться к Flutter.

2️⃣ Добавлен новый рецепт в Cookbook, посвященный тестированию ориентации виджетов. Это поможет разработчикам эффективно проверять, как их виджеты реагируют на изменения ориентации экрана, что особенно важно для мобильных приложений.

3️⃣CupertinoNavigationBar и CupertinoSliverNavigationBar
🔴Поддержка нижнего виджета: Теперь эти навигационные бары принимают нижний виджет, который обычно используется для поля поиска или сегментированного управления.
🔴Режим отображения: в CupertinoSliverNavigationBar можно настроить нижний виджет с помощью свойства bottomMode, позволяя выбрать между автоматическим изменением размера и постоянным отображением.

Здесь все подробности о новом Flutter.

Поддержка Dart 3.7
Вместе с обновлением Flutter выпущена новая версия Dart — Dart 3.7. Она включает в себя улучшения производительности и новые функции языка.

Осторожность при обновлении до Flutter 3.29
Как и в каждом новом релизе, Flutter 3.29 включает в себя ряд разрушающих изменений. Ознакомиться можно тут.

Хотя этот релиз принесет множество новых возможностей и улучшений, важно помнить, что не стоит спешить с обновлением, особенно если вы активно используете сторонние библиотеки из pub.dev. Многие библиотеки на pub.dev могли еще не обновиться под новую версию Flutter. Это может привести к несовместимости и ошибкам в вашем проекте.

❗️В заключение, хотя Flutter 3.29 предлагает много интересного, разумно подождать, пока экосистема не адаптируется к новым изменениям, прежде чем обновляться.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1174👍4
This media is not supported in your browser
VIEW IN TELEGRAM
Всем привет! С вами Анна, Friflex Flutter Team Lead.

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

Для интеграции in-app покупок в приложение на Android и iOS существует плагин in_app_purchase. Он разработан командой Flutter и имеет отличную репутацию (2,24 тыс лайков и более 123 тыс скачиваний). При правильной интеграции его можно смело использовать в продакшн приложениях.

Какой же функционал дает эта библиотека? Разберемся!

1️⃣ Проверка доступности покупок
Покупки на устройстве доступны только тогда, когда платежная платформа активна и не нарушено подкючение устройства к стору, через который будет осуществленна оплата. Метод вернет true или false в зависимости от результатов проверки.

final isAvailable = await InAppPurchase.instance.isAvailable();


2️⃣ Получение данных о конкретных продуктах
Вы можете запросить в магазине основные данные определенных продуктов по их идентификаторам. В случае, если продукт с таким id был найден, сервис вернет его основную информацию — заголовок, описание, цену, валюту. Если какие-то идентификаторы не были обнаружены, библиотека вернет их общим списком.

 final data =
await InAppPurchase.instance.queryProductDetails({'id1', 'id2', 'id3'});

final products = data.productDetails;
final notFoundIds = data.notFoundIDs;


3️⃣ Осуществление покупки
Продукты бывают двух типов — consumable (расходуемые продукты, например, жизни в игре) и non consumable (одноразовые покупки доступа, такие как подписки).
Для оплаты каждого из типов существуют отдельные методы, которые вызывают нативные окна стров для оплаты. Они требуют данных о продукте для покупки. Опционально можно передать некоторый идентификатор пользователя, чтобы покупку в будущем можно было восстановить на другом устройстве.

 await InAppPurchase.instance.buyNonConsumable(
purchaseParam: PurchaseParam(
productDetails: product,
applicationUserName: userId,
),
);

await InAppPurchase.instance.buyConsumable(
purchaseParam: PurchaseParam(
productDetails: product,
applicationUserName: userId,
),
);


4️⃣ Восстановление покупки
Нередко бывает, что пользователь делает покупку на одном устройстве, а через какое-то время меняет его на другое. В таких случаях необходимо давать возможность восстановить доступ к ранее оплаченным покупкам. Для этого библиотека дает доступ к методу восстановления.

await 
InAppPurchase.instance.restorePurchases(applicationUserName: userId);



5️⃣ Получение обновлений о покупках
С помощью потока событий purchaseStream плагин дает возможность отслеживать обновления о текущих покупках. По завершении метода осуществления или восстановления покупки, данные о ней попадают в этот стрим.

InAppPurchase.instance.purchaseStream.listen((purchaseDetails) {
// обработка данных о покупках в МП
});


С помощью этих 5 основных функций вы легко можете интегрировать процесс покупки в ваше приложение.

❗️Если вы планируете публиковать свое приложение не только в Google Play и AppStore, важно понимать, что для других сторов необходима другая имплементация и, соответственно, другие библиотеки. Например, для RuStore подойдет flutter_rustore_billing, а для AppGallery — huawei_iap.

Делитесь в комментариях своим опытом интеграции in-app покупок.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥148👍4👌2🐳1
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, это Роза, Flutter Dev Friflex! 👋

Представь: ты создаешь Flutter-приложение и хочешь сделать переход между экранами эффектным. Что же выбрать?

💡Конечно, Hero

Как и положено настоящему герою, Hero анимирует элементы при смене экранов, плавно изменяя их размер и положение.

Что умеет Hero?
Улучшать восприятие интерфейса  
Сохранять контекст перехода  
Создавать красивые анимации

Например, можно сделать так, чтобы изображение плавно увеличивалось при переходе на новый экран или кнопка перемещалась между экранами.

Как это работает?
Hero связывает элементы с помощью уникального тега и автоматически анимирует их переход.

Давайте разберемся на примере:

📌Первый экран

Оборачиваем изображение в Hero и задаем тег:

GestureDetector(
  onTap: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => SecondScreen(imageUrl: ‘some_image_url’),
      ),
    );
  },
  child: Hero(
    tag: 'image',
    child: Image.network(image, height: 200),
  ),
);


📌Второй экран

Используем тот же тег:

Hero(
  tag: 'image',
  Child: Image.network(imageUrl, width: 300),
);


Hero автоматически выполняет анимацию — никаких сложных настроек.

Параметры Hero:
tag
— уникальный идентификатор элемента
child
— сам анимируемый виджет (изображение, кнопка и другие элементы)
flightShuttleBuilder
— позволяет кастомизировать анимацию
placeholderBuilder
— задает виджет-заполнитель до завершения анимации
transitionOnUserGestures
— разрешает запуск анимации по жесту пользователя

Где еще можно использовать Hero?
🔴В анимации текста  
🔴Для перемещения кнопок  
🔴Чтобы создавать эффектные переходы между карточками

Полезные советы:
➡️Используйте уникальные теги — каждый Hero должен иметь уникальный тег
➡️Комбинируйте с анимациями — FadeTransition, AnimatedContainer и другими
➡️Роутинг:  без правильного маршрута даже Hero не взлетит — выбирайте переходы с анимацией
➡️Не перегружайте интерфейс — анимируйте только ключевые элементы

👍 Лайфхак: можно настроить анимацию вручную через heroFlightShuttleBuilder или управлять временем анимации с помощью HeroController.

Теперь Hero не только в твоем коде, но и в тебе — ты создал крутой переход!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍128🔥6🍾3
Привет, с вами вновь Катя, Flutter Dev Friflex.

Сегодня я расскажу, как работать с Generics.

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

🔖Как это работает?
Рассмотрим пример, где мы создаем класс, который будет работать с моделью Model. Мы определим класс Repository, который будет использовать дженерики для работы с различными типами моделей.

Создадим базовую модельку с параметром name.

class Model {
  Model(this.name);
  final String name;
}


Определим класс Repository, который принимает параметр типа T. Условие extends Model гарантирует, что T будет подтипом Model. Создадим два метода: один — для добавления элементов в список, а другой — для их получения.

class Repository<T extends Model> {
  final List<T> items = [];

  void addItem(T item) => items.add(item);

  List<T> getItems() => items;
}


Теперь добавим модели User и Product, которые будут наследоваться от базовой модели. У каждой модели есть свои свойства id и price. 

class User extends Model {
  User(this.id, String name) : super(name);
  
  final int id;
}

class Product extends Model {
  Product(String name, this.price) : super(name);
  
  final double price;
}


Теперь перейдем к реализации и посмотрим, как с этим работать. В примере я создала два репозитория: один — для управления пользователями, а другой — для управления продуктами.

void main() {
  // Репозиторий для пользователей
  final userRepository = Repository<User>();
  userRepository.addItem(User(1, 'Alice'));
  userRepository.addItem(User(2, 'Bob'));

  // Выводим информацию о пользователях
  for (var user in userRepository.getItems()) {
    print('User: ${user.name}, ID: ${user.id}');
  }

  // Репозиторий для продуктов
  final productRepository = Repository<Product>();
  productRepository.addItem(Product('Laptop', 999.99));
  productRepository.addItem(Product('Smartphone', 499.99));

  // Выводим информацию о продуктах
  for (var product in productRepository.getItems()) {
    print('Product: ${product.name}, Price: \$${product.price}');
  }
}


После запуска приложения выводятся следующие данные: 

консоль
User: Alice, ID: 1
User: Bob, ID: 2

Product: Laptop, Price: $999.99
Product: Smartphone, Price: $499.99


Благодаря тому, что репозиторий принимает дженерики, мы можем подставлять туда любую модель, которая наследуется от Model.

🔖Преимущества использования

🔴Типобезопасность: Generics обеспечивают строгую типизацию, что позволяет избежать ошибок времени выполнения
🔴Повторное использование кода: вы можете создавать универсальные классы и методы, которые можно использовать с различными типами.
🔴Читаемость кода: код становится более понятным и структурированным.

Теперь ты освоил суперсилу гибкости! Главное — применять ее, иначе это будет как шпагат, который уже не такой уж и поперечный🤪
Please open Telegram to view this post
VIEW IN TELEGRAM
9🔥7👍62
Привет, это Анна, Friflex Flutter Team Lead.

Признавайтесь, кому приходилось хотя бы раз писать расширения для строк, чисел и даже списков, чтобы выполнить какие-то простые преобразования или проверки, которых нет среди стандартных функций? Уверена, почти всем! Сегодня я расскажу, как перестать это делать.

Вам нужна всего одна библиотека — dartx.

Пакет дает доступ к дополнительным расширениям классов String, int, num, Iterable, DateTime, File и других, которые используются буквально в каждом проекте. Разберем самые интересные функции.

🔸String
1️⃣ capitalize() и decapitalize() — изменят регистр только первой буквы предложения
2️⃣ isNotNullOrBlank и isNullOrBlank — проверят, есть ли читаемое содержимое, при этом не учитывая специальные символы по типу \n и пробелов
3️⃣ urlEncode и urlDecode — в строке преобразуют ссылку в формат application/x-www-form-urlencoded или обратно

final capitalizedText = 'пример заглавной буквы'.capitalize(); // Пример заглавной буквы
final isBlank = ' \n'.isNullOrBlank; // true
final decodedText = 'Пример%20декодирования'.urlDecode; // Пример декодирования


🔸Iterable
1️⃣ sortedBy() и thenBy() — позволят вам выполнить сортировку по нескольким признакам
2️⃣ chunkWhile() и splitWhen() — объединит в подсписки при выполнении или невыполнении условия

final dogs = [
Dog(name: "Charlie", age: 1),
Dog(name: "Bark", age: 3),
Dog(name: "Charlie", age: 6),
];
final sorted = dogs.sortedBy((dog) => dog.name).thenByDescending((dog) => dog.age); // Bark, Charlie (6), Charlie (3)
final chunckedList =[1, 2, 4, 9, 10, 11].chunkWhile((a, b) => a + 1 == b); // [[1, 2], [4], [9, 10, 11]]


🔸DateTime/Duration (эти функции можно подключить отдельно через пакет time)
1️⃣isAtSameYearAs(date) — проверит, находится ли текущая дата в рамках одного и того же года даты date в параметре (есть аналогичные проверки по месяцу и дню)
2️⃣ minutes.fromNow и minutes.ago — высчитает DateTime по указанной длительности в будущем и прошлом
3️⃣ hours — создаст Duration объект из целого числа

final tenMinutes = 10.minutes; // Duration(minutes: 10)
final isAtSameYear = DateTime(2025, 01, 01).isAtSameYearAs(DateTime(2020, 10, 05)); // false
final timeInFuture = 5.minutes.fromNow; // DatiTime.now() + 5 минут


Это только малая часть того, что умеет dartx!

❤️ — если уже пользуетесь пакетом
🔥 — если только сейчас открыли для себя его чудесные функции
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1566🥰1
This media is not supported in your browser
VIEW IN TELEGRAM
Всем привет! Это Роза, Flutter Dev в Friflex! 👋

Недавно я столкнулась с проблемой: во время поиска в моем приложении происходило что-то странное — оно начинало тормозить, и это раздражало. Каждое нажатие или ввод символа моментально триггерили действие, создавая нагрузку и снижая производительность.

И я нашла решение!

Дело в том, что во время поиска каждое мое нажатие инициировало отправку события на бэкенд и изменение состояния виджетов, тем самым перегружая UI. Чтобы этого избежать, я использовала Debounce.

Что такое Debounce?

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

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

Как это работает?

Debounce ставит таймер при каждом вызове функции
Если в течение этого времени приходит новое событие, таймер сбрасывается
Действие выполняется только тогда, когда пауза между событиями превышает заданное время

 Как использовать Debounce в коде?

Достаточно создать объект Debouncer и указать время задержки:

final _debouncer = Debouncer(milliseconds: 500);


А еще лучше — создать отдельный класс для управления Debounce:

import 'dart:async';

class Debouncer {
  final int milliseconds;
  Timer? _timer;

  Debouncer({required this.milliseconds});

  void run(VoidCallback action) {
    _timer?.cancel(); // Отменяем предыдущий таймер
    _timer = Timer(Duration(milliseconds: milliseconds), action); // Запускаем новый
  }

  void dispose() {
    _timer?.cancel(); // Очищаем ресурсы
  }
}


Каждый раз при вызове run()  действие выполняется не сразу, а только спустя указанное время.

Где использовать Debounce?

▶️ В поиске, чтобы не отправлять запрос на сервер при каждом символе  
▶️ В кнопках, чтобы избежать дублирующих API-запросов  
▶️ В валидации форм, чтобы не проверять ввод на каждом символе  
▶️ В анимации и UI, чтобы сглаживать обновления интерфейса

Почему это важно?

🔴Меньше запросов — API загружается только тогда, когда это действительно нужно  
🔴Более плавный UI — интерфейс не дергается при быстром вводе  
🔴Оптимизированная работа приложения — снижается нагрузка на процессор  
🔴Экономия трафика — меньше ненужных запросов к серверу

📌Важно: не забывай освобождать ресурсы и вызывать dispose() при уничтожении Debounce.

Согласитесь, иногда Debounce не хватает и в жизни💜
Please open Telegram to view this post
VIEW IN TELEGRAM
👍19🔥94
Рисуем как Пикассо, только на Flutter

Привет, это Катя, Flutter Dev Friflex. Flutter предоставляет мощные инструменты для работы с графикой, один из которых — CustomPainter.

Этот класс позволяет рисовать кастомные фигуры, линии, градиенты и другие элементы, которые невозможно создать стандартными виджетами. В этом посте рассмотрим, как использовать CustomPainter, разберем основные методы и попробуем нарисовать кастомную фигуру.

Основные принципы работы CustomPainter
CustomPainter работает в связке с CustomPaint, который отвечает за рендеринг на экране. 

CustomPainter переопределяет два метода:
🔴paint(Canvas canvas, Size size): содержит код отрисовки на canvas.
🔴shouldRepaint(CustomPainter oldDelegate): указывает, нужно ли перерисовывать объект при изменении состояния

Создание простого CustomPainter
Рассмотрим, как нарисовать круг с градиентной заливкой:
1️⃣ Наследуемся от CustomPainter, что позволяет переопределить метод paint, в котором выполняется отрисовка
2️⃣ Внутри метода paint создаем Paint — кисть для рисования
3️⃣ Используем shader для градиентной заливки — это задает радиальный градиент (от центра к краям), который переходит от синего к фиолетовому цвету
4️⃣ С canvasdrawCircle рисуем круг в центре с радиусом, равным половине ширины

import 'package:flutter/material.dart';

class GradientCirclePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..shader = RadialGradient(
        colors: [Colors.blue, Colors.purple],
      ).createShader(Rect.fromCircle(
        center: Offset(size.width / 2, size.height / 2),
        radius: size.width / 2,
      ));

    canvas.drawCircle(
      Offset(size.width / 2, size.height / 2),
      size.width / 2,
      paint,
    );
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}


Теперь используем CustomPaint, чтобы отобразить рисунок:

class GradientCircleWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: Size(200, 200),
      painter: GradientCirclePainter(),
    );
  }
}


Улучшение производительности

Чтобы избежать ненужных перерисовок, важно:
🔸 Указывать shouldRepaint как false, если рисование не меняется
🔸 Использовать RepaintBoundary, чтобы ограничить область перерисовки

RepaintBoundary(
  child: CustomPaint(
    size: Size(200, 200),
    painter: GradientCirclePainter(),
  ),
)


CustomPainter открывает широкие возможности для создания сложных графических элементов в Flutter. Он полезен для кастомных UI-решений, диаграмм, анимаций и визуализаций. Используйте его, когда стандартные виджеты не дают нужного результата.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1263👌2🏆1
Привет! С вами снова Анна, Friflex Flutter Team Lead.

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

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

Проще говоря, в императивном подходе маршруты собираются в единый стек. Для примера их можно представить стопкой тарелок. Когда роут добавляется, он складывается сверху (push). Когда вызывается возврат назад — самый верхний удаляется (pop).

Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
Navigator.pop(context);


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

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

Для лучшего понимания различий можно выделить вопросы для каждого из подходов:
🔴императивный — как выполнить переход и какие методы нужно вызвать для этого?
🔴декларативный — что нужно показать и какое текущее состояние навигации?

А вы какой подход чаще всего используете в своих проектах?
Please open Telegram to view this post
VIEW IN TELEGRAM
10🔥6👍3❤‍🔥1
Привет, с вами Роза, Flutter Dev Friflex👋 И сегодня мы немного погрузимся в магию FutureOr!

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

Лучше, если вы объявите метод, как FutureOr. FutureOr<T> — это такой хитрый тип в Dart, который говорит: «Эй, результат моего метода может быть либо обычным значением типа T, либо Future<T>, если вдруг придется подождать».

Звучит пока не очень понятно? Давайте разберемся на примерах.

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

import 'dart:async';

abstract class SomeService {
  FutureOr<String> fetch();
}

class FirstImplService extends SomeService {
  @override
  Future<String> fetch() async {
    await Future.delayed(Duration(seconds: 2));
    return 'Данные из Future';
  }
}

class SecondImplService extends SomeService {
  @override
  String fetch() {
    return 'Простые данные';
  }
}


Aбстрактный класс SomeService объявляет метод fetch() с типом возвращаемого значения FutureOr<String>. Это значит, что fetch() может вернуть либо String, либо Future<String>.

⚙️Когда же использовать FutureOr?
FutureOr — ваш спаситель, когда вам нужно абстрагироваться от того, является ли результат операции асинхронным или синхронным.

🔧Как обрабатывать FutureOr?
Самый простой способ — использовать проверку типа с помощью is Future.

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

У меня с работой так же. Иногда мне нужен await, чтобы подумать, а иногда все складывается супер. А у вас?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥1241👎1🙏1
Привет, с вами вновь Катя, Flutter Dev Friflex. Сегодня поговорим об extension.

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

Синатксис: основные правила

🔴Имя Extension — имя расширения (необязательно, но рекомендуется для читаемости)
🔴Тип — существующий тип, который расширяется (String, List, int и другие)

extension ИмяExtension on Тип {
  // Методы, геттеры, сеттеры
}


Пример без Extension
В этом случае мы создаем отдельную функцию для изменения строки:

String capitalize(String text) {
  if (text.isEmpty) return text;
  return text[0].toUpperCase() + text.substring(1);
}

void main() {
  print(capitalize('flutter'));  // Flutter
}


Минусы:
🔴Неинтуитивный вызов (capitalize(text)), несвойственный String
🔴Нужно передавать строку в функцию, что делает код менее читаемым
🔴Усложнение автокомплита в IDE, так как методы не привязаны к типу

С использованием Extension
Здесь метод capitalize становится частью String:

extension StringExtension on String {
  String capitalize() {
    if (isEmpty) return this;
    return this[0].toUpperCase() + substring(1);
  }
}

void main() {
  print('flutter'.capitalize());  // Flutter
}


Преимущества:
🔴Код становится лаконичным: text.capitalize() вместо capitalize(text)
🔴Лучшая читаемость и автодополнение
🔴Логика метода инкапсулирована в extension, а не в отдельной функции

Когда использовать Extension?
➡️Для расширения стандартных типов — когда нужно добавить удобные методы к String, List, DateTime и другим встроенным классам.

➡️Для инкапсуляции вспомогательной логики. Если часто используемая функция относится к конкретному типу, лучше оформить ее как метод через extension.

➡️Для упрощения работы с объектами — позволяет обращаться к данным через удобные геттеры или методы, избегая лишнего кода.

Ограничения Extension
🔸Нельзя добавлять новые поля в класс
🔸Нельзя переопределить существующие методы
🔸Расширения не наследуются, то есть нельзя создать extends для другого extension
🔸Конфликты: если два расширения имеют одинаковый метод, нужно явно указывать, какое расширение использовать

📎Если кратко: используйте расширения для инкапсуляции часто используемых методов и упрощения работы с базовыми типами в вашем проекте.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍8🍓43👻1
Всем привет! Это Анна, Friflex Flutter Team Lead.

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

🔗Глубокие ссылки (deeplinks) — это ссылки с кастомной схемой, которые позволяют не только открыть именно ваше приложение на устройстве, но и осуществить переход на вложенные маршруты внутри него. Например, ссылка app://product/id123 позволит открыть ваше приложение сразу на странице продукта с идентификатором id123.

🔗Universal links — это универсальные ссылки iOS приложений, которые имеют формат стандартной веб-ссылки, например, https://www.example.com. В случае, если приложение установлено, ссылки открывают его. Если нет — в браузере открывается веб-сайт, который связан с этой же ссылкой.

🔗App Links — это ссылки для Android, которые работают по принципу, идентичному Universial links на iOS.

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

Для получения ссылок используется экземпляр класса AppLinks:

final _instance = AppLinks();


С помощью этого объекта можно отследить, с какой конкретной ссылки запустили приложение.

Future<void> handleInitialLink() async {
final initialLink = await _instance.getInitialLink();
// обработка начальной ссылки
}


Кроме этого, в момент работы приложения в него из платформы также могут поступать различные ссылки. В таком случае плагин дает возможность получать эти ссылки потоком строк или объектов Uri.

final uriSubscription = _instance.uriLinkStream.listen((uri) {
// обработка ссылки в Uri формате
});

final srtringLinksSubscription = _instance.stringLinkStream.listen((stringLink) {
// обработка ссылки в String формате
});


У библиотеки хорошая репутация: почти 1 тысяча лайков и более 800 тысяч скачиваний.

Делитесь в комментариях своим опытом работы с app_links и с ссылками приложения в целом.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍97🔥5🍓3