Flutter Pulse
640 subscribers
398 photos
879 links
На канале будут новости про flutter с сайтов, информация об обновлении пакетов, а также авторский контент.
Download Telegram
🎨 Vibe coding: Создаём приложение для скетчей с нуля вместе с ИИ!

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

🚀 Что ждет в этом выпуске?
• Чистый проект с нуля: от flutter create до первого коммита
• Работа с Cursor IDE и генерация кода через промпты
• Верстка экранов в стиле Cupertino (iOS)
• Создание компонентов: SketchCanvas, GenerateButton, навигация
• Подготовка к интеграции с GPT API и генерацией изображений (в следующей части!)

💡 Почему это стоит посмотреть?
👉 Реальный процесс работы с ИИ-инструментами
👉 Практические приемы организации кода
👉 Подготовка базы для сложных фич с генерацией контента
👉 Живой процесс принятия решений при разработке

📂 Ресурсы:
🔗 Репозиторий с примерами
🔗 Смотреть видео на YouTube

Не пропустите продолжение! В следующем выпуске — добавление AI-логики и генерации изображений через Gemini.

#Flutter #Dart #FlutterPulse #FlutterPulseTips #FlutterPulseYoutube
Смотрите другие наши видео по хэштегу #FlutterPulseYoutube!
Детали для улучшения формы

Ничто не раздражает так сильно, как невозможность закрыть клавиатуру. Пользователь нажимает в любом месте... и клавиатура остаётся. Вместо этого вы можете просто сделать так:


final _formKey = GlobalKey<FormState>();

class SigninPage extends StatelessWidget {
const SigninPage({super.key, this.canDismiss = true});

@override
Widget build(BuildContext context) {
final translations = Translations.of(context).signin;

return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(), // Убираем фокус с полей при нажатии вне их
child: PopScope(
canPop: canDismiss,
child: TopImgBackground(
bgImagePath: 'assets/images/signin/signin_background.png',
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
backgroundColor: Colors.transparent,
automaticallyImplyLeading: canDismiss,
foregroundColor: context.colors.background,
),
resizeToAvoidBottomInset: false,
body: Form(
autovalidateMode: AutovalidateMode.disabled,
key: _formKey,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: ListView(
children: [
const SizedBox(height: 150),
Text(
translations.title,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
),
),
),
),
),
);
}
}


Решение: Оберните всю страницу в GestureDetector, а затем снимите фокус со всех элементов, используя функцию FocusScope. Это позволит автоматически убрать клавиатуру при нажатии вне текстовых полей.

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

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #UIUX #AppDev #CodingTips #DevTips #FlutterTips
👍4
Шпаргалка по InteractiveViewer

InteractiveViewer - это виджет, который позволяет пользователям перемещать, масштабировать и взаимодействовать с дочерним содержимым с помощью жестов, таких как сжатие и перетаскивание.



InteractiveViewer(
transformationController: TransformationController()
..value = (Matrix4.identity()..scale(scale)),
scaleEnabled: false,
constrained: false,
panEnabled: false,
onInteractionStart: (details) => print('Начало взаимодействия'),
onInteractionUpdate: (details) => print('Обновление взаимодействия'),
onInteractionEnd: (details) => print('Конец взаимодействия'),
child: Image.asset("name_of_your_image.png"),
)



* scaleEnabled:
* Позволяет пользователю масштабировать с помощью жестов сжатия (по умолчанию: true).
* Установите значение false, чтобы отключить масштабирование.
* constrained:
* Ограничивает дочерний элемент в пределах границ просмотра (по умолчанию: true).
* Установите значение false для неограниченного перемещения/масштабирования за пределами границ просмотра.
* panEnabled:
* Позволяет перетаскивать/перемещать дочерний элемент с помощью касания (по умолчанию: true).
* Установите значение false, чтобы отключить перемещение.
* onInteractionStart:
* Callback, срабатывающий при начале взаимодействия (перемещение или масштабирование).
* Предоставляет детали, такие как фокусная точка и масштаб.
* onInteractionUpdate:
* Callback, вызываемый непрерывно при перемещении или масштабировании пользователем.
* Полезно для отслеживания обновлений жестов в реальном времени.

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

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #UIUX #FlutterTips #CodingCheatsheet
👍1
Шпаргалка по форматированию цен

