Flutter Pulse
432 subscribers
284 photos
581 links
На канале будут новости про flutter с сайтов, информация об обновлении пакетов, а также авторский контент.
Download Telegram
💙🔥 Flutter 2025 Roadmap: Что нас ждет? 🔥💙

Команда Flutter снова радует нас долгожданным обновлением дорожной карты на 2025 год! 🚀 Как и прежде, Google открыто делится своими планами, а значит, самое время заглянуть в будущее экосистемы Flutter!

🔹 Производительность и Impeller – iOS окончательно прощается со Skia, а Impeller станет стандартом для Android 29+!
🔹 Web – улучшенная производительность, меньший размер приложений, WebAssembly и новый механизм JS interop.
🔹 Мобильные платформы – поддержка iOS 19 и Android 16, обновления для SwiftPM и Gradle.
🔹 Десктоп – Canonical продолжает развивать multi-window, поддержку клавиатуры и текстового ввода.
🔹 Dart & AI – новые языковые фичи, прокачанный кодоген и мощный AI-помощник для разработчиков.
🔹 Четыре стабильных релиза в 2025 – больше тестов, меньше багов!

🔥 Flutter становится мощнее, быстрее и доступнее! Следите за обновлениями и готовьтесь к новым возможностям! 🚀
Подробнее здесь

#Flutter #FlutterPulse #Dart #Impeller #MobileDev #WebDev #OpenSource
Flutter Pulse
У нас есть супер новость! 🤩 В 8 вечера сегодня начался стрим на Ютубе 📹, который продлится примерно час 🕰️! 🎉 Не пропустите эту возможность учиться и общаться с другими программистами! 🤝 Хотите узнать больше? 🤔 Тогда перейдите по ссылке и получите всю информацию!…
🚨 Flutter Roadmap 2025 — 🐦 больше не летает, а ползает
🕒 Почти час обсуждения, критики и немного философии от сообщества. Решил вам сделать краткий пересказ видео

💬 В этом видео разбирается не только новая дорожная карта Flutter, но и:
📉 Почему сообщество разочаровано
🧱 Проблемы с динамикой, сборкой и Web
💻 Слабая поддержка десктопа и SEO
🛠 Dart 3.8 и куда пропали макросы
🇷🇺 Форки от Яндекса и судьба Flock
🧠 Немного про курсы, книги и менторинг

🔥 Основные тезисы:
— Flutter всё ещё чувствует себя как «второсортный гражданин» в экосистеме Google
— Skia — больше не приоритет. А что вместо? 🤔
— Dart меняется, но не так, чтобы прям вау
— Веб-платформа застыла. SEO? Забудьте.
— Kotlin подбирается, а Canonical остаётся в стороне
— Много ожиданий, мало уверенности

🎓 Обсуждаются и образовательные инициативы: курс на Stepik, планы на лето и новая книга по конкурентной памяти Dart.

📽 Хотите посмотреть весь разбор? Вперед: "Flutter Roadmap на 2025 год сосёт" — звучит провокационно, но суть не в крике, а в аргументах.

#flutter #dart #roadmap2025 #разработка #mobiledev #frontend #webdev #каналотразработчиков 👨‍💻📱
📘🔥 "Flutter. Большая книга" – Всё, что вы хотели знать! 🔥📘
Вышел важнейший выпуск от авторов книги "Flutter. Большая книга" – финальный разговор перед релизом. Если ты хоть раз писал setState() — тебе точно стоит посмотреть это видео!
💡Что обсуждают:
Книга готова! Авторы поделились, как шёл процесс: что было сложно, что вдохновляло, и почему она не похожа ни на одну Flutter-книгу на русском (и не только).
Когда будет релиз и какие форматы запланированы: электронка, бумага, доступ на платформе — всё подробно.
Как поддержать проект — спойлер: можно не только деньгами, но и участием в комьюнити.
Честно о деньгах — сколько заработают авторы, и почему для них это не бизнес, а миссия.
Книга для кого? — будет полезна и джунам, и тем, кто готовит себя к роли тимлида. Есть лабораторный практикум, примеры, архитектурные подходы, опыт реальных проектов.
Книга vs. Видеокурс — почему выбрали именно формат книги и как правильно читать техлит, чтобы не засыпать.
Flutter-рынок, собесы, карьерный рост — много инсайтов из индустрии, честных мыслей и мотивации.
Крутая визуальная подача — дизайн книги радует глаз и помогает усваивать материал.
Да, будет версия с автографами. И да, можно будет купить за пределами РФ.
🔥 В этом видео — не просто анонс. Это манифест Flutter-разработки на русском языке. Настоящий разговор по душам.
#Flutter #FlutterPulse #FlutterКнига #Dart #MobileDev #Разработка #Карьера #Программирование #DDD #SoftwareDesign
Умный запрос оценки
Не спамите запросом оценки! 🤔

