Flutter Pulse
501 subscribers
313 photos
666 links
На канале будут новости про flutter с сайтов, информация об обновлении пакетов, а также авторский контент.
Download Telegram
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
👍5
🎯 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
🔥4
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
👍2
Автоматическое создание новой версии в 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
🔥3
🔥 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
🔥1
Использование ИИ в вашем приложении с помощью 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
👍3
Обработка ошибок с 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
👍1
Отслеживание видимости клавиатуры
Без каких-либо плагинов 😉

В разработке 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
1👍1
Создание изображения из виджета

Привет, 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
🔥1
Приостановка потока, когда нет подписчиков

Не теряйте события, когда некому их слушать 😉

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

Пример реализации:



// Этот класс отвечает за рассылку уведомлений приложению
// Он также отвечает за прослушивание уведомлений
class AppEventsDispatcher {
final StreamController<AppEvent> _controller;
late final Stream<AppEvent?> _stream;

Stream<AppEvent?> get stream => _stream;

final List<AppEvent> _onNotificationEventsSubscriber;

AppEventsDispatcher()
: _onNotificationEventsSubscriber = [],
_controller = StreamController() {
_stream = _controller.stream.asBroadcastStream(
onCancel: (c) => c.pause(),
onListen: (el) {
if (el.isPaused) {
el.resume();
}
},
);
}

void dispose() {
_onNotificationEventsSubscriber.clear();
_controller.close();
}

void publish(AppEvent event) {
_controller.add(event);
}
}

final dispatcher = AppEventsDispatcher();
// Публикуем событие
dispatcher.publish(AppEvent('Новое уведомление'));
// Ждём немного
await Future.delayed(Duration(seconds: 1));
// Подписываемся на поток
final subscription = dispatcher.stream.listen((event) {
print('Получено событие: $event');
// так как мы приостановили поток, мы получим события
});



В этом примере мы создаём класс AppEventsDispatcher, который управляет потоком событий AppEvent. Когда нет подписчиков, поток приостанавливается, а при появлении нового подписчика - возобновляется с последнего события.

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

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #StreamManagement #AppDevelopment #CodingTips
👍2
Canvas с GestureDetector: обработка событий только в пределах радиуса

Привет, Flutter-разработчики! 👋 Сегодня мы поделимся с вами полезным советом о том, как использовать GestureDetector с CustomPaint для обработки жестов только в определенной области. 📱💡

Вы когда-нибудь сталкивались с ситуацией, когда ваш CustomPaint должен реагировать на жесты только в определенной области? Например, вам нужно, чтобы нажатие обрабатывалось только если оно произошло в пределах определенного радиуса вокруг объекта? 🔍

Для этого можно использовать метод hitTest в вашем CustomPainter. Вот пример кода:


class WorldPainter extends CustomPainter {
...
bool hitTest(Offset position) {
// вычисляем расстояние от позиции до нужной точки
var distance = position.distanceTo(this.location);
// возвращаем true, если расстояние меньше радиуса обнаружения
return distance < detectionRadius;
}
...
}



Затем оберните ваш CustomPaint в GestureDetector:


class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => print("do what you want"), // действие при нажатии
child: CustomPaint(
size: Size.infinite, // размер canvas
painter: WorldPainter(), // ваш кастомный painter
),
);
}
}



Таким образом, событие нажатия будет обрабатываться только если оно произошло в пределах заданного радиуса. 🔝

Оцените нашу новую рубрику и оставьте свои комментарии! 💬 Все подобные новости вы можете найти по хэштегу #FlutterPulseTips. 👉 #flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #UIUX #FlutterTips #CodingTips #AppDevelopment
Расширение цвета из шестнадцатеричной строки
Привет, разработчики Flutter! 👋 Сегодня мы рассмотрим полезный совет, который упростит работу с цветами в ваших приложениях. 🌈

Проблема: Часто цвета в приложениях представлены в виде шестнадцатеричных строк (например, "#FFFFFF" или "FF0000"). Но как легко преобразовать эти строки в объекты Color, с которыми можно работать в Flutter? 🤔

Решение: Мы создадим расширение для класса Color, которое позволит легко конвертировать шестнадцатеричные строки в цвета. 💡


extension HexColor on Color {
static Color fromHex(String hexString) {
final buffer = StringBuffer(); // Создаем буфер для формирования итоговой строки
if (hexString.length == 6 || hexString.length == 7) // Проверяем длину строки
buffer.write('ff'); // Если длина 6 или 7, добавляем 'ff' (полная непрозрачность)
buffer.write(hexString.replaceFirst('#', '')); // Удаляем символ '#' из строки
return Color(int.parse(buffer.toString(), radix: 16)); // Преобразуем строку в число и создаем цвет
}
}