Вы можете легко форматировать цены, используя пакет intl. Вот наиболее распространенные методы:


import 'package:intl/intl.dart';

// Форматирование с указанием локали и символа валюты
NumberFormat.currency(locale: 'en_US', symbol: '\$').format(12.2);
// $12.2

// Форматирование валюты с использованием текущей локали устройства
NumberFormat.currency().format(12.2);
// US 12.2 или EUR 12.2 в зависимости от локали устройства

// Форматирование валюты без десятичных знаков
NumberFormat.currency(decimalDigits: 0).format(12.2);
// US 12

// Простое форматирование валюты
NumberFormat.simpleCurrency().format(12.2);
// $12.2

// Компактное форматирование больших чисел
NumberFormat.compactSimpleCurrency().format(1200000);
// $1.2M


Оцените нашу новую рубрику по Flutter советам! 👍 Оставьте свои комментарии и реакции, если вам понравился этот пост! 💬👍

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #AppDevelopment #ProgrammingTips #Coding #FlutterTips
👍4
Как правильно обрабатывать результат диалога

Не пытайтесь выполнить код напрямую после закрытия диалога! 🚫💻

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

Неправильный способ: 🚫

Future<void> showRatingDialog(BuildContext context) {
return showDialog<RatingResult>(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: const Text('Rate the app'),
content: const Text('Please rate the app'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
// не пытайтесь выполнить что-либо здесь
// потому что диалог отклонен
},
child: const Text('Rate'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
// не пытайтесь выполнить что-либо здесь
// потому что диалог отклонен
},
child: const Text('Improve'),
),
],
);
},
);
}


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

enum RatingResult {
rate,
improve,
never,
}

Future<RatingResult?> showRatingDialog(BuildContext context) {
return showDialog<RatingResult>(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: const Text('Rate the app'),
content: const Text('Please rate the app'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(RatingResult.rate);
},
child: const Text('Rate'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(RatingResult.improve);
},
child: const Text('Improve'),
),
],
);
},
);
}


В первом примере при нажатии на кнопки "Rate" или "Improve" диалог закрывается, но результат не возвращается. Во втором примере при нажатии на кнопки возвращается соответствующее значение из enum `RatingResult`, что позволяет корректно обработать результат.

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

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #AppDevelopment #ProgrammingTips #UIUX #SoftwareDevelopment
👍31🤔1
Как воспроизвести звуки в Flutter

Звуки могут изменить опыт использования приложения. Что может быть более удовлетворительным, чем хороший звук уведомления? 😊

Шаг 1: Установите плагин audioplayers

import 'package:audioplayers/audioplayers.dart';


Шаг 2: Воспроизведите звук напрямую...

await player.play(AssetSource("sounds/beep.wav"));


Это здорово, потому что вам не нужно готовить файл или сбрасывать его, если он уже был воспроизведен... просто попросите воспроизвести, и он сделает свое дело! 👍

Дополнительные возможности

// Установите режим освобождения, чтобы сохранить источник после завершения воспроизведения.
player.setReleaseMode(ReleaseMode.stop);


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

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

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

#flutter #dart #flutterpulse #FlutterPulseTips #mobiledevelopment #appdevelopment #codingtips #programming #softwaredevelopment
👍1
Перегрузка операторов
Ещё один отличный способ улучшить читаемость кода 😉

Вы знаете, что в Dart можно перегружать операторы? 🤔 Это очень полезная функция, которая позволяет сделать ваш код более интуитивным и понятным.

Давайте рассмотрим пример с классом Vector. Мы хотим складывать векторы с помощью оператора +. Для этого нам нужно перегрузить этот оператор в нашем классе.


class Vector {
final int x, y;
Vector(this.x, this.y);

// Перегружаем оператор +
Vector operator +(Vector other) => Vector(x + other.x, y + other.y);
}

void main() {
final v1 = Vector(1, 2);
final v2 = Vector(3, 4);

final result = v1 + v2; // Теперь мы можем складывать векторы с помощью оператора +
print('(${result.x}, ${result.y})'); // Вывод: (4, 6)
}