Почему?
• Apple Store ограничивает запрос оценки через нативное всплывающее окно до 2 раз в год 📆
• Google Play Store ограничивает запрос почти 2 раза каждые 3 месяца... 🤷‍♂️
• Вы не хотите раздражать пользователей и получать плохие оценки 😠

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


class RatingSettings {
final Duration delayBeforeAsking;
final Duration delayBeforeAskingAgain;

RatingSettings({
required this.delayBeforeAsking,
required this.delayBeforeAskingAgain,
});
}

class Rating {
final RatingApi _ratingApi;
final RatingSettings settings;
final DateTime? lastAskingDate;
final DateTime userCreationDate;
final DateTime _current;
final bool hasRateApp;

Rating({
required this.settings,
required RatingApi ratingApi,
required this.lastAskingDate,
required this.userCreationDate,
required this.hasRateApp,
}) : _ratingApi = ratingApi,
_current = DateTime.now();

bool shouldAsk() {
// Если пользователь уже оценил приложение или мы не знаем дату создания пользователя, пропустим
if (userCreationDate == null || hasRateApp) {
return false;
}

final appInstallDiff = _current.difference(userCreationDate);
if (appInstallDiff < settings.delayBeforeAsking) {
return false;
}

if (lastAskingDate == null) {
return true;
}
return lastAskingDate!.difference(_current) > settings.delayBeforeAskingAgain;
}

Future<void> openStoreListing() => _ratingApi.openStoreListing();
Future<void> showRatingDialog() => _ratingApi.showRatingDialog();
}



void showRatingPopup(WidgetRef ref) {
final ratingRepository = ref.watch(ratingRepositoryProvider);
final userState = ref.watch(userStateNotifierProvider);
final ratingFuture = ratingRepository.getUserState(user);

ratingFuture.then((rating) {
if (rating.shouldAsk()) {
rating.delay();
showDialog(
context: context,
builder: (context) => AlertDialog.adaptive(
title: const Text('Спасибо за использование ULY'),
content: const Text('У вас есть минута, чтобы оставить отзыв?'),
actions: [
TextButton(
child: const Text('Позже'),
onPressed: () async {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('Да, конечно!'),
onPressed: () async {
Navigator.of(context).pop();
rating.rate();
},
),
],
),
);
}
});
}


Вы можете хранить RatingSettings в Firebase Remote Config, чтобы регулировать эти настройки без пересборки приложения и находить оптимальные значения 🔧

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

Оцените новую рубрику! 👍👎
Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #SmartRating #RatingSystem #UXTips
Как показать уведомление Toast с Riverpod
Избавляемся от требования BuildContext

При разработке Flutter-приложений часто возникает необходимость отображать уведомления или сообщения Toast. Использование Riverpod для управления состоянием приложения позволяет упростить этот процесс. В этой статье мы рассмотрим, как показать уведомление Toast, используя Riverpod, и избавимся от необходимости передавать BuildContext.

Для начала нам нужно создать провайдер, который будет отвечать за отображение Toast-уведомлений. Мы будем использовать пакет another_flushbar для отображения уведомлений.


import 'package:another_flushbar/flushbar.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Это наш провайдер из riverpod для вызова метода toast из любой функции riverpod
final toastProvider = Provider<ToastBuilder>((ref) => ToastBuilder());

class ToastBuilder {
void success({
required String title,
required String text,
}) {
// Продолжительность показа уведомления
Duration duration = const Duration(seconds: 3);
_showSuccessToast(
context: navigatorKey.currentContext!, // Используем глобальный ключ навигации
title: title,
text: text,
duration: duration,
);
}

void _showSuccessToast({
required BuildContext context,
required String title,
required String text,
required Duration duration,
}) {
// Хак, чтобы предотвратить показ toast во время тестов
if (Platform.environment.containsKey('FLUTTER_TEST')) {
return;
}
if (!context.mounted) {
return; // Проверяем, смонтирован ли контекст
}

// Используем пакет another_flushbar, но есть много других отличных решений
Flushbar(
flushbarPosition: FlushbarPosition.TOP,
title: title,
message: text,
// Здесь можно добавить стили
).show(context);
}
}


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

/// Не забудьте создать этот глобальный ключ и добавить его в ваш основной маршрутизатор
final navigatorKey = GlobalKey<NavigatorState>();


Теперь мы можем использовать наш провайдер toastProvider в любом месте приложения, где доступен Riverpod, чтобы показать Toast-уведомление:

import 'package:flutter_riverpod/flutter_riverpod.dart';

@Riverpod(keepAlive: false)
class EditAlbumNotifier extends _$EditAlbumNotifier {
// ...

Future<void> save() async {
// ...
// Например, после сохранения альбома
ref.read(toastProvider).success(
title: "Успех",
text: "Журнал архивирован",
);
}
}


Таким образом, мы можем легко отображать Toast-уведомления в нашем Flutter-приложении, используя Riverpod и пакет another_flushbar, а также избавиться от необходимости передавать BuildContext, используя глобальный ключ навигации.

Оцените новую рубрику и напишите в комментариях, что бы вы хотели видеть в следующих выпусках! 👍💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #Riverpod #ToastNotifications #FlutterTips
Как открыть страницу настроек приложения в системе
Привет, друзья! 👋 Сегодня мы поделимся с вами полезным советом о том, как открыть страницу настроек вашего приложения на устройстве пользователя. 📱💻

Зачем это нужно? 🤔
Например, если пользователь отклонил уведомления, система больше не будет запрашивать разрешение. В этом случае вы можете помочь пользователю перейти в настройки и включить уведомления вручную. 🔔

Используем url_launcher 📲

import 'package:url_launcher/url_launcher.dart';

Future<void> openSettings() async {
Uri url;
if (defaultTargetPlatform == TargetPlatform.iOS) {
url = Uri.parse('app-settings:');
} else if (defaultTargetPlatform == TargetPlatform.android) {
url = Uri.parse('package:uly.vlog.diary');
} else {
throw Exception('Unsupported platform');
}

if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
throw 'Could not launch $url';
}
}