Как это работает:
1. Мы проверяем длину строки: если она 6 или 7 символов, добавляем 'ff' для полной непрозрачности.
2. Удаляем символ '#' из строки, если он есть.
3. Преобразуем полученную строку в число в шестнадцатеричной системе и создаем объект Color.

Теперь вы можете легко использовать шестнадцатеричные строки для создания цветов в вашем приложении Flutter! 🎉

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDevelopment #CodingTips #ColorUtils
👍2
Триггер событий при навигации по страницам с помощью Flutter RouteObserver

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

Что такое RouteObserver?
`RouteObserver` - это класс, позволяющий отслеживать изменения маршрутов в вашем приложении. Он предоставляет методы для реакции на различные события навигации, такие как открытие новой страницы или возврат к предыдущей. 🔄

Пример использования:
Чтобы использовать `RouteObserver`, вам нужно создать экземпляр класса, наследующего от `RouteObserver<PageRoute<dynamic>>`, и переопределить нужные методы. Ниже приведён пример кода:



class MyNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
// Код, выполняемый при открытии новой страницы
}

@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
// Код, выполняемый при замене маршрута
}

@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
// Код, выполняемый при закрытии текущей страницы
}
}



Затем добавьте этот наблюдатель в ваше `MaterialApp`:



class MyApp extends StatelessWidget {
final _navigatorKey = GlobalKey<NavigatorState>();
final navObserver = MyNavigatorObserver();

@override
Widget build(BuildContext context) =>
MaterialApp(
navigatorObservers: [navObserver],
// Другие свойства MaterialApp
);
}



Зачем это нужно?
Использование `RouteObserver` позволяет легко отслеживать события навигации, что может быть полезно для аналитики, логирования или выполнения определённых действий при переходе между экранами. 📊🔍

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDevelopment #CodingTips #FlutterTips
👍3
🎯 RuStore — теперь не просто альтернатива, а необходимость
📲 Подписан закон, который обязывает при продаже смартфонов и планшетов в России обеспечивать доступ к установке и обновлению приложений через 🇷🇺 единый магазин RuStore.
💥 Что это значит?
— Устройства, продаваемые в РФ, должны поддерживать RuStore "из коробки"
Никаких блокировок, ограничений или "невидимых барьеров" для российских приложений
Запрещено препятствовать обновлениям, уведомлениям, платежам и работе функций, если они идут через RuStore
— Даже 🍏 устройства, которые традиционно "закрыты", формально обязаны обеспечить такую возможность
🚨 Причём:
📌 В законе отдельно упоминаются производители, ограничивающие доступ к сторонним магазинам.
📌 Закон напрямую запрещает такие ограничения — включая блокировку API, ограничение платежей или недоступность функций для сторонних приложений.

🔥 А значит — не исключено, что в ближайшем будущем можно будет ставить приложения на iOS через RuStore (в теории… но реализация покажет 😉).
📢 Flutter-разработчики, следим за ситуацией внимательно: возможно, скоро откроются новые каналы распространения даже для iOS. А пока — продолжаем пилить 🧱 🧑‍💻.
🔗 Подписывайтесь на FlutterPulse — мы разбираем важные законы, фреймворки, SDK и публикуем апдейты без воды.
💬 Что думаете о нововведении? Откроет ли это путь для Flutter-приложений на iOS вне App Store? Прогнётся ли Apple? Пишите в комментариях!
#RuStore #iOS #Закон #МагазинПриложений #MobileDev #Flutter #flutterdev #flutterpulse #россия #санкции #iosdev #androiddev #appdistribution #russia #technews
🔥5
Отслеживание жизненного цикла приложения
Привет, подписчики! 👋 Сегодня мы рассмотрим очень полезный совет по Flutter - отслеживание жизненного цикла приложения. 📱💻

Вы знаете, что происходит с вашим приложением, когда оно переходит в фон или возвращается на передний план? 🤔 Чтобы управлять этими изменениями, мы можем использовать специальный метод didChangeAppLifecycleState. 🔍

В примере кода ниже показано, как это работает:


class CameraAwesomeState extends State<CameraAwesome> with WidgetsBindingObserver {
// ...
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.resumed:
if (!started) {
CamerawesomePlugin.start(); // Запуск камеры при возобновлении работы приложения
}
break;
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
if (started) {
CamerawesomePlugin.stop(); // Остановка камеры при переходе в фон или закрытии приложения
}
break;
}
}
// ...
}



Жизненный цикл приложения включает четыре основных состояния:
- resumed - приложение активно и видимо для пользователя.
- inactive - приложение не активно, но ещё видимо.
- paused - приложение не активно и не видимо (находится в фоне).
- detached - приложение закрыто.