Таким образом, мы можем перегружать любые операторы, такие как -, *, / и другие, чтобы сделать наш код более удобным и читаемым. 👍

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

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #codingTips #mobileDevelopment #programmingTips
👍1
Flutter Pulse
🎨 Vibe coding: Создаём приложение для скетчей с нуля вместе с ИИ! Привет, Flutter-разработчики! 👋 Сегодня мы делимся крутым видео, которое точно стоит вашего внимания. Руслан Цицер начинает новый практический цикл по созданию приложения для скетчей с использованием…
🎨 Vibe coding. Приложение для скрипинга с нуля. Часть 2

Продолжаем наше погружение в увлекательный мир vibe coding! 🚀 В этом выпуске Руслан берет нашу заготовку с рисованием линий и превращает ее в нечто по-настоящему волшебное.

Хотели ли вы, чтобы ваш простой скетч превратился в полноценную, стилизованную картинку? Теперь это реальность!

В этом выпуске вы увидите:
🔹 Как добавить кнопку «Сгенерировать» и реализовать логику захвата текущего рисунка
🔹 Работу с RepaintBoundary для преобразования canvas в изображение
🔹 Самое интересное — интеграцию с мощным AI Gemini от Google через REST API для генерации картинок!
🔹 Практические примеры: как простой набросок котика 🐱 превращается в крутой арт

Это не просто теория — наш подписчик Руслан Цицер проходит каждый шаг, от настройки Dart defines для безопасного хранения API-ключа до обработки ответов от нейросети и отображения результата прямо в приложении.

Хотите узнать, как опубликовать такое приложение или есть идеи для улучшения? Обязательно напишите об этом в комментариях! 💬

Смотрите видео здесь 👉 Vibe coding. Приложение для скетчей с нуля. Часть 2

Ищите другие полезные видео по хэштегу #FlutterPulseYoutube!

#Flutter #Dart #FlutterPulse #FlutterPulseTips #FlutterPulseYoutube #Refactoring #CleanCode #FlutterArchitecture #StatefulWidget #ComponentBasedUI #fluttertutorial #ffi #rust
Single execution Future Builder

FutureBuilder с единственным выполнением

FutureBuilder будет выполнять наше будущее при каждой пересборке. Если это будущее выполняет вызов API, это может быть дорогостоящим и перегружать наш бэкэнд.

Мы предоставляем Future функцию, которая предотвращает повторное выполнение Future при каждой пересборке страницы.



typedef AsyncFutureBuilder<T> = Future<T> Function();

class SingleExecFutureBuilder<T> extends StatefulWidget {
final AsyncFutureBuilder<T> future;
final Widget Function(BuildContext context, T data) builder;

const SingleExecFutureBuilder({
super.key,
required this.future,
required this.builder,
});

@override
State<SingleExecFutureBuilder<T>> createState() => _SingleExecFutureBuilderState<T>();
}

class _SingleExecFutureBuilderState<T> extends State<SingleExecFutureBuilder<T>> {
T? _futureRes;
late FutureState _futureState;

@override
void initState() {
super.initState();
_futureState = FutureState.pending;
}

Future<T?> executeFuture() async {
if (_futureState == FutureState.pending) {
try {
_futureRes = await widget.future();
_futureState = FutureState.done;
return _futureRes;
} catch (e) {
_futureState = FutureState.error;
rethrow;
}
}
return _futureRes;
}

@override
Widget build(BuildContext context) {
return FutureBuilder<T>(
future: executeFuture(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const SizedBox.shrink();
}
return widget.builder(context, snapshot.data as T);
},
);
}
}

enum FutureState { pending, done, error }



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


@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Count: $count'),
const SizedBox(height: 16),
SingleExecFutureBuilder<String>(
future: () async {
print("Getting future once");
await Future.delayed(const Duration(milliseconds: 1100));
return "Hello";
},
builder: (context, data) => Text(data),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
setState(() {
count++;
});
},
child: const Text('+'),
),
],
),
),
);
}



Выполните этот код, и вы увидите "Getting future once" только один раз, вместо того, чтобы видеть это каждый раз, когда вы нажимаете кнопку "+".


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

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #FutureBuilder #FlutterTips #CodingTips #AppDevelopment
👍3
Как задать высоту строки внутри колонки