Или проще с permission_handler 📦

import 'package:permission_handler/permission_handler.dart';

openAppSettings();



Оцените новую рубрику и напишите в комментариях, что вы думаете о ней! 💬 Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #ProgrammingTips #CodingTips
Flutter tips: how to create a responsive layout
Адаптивный макет: советы по Flutter

Привет, разработчики! 👋 Сегодня мы поговорим о создании адаптивного макета в Flutter. 📱💻

Адаптация макета под разные размеры экранов 📐
Чтобы ваше приложение выглядело отлично на разных устройствах, необходимо адаптировать макет под различные размеры экранов. 📊


import 'package:flutter/widgets.dart';

// Перечисление типов устройств
enum DeviceType { small, medium, large, xlarge }

// Виджет, который адаптирует свой контент к текущему типу устройства
class ResponsiveLayout extends StatelessWidget {
final Widget? small; // Виджет для маленьких экранов
final Widget? medium; // Виджет для средних экранов
final Widget? large; // Виджет для больших экранов
final Widget? xlarge; // Виджет для очень больших экранов

const ResponsiveLayout({
super.key,
required this.small,
this.medium,
this.large,
this.xlarge,
});

@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// Определение типа устройства на основе ширины экрана
switch (getDeviceType(constraints)) {
case DeviceType.small:
return small!; // Возвращаем виджет для маленьких экранов
case DeviceType.medium:
return medium ?? small!; // Возвращаем виджет для средних экранов или fallback к маленькому
case DeviceType.large:
return large ?? medium ?? small!; // Возвращаем виджет для больших экранов или fallback
case DeviceType.xlarge:
return xlarge ?? large ?? medium ?? small!; // Возвращаем виджет для очень больших экранов или fallback
}
},
);
}

// Метод для определения типа устройства на основе ограничений
DeviceType getDeviceType(BoxConstraints constraints) {
return switch (constraints.maxWidth) {
> 1200 => DeviceType.xlarge, // Очень большой экран
> 1024 => DeviceType.large, // Большой экран
> 768 => DeviceType.medium, // Средний экран
_ => DeviceType.small, // Маленький экран
};
}
}


Пример использования:

ResponsiveLayout(
small: SigninForm(), // Форма входа для маленьких экранов
medium: Center( // Центрирование для средних экранов и больше
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 600), // Ограничение максимальной ширины
child: SigninForm(), // Форма входа
),
),
)


Этот код позволяет создать адаптивный макет, который будет корректно отображаться на различных устройствах. 📱💻

Оцените новую рубрику и напишите своё мнение в комментариях! 💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #UIUX #ProgrammingTips #CodingTips
Flutter tips: Расширения для работы с датами

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

Использование расширений для дат

Расширения в Dart позволяют добавлять новые методы к существующим классам. В данном случае мы будем использовать расширение для класса DateTime, чтобы добавить полезные методы для работы с датами.