Используя эти знания, вы сможете лучше управлять ресурсами вашего приложения и улучшать пользовательский опыт! 🌟

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

Все подобные новости можно найти по хэштегу #FlutterPulseTips. 🔍
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDevelopment #ProgrammingTips #TechNews
👍5
Предоставление разрешений в интеграционных тестах на Android с помощью командной строки adb

Привет, разработчики Flutter! 👋 Сегодня мы поделимся с вами полезным советом о том, как предоставлять разрешения в интеграционных тестах на Android с помощью командной строки adb. 📱💻

Зачем это нужно?
При выполнении интеграционных тестов на Android ваше приложение может требовать определённые разрешения. Без этих разрешений тесты могут завершиться неудачей. 🤕

Решение
Используйте команду adb для предоставления разрешений вашему приложению перед выполнением тестов. Вот пример кода на Dart, который демонстрирует, как это можно сделать:



const List<String> permissions = [
'android.permission.WRITE_EXTERNAL_STORAGE', // Разрешение на запись во внешнее хранилище
'android.permission.RECORD_AUDIO', // Разрешение на запись аудио
];

const String _examplePackage = 'com.apparence.example'; // Имя пакета вашего приложения

Future<void> main() async {
// Предоставляем разрешения перед запуском тестов
permissions.forEach((permission) => Process.runSync(
'adb', ['shell', 'pm', 'grant', _examplePackage, permission]));

print('Starting test.'); // Начало теста
await integrationDriver(); // Запуск интеграционных тестов
print('Test finished. Revoking permissions...'); // Окончание теста и отзыв разрешений

// Отзываем разрешения после завершения тестов
permissions.forEach((permission) => Process.runSync(
'adb', ['shell', 'pm', 'revoke', _examplePackage, permission]));
}



Этот код сначала предоставляет необходимые разрешения, затем запускает интеграционные тесты, и, наконец, отзывает разрешения после завершения тестов. 🔄

Оцените нашу новую рубрику советов для Flutter-разработчиков! 👍 Ваши отзывы помогут нам сделать её ещё лучше. 💬

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AndroidDev #IntegrationTesting #FlutterTips
👍2
Изменение модели родительского виджета из дочернего
с использованием виджета Actions и модели Intent 🤯

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

Основная идея
Используем виджет Actions и модель Intent для взаимодействия между родительским и дочерним компонентами.

Пример кода


// Используем intent для изменения модели действия
class AppBarBuildIntent extends Intent {
final PreferredSizeWidget? appbar;
AppBarBuildIntent(this.appbar);
}

// Действие, которое можно вызвать из любого дочернего элемента
class BartAppbarAction extends Action<AppBarBuildIntent> {
final ValueNotifier<PreferredSizeWidget?> appbar;
BartAppbarAction(this.appbar);

@override
void invoke(covariant AppBarBuildIntent intent) {
this.appbar.value = intent.appbar;
}
}

class MyWidget extends StatelessWidget {
final ValueNotifier<PreferredSizeWidget?> appBarNotifier = ValueNotifier(null);

MyWidget({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Actions(
actions: <Type, Action<Intent>>{
AppBarBuildIntent: BartAppbarAction(appBarNotifier),
},
child: AnimatedBuilder(
animation: appBarNotifier,
builder: (ctx, child) => ...,
),
);
}
}

// Вызов в дочернем элементе для изменения родителя (appBar)
Actions.invoke(context, AppBarBuildIntent(AppBar(title: Text("title text"))));



Этот подход позволяет элегантно управлять состоянием родительского виджета из дочерних компонентов. 👍

Оцените новую рубрику FlutterPulseTips! 😊 Нам важно ваше мнение!

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #WidgetMagic #StateManagement #FlutterTips
Действия клавиатуры ввода в Flutter

Улучшите опыт пользователей вашего приложения с помощью действий клавиатуры ввода! 🌟 Они помогают пользователям быстрее заполнять формы, предоставляя интуитивно понятные и удобные элементы управления. 🚀

Как это работает? 🤔
1. Покажите действие "следующее поле" на клавиатуре и свяжите его с отправкой формы.
2. При нажатии на "следующее поле" текущее поле теряет фокус, а следующее поле получает фокус.



TextFormField(
focusNode: _emailFocus,
textInputAction: TextInputAction.next, // Действие "следующее поле"
onFieldSubmitted: (term) {
_emailFocus.unfocus(); // Убрать фокус с текущего поля
FocusScope.of(context.buildContext).requestFocus(_pwdFocus); // Передать фокус следующему полю
},
keyboardType: TextInputType.emailAddress, // Тип клавиатуры для ввода email
)



👍 Оцените новую рубрику по Flutter и Dart! Ваши отзывы помогут нам улучшать контент. 💬

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #UIUX #FlutterTips #DartLang
1