Привет, Flutter-разработчики! 👋 Сегодня мы рассмотрим интересную задачу: как правильно задать высоту строки (Row) внутри колонки (Column). Эта проблема часто возникает при создании адаптивных интерфейсов, и мы разберем, как ее решить с помощью виджета IntrinsicHeight. 📐

Проблема: 🤔
Когда вы пытаетесь разместить Row внутри Column и хотите, чтобы высота Row определялась максимальным размером дочерних элементов, вы можете столкнуться с ошибкой. Flutter требует, чтобы размеры виджетов были ограничены, а Row по умолчанию не имеет ограничений по высоте.

Решение: 💡
Используйте виджет IntrinsicHeight в качестве родителя для Row. Этот виджет устанавливает высоту Row равной максимальному размеру его дочерних элементов.



@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
IntrinsicHeight( // Оберните Row в IntrinsicHeight
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
flex: 2,
child: Container(
color: Colors.red,
padding: const EdgeInsets.all(32.0),
child: const Center(child: Text('Flex 2')),
),
),
Expanded(
flex: 1,
child: Container(
color: Colors.green,
padding: const EdgeInsets.all(32.0),
child: const Center(child: Text('Flex 1')),
),
),
],
),
),
Container(
height: 100,
color: Colors.blue,
child: const Center(child: Text('Контейнер с фиксированной высотой')),
),
],
),
),
);
}



Почему это работает? 🔍
- IntrinsicHeight определяет максимальную внутреннюю высоту дочерних элементов Row и применяет ее ко всем детям.
- CrossAxisAlignment.stretch растягивает дочерние элементы на всю доступную высоту.
- mainAxisSize: MainAxisSize.min устанавливает размер Row по основной оси в минимально необходимый.

Вывод: 🎉
Использование IntrinsicHeight позволяет легко управлять размером Row внутри Column, делая ваш интерфейс гибким и адаптивным. Оцените эту рубрику и оставляйте свои комментарии! 💬

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #UIUX #FlutterTips #AppDevelopment #CodingTips
👍2
Supabase: Привязка анонимного пользователя к аутентифицированному

Анонимный пользователь - это действительно здорово. Вы автоматически создаете нового пользователя в своей базе данных каждый раз, когда кто-то впервые запускает ваше приложение.
Затем вы можете позволить ему подписаться, начать работать с вашим приложением, не беспокоя его просьбой ввести email...
А затем, когда он действительно вовлечется в процесс, вы можете привязать его учетную запись.



@override
Future<Credentials> signup(String email, String password) async {
if (client.auth.currentUser?.isAnonymous == true) {
// Обновляем анонимного пользователя с помощью email и password
final res = await client.auth.updateUser(UserAttributes(email: email, password: password));
if (res.user != null) {
return Credentials(id: res.user!.id);
} else {
throw 'Ошибка при обновлении пользователя';
}
}
return client.auth
.signUp(email: email, password: password)
.then(
(value) => Credentials(id: value.user!.id),
onError: (error) {
Logger().e("Ошибка при регистрации: $error");
throw SignupException();
},
);
}



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

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #Supabase #MobileDev #AppDev #CodingTips #DevTips
👍1
Flutter-дайджест сентября 🌐

Сентябрь выдался ярким месяцем для Flutter-разработчиков: статьи на Хабре, новые пакеты и мощные обновления, разборы на YouTube и полезные советы для прокачки проектов 🚀

В дайджесте вы найдёте:

🎓 статьи с Хабра (RenderObject, CI/CD, хитрости релиза в сторы);
🎥 лучшие видео (FFI + Rust, Talker, сборка бинарников);
📦 топовые пакеты месяца (flutter_slidable, go_router, drift, flutter_map, Firebase и др.);
🆕 новые пакеты недели (bodychart_heatmap, fquery, isar_plus, loader_pro, cupertino_native);
💡 #FlutterPulseTips (кастомные переходы страниц, точный таймер, лайфхаки с формами).

Полный обзор читайте 👉 здесь

Давайте вместе вспомним что произошло за прошедший месяц