extension DateExtension on DateTime {
DateTime get firstDayOfWeek {
return subtract(Duration(days: weekday - 1));
}

DateTime get lastDayOfWeek {
return add(Duration(days: 7 - weekday));
}

DateTime get firstDayOfMonth {
return DateTime(year, month);
}

bool isToday() => isSameDay(DateTime.now());

bool isSameDay(DateTime other) {
return day == other.day && month == other.month && year == other.year;
}

bool isAfterDayOrEqual(DateTime other) {
return isAfter(other) || isSameDay(other);
}

bool isBeforeDayOrEqual(DateTime other) {
return isBefore(other) || isSameDay(other);
}

bool isSameWeek(DateTime other) {
final kfirstDayOfTheWeek = firstDayOfWeek;
return other.isAfterDayOrEqual(kfirstDayOfTheWeek) &&
other.isBefore(lastDayOfWeek);
}
}


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

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

if (!activities.hasActivity(date) && date.isToday()) {
// код
}


Это намного чище и читаемее, чем:

final today = DateTime.now();
if (!activities.hasActivity(date) && date.day == today.day && date.month == today.month && date.year == today.year) {
// код
}


Оцените новую рубрику! Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #CodingTips #AppDev #FlutterTips
🎯 Flutter совет: Шаблон подписки с Provider

Управление подпиской через Provider — один из самых чистых и масштабируемых способов построения архитектуры подписок в Flutter. Ниже — полный шаблон, который можно адаптировать под RevenueCat, Qonversion, Firebase и другие платформы.

---

🔧 Модель состояния подписки

class SubscriptionModel extends ChangeNotifier {
  bool _isSubscribed = false;
  DateTime? _expiryDate;

  bool get isSubscribed => _isSubscribed;
  DateTime? get expiryDate => _expiryDate;

  void updateStatus({required bool subscribed, DateTime? expiry}) {
    _isSubscribed = subscribed;
    _expiryDate = expiry;
    notifyListeners();
  }

  void reset() {
    _isSubscribed = false;
    _expiryDate = null;
    notifyListeners();
  }

  bool get isExpired {
    if (_expiryDate == null) return true;
    return DateTime.now().isAfter(_expiryDate!);
  }
}

---

🧩 Регистрация провайдера в main.dart

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => SubscriptionModel(),
      child: const MyApp(),
    ),
  );
}

---
Использование подписки в UI

Consumer<SubscriptionModel>(
  builder: (context, model, _) {
    if (model.isSubscribed) {
      return const PremiumContent();
    }

    return Column(
      children: [
        const Text("Оформите подписку для доступа к премиум-функциям"),
        ElevatedButton(
          onPressed: () => handleSubscription(context),
          child: const Text("Подписаться"),
        ),
      ],
    );
  },
)

---
Пример обработки подписки

Future<void> handleSubscription(BuildContext context) async {
  final model = context.read<SubscriptionModel>();

  final result = await PurchaseService.buy(); // ваша реализация

  if (result.success) {
    model.updateStatus(
      subscribed: true,
      expiry: result.expiryDate,
    );
  } else {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text("Не удалось оформить подписку")),
    );
  }
}

---

💡 Совет: Периодически проверяйте подписку при запуске приложения — особенно если используете серверную валидацию или Webhook-обновления.

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

📌 Все советы рубрики — по хэштегу #FlutterPulseTips

#flutter #dart #provider #architecture #subscriptions #revenuecat #qonversion #boilerplate #mobiledev #FlutterPulseTips #UIUX #premiumapps
AnimatedSwitcher с Riverpod: Анимируем текст при каждом изменении

Привет, разработчики Flutter! 👋 Сегодня мы рассмотрим интересный совет по использованию AnimatedSwitcher вместе с Riverpod для создания анимированных изменений текста. 📱💡

Что такое AnimatedSwitcher?

AnimatedSwitcher(
// Виджет, который по умолчанию делает плавное переключение между новым виджетом и предыдущим
duration: const Duration(milliseconds: 250), // Продолжительность анимации
switchInCurve: Curves.easeInOut, // Кривая анимации при появлении
transitionBuilder: (Widget child, Animation<double> animation) {
// Строитель анимации перехода
return ScaleTransition(scale: animation, child: child);
},
child: Text(
'MyText', // Текст, который будет отображаться
key: Key('My-${state.value?.currentStepIndex}'), // Ключ, который запускает анимацию при изменении
style: TextStyle(fontSize: 24), // Стиль текста
),
)


Как это работает?
1. Мы используем AnimatedSwitcher для обёртки нашего текстового виджета.
2. Ключ (Key) играет решающую роль. Когда значение состояния меняется, ключ обновляется, запуская анимацию.
3. transitionBuilder позволяет настроить тип анимации. В примере используется ScaleTransition.