#Flutter #Dart #FlutterPulse #PubDev #Хабр #MobileDev
Привет всем! ✌️ Руслан продолжает свой увлекательный эксперимент по созданию приложения для генерации скетчей во Flutter. В этом выпуске — сразу несколько крутых обновлений! 🚀

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

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

В видео вы увидите:
🔹 Как работать с библиотекой Liquid Glass для создания красивых UI-эффектов.
🔹 Как правильно выносить код в отдельные виджеты и файлы.
🔹 Как добавить анимации нажатия на кнопки с помощью Implicit Animations.
🔹 Как избежать race condition при множественных запросах.

Если вам интересно продолжение — например, как публиковать такое приложение или есть идеи по улучшению — оставляйте комментарии прямо под видео! 💬

🔗 Ссылка на видео: Vibe coding. Приложение для скетчей с нуля. Часть 3

📩 Связаться с Русланом: https://t.me/ruslan_tsitser

Обязательно посмотрите и другие видео по хэштегу #FlutterPulseYoutube! 🎥

#Flutter #Dart #FlutterPulse #FlutterPulseTips #FlutterPulseYoutube #Refactoring #CleanCode #FlutterArchitecture #StatefulWidget #ComponentBasedUI #fluttertutorial
Полезные конфигурации VSCode для экономии времени

VSCode позволяет запускать код при сохранении или включить автоматическое форматирование... Вот несколько конфигураций, которые сэкономят ваше время!

{
"editor.formatOnSave": true,
"[dart]": {
"editor.formatOnSave": false, // Автоматически форматировать dart файлы (вкл/выкл)
"editor.inlayHints.enabled": "off",
"editor.codeActionsOnSave": {
"source.fixAll": "explicit", // Автоматически исправлять весь код при явном сохранении dart файла
"source.organizeImports": "explicit" // Автоматически исправлять код (добавлять const...) при сохранении dart файла
}
}
}


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

Оцените новую рубрику и оставьте свои комментарии! 👍💬 Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #VSCode #ProductivityTips #CodingTips #FlutterDev #DartLang
👍1
Что такое Render Objects?

Render Objects являются основой визуальной системы Flutter. Они используются для отрисовки элементов интерфейса на экране.

Flutter строит пользовательские интерфейсы в три слоя:
1. Widget: конфигурация визуального элемента
2. Element: соединяет виджеты и объекты рендеринга
3. RenderObject: рисует на вашем экране

Настройка RenderObject
Поскольку мы будем рендерить одного потомка, мы расширяем SingleChildRenderObjectWidget.

Render Objects рисуют как CustomPainter, но с гораздо большими возможностями:
- Могут иметь потомков и реагировать на ограничения макета
- Управляют как макетом, так и отрисовкой
- Обрабатывают ограничения макета
- Управляют своим собственным размером

Зачем использовать RenderObject?
Большую часть времени вам не придётся писать RenderObjects самостоятельно. Используйте виджеты!
Но пользовательские RenderObjects дают неограниченную творческую свободу для нестандартных виджетов.


class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
body: const Center(
child: CustomStar(
size: 200,
color: Colors.amber,
),
),
);
}
}

class CustomStar extends SingleChildRenderObjectWidget {
final double size;
final Color color;

const CustomStar({
super.key,
required this.size,
required this.color,
});

@override
RenderObject createRenderObject(BuildContext context) {
return RenderCustomStar(size: size, color: color);
}

@override
void updateRenderObject(BuildContext context, RenderCustomStar renderObject) {
renderObject.starSize = size;
renderObject.color = color;
}
}

class RenderCustomStar extends RenderBox {
RenderCustomStar({
required double size,
required Color color,
}) : starSize = size,
color = color;

double starSize;
Color color;

@override
void performLayout() {
size = constraints.biggest;
}

@override
void paint(PaintingContext context, Offset offset) {
final canvas = context.canvas;
canvas.save();
canvas.translate(offset.dx, offset.dy);
// ... рисуйте то, что вам нужно здесь
}
}


Оцените новую рубрику лайком! 👍
Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #mobiledev #renderobjects #flutterrenderobjects #flutterui #flutterdevelopment #codingtips
1👍1