class SessionExerciceTitle extends ConsumerStatefulWidget {
const SessionExerciceTitle({super.key});

@override
ConsumerState<ConsumerStatefulWidget> createState() => _SessionExerciceTitleState();
}

class _SessionExerciceTitleState extends ConsumerState<SessionExerciceTitle> {
Key titleKey(AsyncValue<UserSessionState> state) =>
ValueKey<String?>("My-${state.value?.currentStepIndex}");

@override
Widget build(BuildContext context) {
final state = ref.watch(userSessionNotifierProvider); // Следим за изменениями состояния
return AnimatedSwitcher(
duration: const Duration(milliseconds: 250), // Продолжительность анимации
switchInCurve: Curves.easeInOut, // Кривая анимации при появлении
transitionBuilder: (Widget child, Animation<double> animation) {
// Строитель анимации перехода
return ScaleTransition(scale: animation, child: child);
},
child: Text(
state.value?.myText ?? '', // Отображаемый текст
key: titleKey(state), // Ключ, который запускает анимацию при изменении состояния
textAlign: TextAlign.center, // Выравнивание текста
),
);
}
}



Почему это полезно?
- Плавные переходы улучшают пользовательский опыт 🌟
- Легко реализуется с помощью Riverpod и AnimatedSwitcher 👍

Оцените новую рубрику и напишите в комментариях, какие темы вам интересны! 💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #UIUX #Animation #RiverpodTips #FlutterTips
Автоматическое создание новой версии в Apple Store
Перестаньте тратить время на заполнение метаданных для каждого языка!

Когда это полезно?
Каждый раз, когда вы публикуете новую версию приложения в Apple Store Connect, вам приходится заполнять 2 поля для каждого языка. Но вы также можете использовать это для обновления других полей метаданных.

Шаги для автоматизации процесса:

1. Установите fastlane.
2. Инициализируйте fastlane в папке iOS вашего проекта:
fastlane init

3. Настройте учетные данные Apple Store Connect в файле Appfile:

app_identifier("app.your.app.id") # Идентификатор пакета вашего приложения
apple_id("your@email.com") # Ваша учетная запись Apple ID
team_id("TEAM_ID") # ID команды Apple Store Connect
itc_team_name("TEAM_NAME") # Имя команды в App Store Connect


4. Добавьте следующий код в файл Fastfile:

default_platform(:ios)

platform :ios do
desc "Скачать метаданные из App Store Connect"
lane :download_metadata do
# Код для скачивания метаданных
end

desc "Создать новую версию в App Store Connect"
lane :release_new_version do |options|
# Код для создания новой версии
deliver(
app_version: options[:version],
skip_binary_upload: true,
force: true,
submit_for_review: false,
automatic_release: false,
metadata_path: "fastlane/metadata",
release_notes: "fastlane/metadata/#{options[:version]}/release_notes.txt",
skip_screenshots: true
)
end
end


Использование:
1. Скачайте метаданные:
fastlane deliver download_metadata

2. Создайте новую версию с предварительно заполненными данными для всех языков:
fastlane release_new_version version:"3.12.3"


Оцените новую рубрику и напишите свое мнение! 😊👍

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppStore #Fastlane #DevTips #ProductivityTips
🔥 Liquid Glass Renderer — эффект «жидкого стекла» теперь и во Flutter!

Новый пакет liquid_glass_renderer позволяет Flutter-разработчикам воссоздать визуальный стиль, вдохновлённый новым языком дизайна Apple — Liquid Glass, представленным на WWDC 2025 вместе с iOS 26, macOS Tahoe и другими платформами.

Что такое Liquid Glass?

Liquid Glass — это современный визуальный стиль с эффектами стеклянности, динамической прозрачности, глубины и плавного взаимодействия слоёв. Его основа — реалистичное поведение света и стекла в интерфейсах.

Теперь и во Flutter:

🧊 Эффект жидкого стекла: просто оберни любой виджет в LiquidGlass
🧬 Слои, которые сливаются между собой как настоящая жидкость
🎨 Гибкая настройка — толщина, цвет, освещение, блики, размытие
⚡️ Высокая производительность благодаря поддержке Impeller и шейдерам

Пример:

LiquidGlass(
shape: LiquidRoundedSuperellipse(
borderRadius: Radius.circular(50),
),
child: const SizedBox(
height: 200,
width: 200,
child: Center(child: FlutterLogo(size: 100)),
),
)


Важно:

Работает только на Impeller (поддержка Web, Windows, Linux — пока нет)

Поддерживается до 3 стеклянных фигур в одном LiquidGlassLayer

Установка:

flutter pub add liquid_glass_renderer


Импорт:

import 'package:liquid_glass_renderer/liquid_glass_renderer.dart';


Оцените эффект Liquid Glass сами — теперь вы можете создавать интерфейсы нового уровня прямо во Flutter!

#flutter #dart #liquidglass #design #WWDC2025 #FlutterUI #FlutterEffects #MobileDev #AppDesign #flutterpulse
Использование ИИ в вашем приложении с помощью Gemini

Привет, разработчики Flutter! 🤖💻 Сегодня мы расскажем, как добавить Gemini в ваше приложение с помощью Firebase Functions безопасным способом 🔒.

Почему не стоит использовать плагин flutter_gemini?

Из соображений безопасности я настоятельно предпочитаю не раскрывать свой ключ API Gemini в приложении 🔑.

Шаги по интеграции Gemini с Firebase:

1. Создайте новый проект Firebase с помощью команды firebase init genkit или установите необходимые зависимости 📦.
- Установите следующие пакеты:
- @genkit-ai/ai
- @genkit-ai/core
- @genkit-ai/dotprompt
- @genkit-ai/firebase
- @genkit-ai/flow
- @genkit-ai/googleai
- zod

2. Установите genkit глобально: npm install -g genkit 🌐.

3. Инициализируйте gemini, используя configureGenkit 🔧.



import { initializeApp } from "firebase/app";
import { defineString } from "firebase-functions/params";
import { configureGenkit } from '@genkit-ai/core';
import { firebase } from '@genkit-ai/firebase';

defineString('GOOGLE_GENAI_API_KEY');
const firebaseApp = initializeApp();

configureGenkit({
plugins: [
firebase(),
googleAI({ apiKey: defineString('GOOGLE_GENAI_API_KEY') }),
],
// ...
enableTracingAndMetrics: true,
});



Добавьте ключ API в файл .env и не забудьте удалить .env из .gitignore, иначе Firebase не сможет его использовать 🚫.



export const suggestionFlow = onFlow({
name: "suggestionFlow",
httpsOptions: { cors: true },
region: "europe-west1",
inputSchema: z.object({ uid: z.string(), query: z.string(), language: z.string() }),
outputSchema: z.string(),
authPolicy: (auth, input) => {
// Проверка аутентификации пользователя
if (auth.uid != input.uid) {
throw new Error("You can only access your own data");
}
// Проверка поддерживаемого языка
if (input.language != "fr" && input.language != "en") {
throw new Error("Only French and English are supported for now");
}
// Разрешить доступ только аутентифицированным пользователям
return !!auth.uid;
},
}, async (input) => {
const exercicesContext = JSON.stringify(exercices);
const userLanguage = input.language;
const prompt = `You are an AI assistant that helps users with XXX. You will speak in the user's language: $userLanguage.
You will answer with the JSON format below:
{"days": [{"day": number, "steps": {...}, "description": string}]}
Here is the user query: ${input.query}`;

const llmResponse = await generate({
model: gemini15Flash,
prompt,
config: {
temperature: 1,
maxOutputTokens: 3000,
},
});

return llmResponse.text();
});



Разверните эту функцию на Firebase с помощью команды firebase deploy --only functions 🚀.

Вызов функции из Flutter:


Future<String> fetchStretchingSuggestionFlow(
String uid,
String query,
String userLanguage,
) async {
final callable = FirebaseFunctions.instance.httpsCallable('suggestionFlow');
final result = await callable.call<String>({
'uid': uid,
'query': query,
'language': userLanguage,
});
final data = (await decode(result.data)) as Map<String, dynamic>;
return AIexercice.fromJson(data);
}



Оцените нашу новую рубрику и оставьте свои отзывы! 😊👍

Все подобные новости можно найти по хэштегу #FlutterPulseTips.

#flutter #dart #flutterpulse #FlutterPulseTips #Firebase #Gemini #AI #FlutterTips #MobileDev #AppDev
🔧 DevScreen на Flutter: свой путь к инструменту для логирования, тестирования и смены окружений

🗣 Спикер: Павел, Flutter-разработчик с 10+ годами опыта, один из первых пользователей Flutter в РФ, соавтор книги «Основы Flutter», лектор СФУ и активный участник комьюнити.

На конференции он представил разработку своей команды — DevScreen, универсальный внутренний инструмент для мобильной отладки, логирования, диагностики и смены окружений.
🔎 Зачем нужен DevScreen?

В боевой разработке часто не хватает:

🎛 экрана с настройками (окружения, прокси)

🧾 доступа к логам прямо в приложении

🐞 удобного сбора стектрейсов для QA

📉 отладки UI, FPS, границ, состояния виджетов

🧪 триггера запуска debug-инструментов без пересборки

💬 «Качество — это ответственность всей команды», — говорит Павел. А значит, и тестировщики, и дизайнеры, и разработчики должны иметь доступ к полезной внутренней информации прямо в приложении.
🧰 Что уже есть на рынке?

Рассмотрели 2 популярных решения:
1. Ume

умеет логировать запросы
показывает FPS, границы виджетов, device info
не умеет работать с окружениями и прокси
нет нормального логирования ошибок
триггер ручной (нужно писать самому)

💡 Хорош для дизайнеров и UI-отладки, но слабо помогает QA и не масштабируется под все нужды.

2. Talker

готовый экран логов
отличное логирование (включая ошибки, кастомные события, API-запросы)
удобен и в приложении, и в консоли
не умеет менять окружение и прокси
триггер — тоже руками
нет UI-отладки

💬 «Talker — это скорее логер, чем полноценный DevScreen. Мы хотели большего».


Так родилась идея своего DevScreen MVP, который:
🔓 открывается по shake-жесту или скрытой зоне (не мешает пользователю)
🌐 позволяет менять окружения, добавлять прокси
🧾 логирует ошибки, API-запросы, аналитику и кастомные события
🔄 сбрасывает кэш, симулирует логаут, показывает device info
🛠 кастомизируется под проект, легко расширяется
🔐 имеет авторизацию (доступ по роли или логину)
🧪 отделён от боевого UI, не ломает UX обычного пользователя
⚙️ поддерживает показ границ виджетов, FPS, и другую системную инфу

💡 Всё настраивается модульно. Хочешь только прокси? Подключай только его. Нужны только логи? Без проблем.

🤝 Кто пользуется внутри?
Разработчики — смотрят логи, стектрейсы, ошибки
Тестировщики — не пишут баг на глаз, а прикладывают API-ответ и stacktrace
Дизайнеры — проверяют реализацию UI прямо в приложении (границы, цвета, размеры)

🚀 Что дальше?

В планах:
🧩 сделать DevScreen переиспользуемым пакетом
🛡 вынести доступ к нему за фичефлаг
🧪 покрыть всё юнит- и интеграционными тестами
📢 возможно — опубликовать в open-source, если будет спрос

📺 Полное видео доклада (YouTube):
📌 Такой подход реально экономит часы работы QA и разработчиков, а главное — повышает качество продукта уже на уровне debug. Всё прозрачно, логируемо и воспроизводимо.

🧵 #flutter #flutterpulse #debug #talker #devtools #qa #мобилкарф #тестирование #логирование #devscreen #mobiledev #dart #flutterмного #конференция
Проверка статуса продления подписки пользователя

Привет, Flutter-разработчики! 👋 Сегодня мы рассмотрим полезный совет по проверке статуса продления подписки пользователя с помощью RevenueCat. 📈

Зачем проверять статус продления подписки?

Проверка статуса продления подписки позволяет вам:
Показывать промо-офферы перед тем, как потерять пользователя
Управлять доступом к premium-функциям вашего приложения

Пример кода на Dart:


Future<bool> hasRenewal() async {
final customerInfo = await Purchases.getCustomerInfo();
final entitlements = customerInfo.entitlements.active.values.firstOrNull;
return entitlements?.willRenew ?? false;
}


В этом коде мы:
1. Получаем информацию о покупателе с помощью `Purchases.getCustomerInfo()`
2. Извлекаем активные права доступа (`entitlements`)
3. Проверяем, будет ли подписка продлена (`willRenew`)

Используйте эту информацию, чтобы улучшить удержание пользователей! 📊

Оцените новую рубрику и напишите в комментариях, какие темы вам интересны! 🤔

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #SubscriptionManagement #RevenueCat #FlutterTips
Обработка ошибок с Future
Catch them all 🎯

Неправильная функция

Future<void> functionInError() async {
throw "I am failing"; // Выбрасываем ошибку
}


Неправильный способ обработки

functionInError()
.catchError((err) => print("error catched")) // Перехватываем ошибку
.then(
(value) => print("success"), // Успешное выполнение
onError: (err) => print("I failed"), // Обработка ошибки
);

// Результат:
// error catched
// success


catchError перехватывает ошибку, но не возвращает её. Поэтому then выведет success вместо "I failed".

Правильный способ обработки

functionInError()
.catchError((err) {
print("error catched"); // Печатаем сообщение об ошибке
throw err; // Пробрасываем ошибку дальше
})
.then(
(value) => print("success"), // Успешное выполнение
onError: (err) => print("I failed"), // Обработка ошибки
);

// Результат:
// error catched
// I failed


Лучше обрабатывать ошибки в колбэке onError. Но если хотите цеплять функции друг за другом, не забудьте пробросить ошибку дальше.

Цепочка нескольких Future и обработка ошибок

void main() {
functionInError()
.then((res) => workingFuture()) // Продолжаем цепочку
.then((res) => print("ended")) // Завершаем цепочку
.onError((err) => print("error catched")); // Обрабатываем ошибку
}

Future<void> functionInError() async {
throw "I am failing"; // Выбрасываем ошибку
}

Future<void> workingFuture() async {
print("working future"); // Печатаем сообщение о работе
}

// Результат:
// error catched


Ошибка передаётся в последний onError.

Или обработка ошибок через try-catch с await

main() async {
try {
await functionInError(); // Ждём завершения функции
} catch (err) {
print("I failed"); // Обрабатываем ошибку
}
}


Оцените новую рубрику! 👍💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #Future #ErrorHandling #FlutterTips #MobileDev #CodingTips
Отслеживание видимости клавиатуры
Без каких-либо плагинов 😉

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

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

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



import 'package:flutter/material.dart';

// состояние клавиатуры
enum KeyboardVisibilityState { visible, hidden }

// определение функции слушателя
typedef OnKeyboardStateChanged = void Function(KeyboardVisibilityState state);

// stateful виджет для отслеживания изменения видимости клавиатуры
class KeyboardVisibility extends StatefulWidget {
final Widget child;
final OnKeyboardStateChanged onKeyboardStateChanged;

const KeyboardVisibility({
super.key,
required this.child,
required this.onKeyboardStateChanged,
});

@override
State<KeyboardVisibility> createState() => _KeyboardVisibilityState();
}

class _KeyboardVisibilityState extends State<KeyboardVisibility> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

@override
void didChangeMetrics() {
super.didChangeMetrics();
checkState();
}

void checkState() {
final value = WidgetsBinding.instance.platformDispatcher.views.first.viewInsets.bottom;
switch (value != 0.0) {
case true:
widget.onKeyboardStateChanged(KeyboardVisibilityState.visible);
case false:
widget.onKeyboardStateChanged(KeyboardVisibilityState.hidden);
}
}

@override
Widget build(BuildContext context) {
return widget.child;
}
}



Использование:
Виджет KeyboardVisibility можно использовать следующим образом:



KeyboardVisibility(
onKeyboardStateChanged: (state) => _showOrHideBottomAction(state),
child: Form(...),
)



Оцените новую рубрику и напишите свое мнение в комментариях! 👍

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #UIUX #CodingTips #AppDev #DevTips
Создание изображения из виджета

Привет, Flutter-разработчики! 👋 Сегодня мы рассмотрим интересную и полезную тему: создание изображения из любого виджета в вашем приложении. 📸

Шаг 1: Добавьте RepaintBoundary поверх виджета, который хотите экспортировать

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



final GlobalKey _repaintBoundaryKey = GlobalKey();

RepaintBoundary(
key: _repaintBoundaryKey,
child: CustomPaint(
painter: MyPainter(),
...
),
)



Этот ключ позволит нам найти объект рендеринга и вызвать метод toImage. 📚 Для более глубокого понимания можно изучить, что такое Widget tree, Element tree и RenderObject tree.

Шаг 2: Вызовите метод для экспорта изображения в галерею телефона

Теперь напишем функцию, которая будет экспортировать изображение:



import 'package:image_gallery_saver/image_gallery_saver.dart';

Future<void> exportImage() async {
try {
RenderRepaintBoundary boundary = _repaintBoundaryKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
ui.Image image = await boundary.toImage(pixelRatio: 3.0);
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData!.buffer.asUint8List();

final directory = await getDownloadsDirectory();
final imgName = 'myapp-${DateTime.now().microsecondsSinceEpoch}';
final result = await ImageGallerySaver.saveImage(pngBytes, quality: 100, name: imgName);

if (result['isSuccess']) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Изображение экспортировано')),
);
}
} catch (e) {
// Обработайте ошибку (покажите сообщение пользователю, сохраните отчет о сбое)
}
}



Мы используем пакет image_gallery_saver, чтобы сохранить изображение в галерею телефона. 📁📸

Оцените новую рубрику и напишите в комментариях, что хотите видеть в следующих постах! 💬

Все подобные новости можно найти по хэштегу #FlutterPulseTips

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #CodingTips #UIUX #FlutterTips