Flutter Pulse
594 subscribers
371 photos
821 links
На канале будут новости про flutter с сайтов, информация об обновлении пакетов, а также авторский контент.
Download Telegram
Правильно называйте свои классы
🤔 Название класса должно отражать его сущность, а не выполняемые им действия! 💡

При именовании классов важно следовать правилу: класс должен называться тем, чем он является, а не тем, что он делает. Это делает ваш код более читаемым и понятным. 📚

👀 Рассмотрим пример на Dart:

class EmailValidator { // Неправильное название, так как оно описывает действие
EmailValidator();

void validate(String email) {
const pattern = r'...'; // Регулярное выражение для проверки email
final regex = RegExp(pattern);
final isValidEmail = regex.hasMatch(email);
if (!isValidEmail) {
throw const EmailException("Email not valid"); // Ошибка, если email не валиден
}
}
}



class Email { // Правильное название, отражает сущность класса
final String _value;
Email(String email) : _value = email.trim();

void validate() {
const pattern = r'...'; // Регулярное выражение для проверки email
final regex = RegExp(pattern);
final isValidEmail = regex.hasMatch(_value);
if (!isValidEmail) {
throw const EmailException("Email not valid"); // Ошибка, если email не валиден
}
}
}


В первом примере класс назван EmailValidator, что указывает на выполняемое действие (валидация email). Это не отражает суть класса. 🔴
Во втором примере класс назван Email, что отражает его сущность (email). Это делает код более логичным и понятным. 🟢

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

Все подобные советы можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #CodingTips #MobileDevelopment #CleanCode #ProgrammingTips
Эффективная передача стиля текста виджетам

Привет, разработчики Flutter! 👋 Сегодня мы поделимся с вами полезным советом о том, как эффективно передавать стиль текста вашим виджетам. 📝

Вы когда-нибудь сталкивались с ситуацией, когда приходилось копировать стиль текста для дочерних элементов? 🤔 Теперь вы можете этого избежать! 😉

Использование DefaultTextStyle.merge


class MyWidget extends StatelessWidget {
final Widget title;

const TheBestCustomWidget({
super.key,
required this.title,
});

@override
Widget build(BuildContext context) {
return WidgetContainer(
children: [
DefaultTextStyle.merge(
style: Theme.of(context).textTheme.headlineMedium,
child: title,
),
],
);
}
}



MyWidget(
title: Text(
'Signup now',
style: Theme.of(context).textTheme.headlineLarge.copyWith(
color: Theme.of(context).colorScheme.onBackground,
),
),
)



MyWidget(
title: Text(
'Signup now',
style: TextStyle(
color: Theme.of(context).colorScheme.onBackground,
),
),
)


Преимущества использования DefaultTextStyle.merge:

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

👍 Оцените новую рубрику FlutterPulseTips и поделитесь своими мыслями! 🤔

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #UIUX #CodingTips #AppDevelopment
👍1
Воспроизведение видео на весь экран
Покажите полноэкранное видео с правильным соотношением сторон 🤩

Чтобы показать видео на весь экран с сохранением правильного соотношения сторон, следуйте этим простым шагам:

1. Установите пакет video_player 📦
2. Встройте видеоплеер внутрь виджета VideoContainer 📺

Обеспечьте, чтобы содержимое занимало весь экран 📱

return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
),
extendBodyBehindAppBar: true,
body: Stack(
children: [
Positioned.fill(
child: GestureDetector(
onTap: () => videoListener?.pauseOrResume(),
child: VideoContainer.fromController(_controller!),
),
),
],
),
);


Код виджета VideoContainer:

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

class VideoContainer extends StatelessWidget {
final Widget child;
final double ratio;
final Size contentsSize;

const VideoContainer({
super.key,
required this.child,
required this.ratio,
required this.contentsSize,
});

factory VideoContainer.fromController(VideoPlayerController controller) =>
VideoContainer(
ratio: controller.value.aspectRatio,
contentsSize: controller.value.size,
child: VideoPlayer(controller),
);

@override
Widget build(BuildContext context) {
return FittedBox(
fit: BoxFit.cover,
child: AspectRatio(
aspectRatio: ratio,
child: child,
),
);
}
}


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

#flutter #dart #flutterpulse #FlutterPulseTips #mobiledev #appdev #codingtips #uiux #videostreaming #fullscreenvideo
👍1
Как протестировать Изолят
Запуск функции изолята в модульных тестах

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

Рассмотрим пример неправильного тестирования изолята:


testWidgets('upload file and save avatar', (tester) async {
final file = await rootBundle.load('assets/images/splashscreen.png');
final bytes = file.buffer.asUint8List();
final xfile = XFile.fromData(bytes);

final jpgData = await compute(_avatarThumbnail, file);
}

// Функция, выполняющаяся в изоляте
Future<Uint8List> _avatarThumbnail(XFile file) {
return file.toJpeg(file, 300, 80);
}


Такой тест зависнет и никогда не завершится, поскольку функция compute запускает _avatarThumbnail в изоляте, но тест не ожидает завершения изолята.

Решение: Используйте tester.runAsync() для запуска асинхронного кода в тесте:


testWidgets('upload file and save avatar', (tester) async {
await tester.runAsync(() async {
final file = await rootBundle.load('assets/images/splashscreen.png');
final bytes = file.buffer.asUint8List();
final xfile = XFile.fromData(bytes);

final jpgData = await compute(_avatarThumbnail, file);
});
});

// Функция, выполняющаяся в изоляте
Future<Uint8List> _avatarThumbnail(XFile file) {
return file.toJpeg(file, 300, 80);
}


Таким образом, вы сможете корректно протестировать функции, выполняющиеся в изоляте.

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

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #Testing #Isolate #FlutterTips #DartTips
👍1
Получайте отзывы в магазинах
Это не так просто, но есть некоторые нюансы, которые стоит знать

Проблема:
• Apple Store ограничивает запрос оценки через нативное всплывающее окно до 2 раз в год
• Google Play Store ограничивает запрос оценки примерно 2 раза каждые 3 месяца...
• Вы не хотите докучать пользователям и получать плохие оценки

Запрашивайте оценку в подходящее время:
Найдите "момент истины", когда пользователь получил наилучший опыт от использования вашего приложения. И запросите оценку именно в этот момент. Никогда не спрашивайте оценку у тех, кто только что установил приложение или никогда с ним не взаимодействовал...
Будьте терпеливы

Решение:
Создайте предварительный попап или карточку в вашем приложении
Но не пытайтесь повлиять на решение пользователя



void _showReviewPopup() {
// Проверяем, согласен ли пользователь оставить отзыв
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Оцените наше приложение!'),
content: Text('Если вам нравится наше приложение, пожалуйста, оставьте отзыв в магазине приложений.'),
actions: <Widget>[
TextButton(
child: Text('Не сейчас'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('Оставить отзыв'),
onPressed: () {
// Ссылка на страницу оценки в магазине
launchReviewUrl(); // Реализуйте эту функцию для открытия нужного URL
Navigator.of(context).pop();
},
),
],
);
},
);
}

// Функция для открытия URL (пример)
void launchReviewUrl() async {
const url = 'https://your-app-review-url.com'; // Замените на реальную ссылку
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Не удалось открыть $url';
}
}



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

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDevTips #ProgrammingTips
👍4
Запуск анимации при изменении свойства
Привет, подписчики! 👋 Сегодня мы рассмотрим интересный вопрос: как запустить анимацию каждый раз, когда меняется определенное свойство? 🤔

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

Для этого мы можем использовать метод didUpdateWidget в StatefulWidget. Этот метод вызывается каждый раз, когда виджет обновляется. 🔄

Пример кода:


@override
void didUpdateWidget(covariant UploadedAvatarAnimation oldWidget) {
super.didUpdateWidget(oldWidget);
final (wasUploading, isUploading) = (oldWidget.isUploading, widget.isUploading);
switch ((wasUploading, isUploading)) {
case (false, true):
_controller.forward(from: 0);
_initScaleAnim(0, pt);
case (true, false):
_controller.reverse(from: 1);
default:
}
}



В этом примере мы проверяем, изменилось ли свойство isUploading, и запускаем анимацию соответствующим образом. 🔮

Как это работает?
1. Мы используем метод didUpdateWidget, чтобы отслеживать изменения виджета.
2. Мы сравниваем старое и новое значения свойства isUploading.
3. В зависимости от изменения, мы запускаем анимацию вперед или назад.

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

Все подобные новости можно найти по хэштегу #FlutterPulseTips 👍
#flutter #dart #flutterpulse #FlutterPulseTips #mobiledev #appdev #animation #ui #ux #codingtips #programming #softwaredevelopment
👍4
🚀 Расширяем возможности Flutter с помощью FFI и Rust: Глубокое погружение в управление памятью!

🔥 Хотите выжать максимум из Flutter, подружив его с низкоуровневыми языками? В новом видео от эксперта Руслана Цицера — ключ к мощным оптимизациям!

📚 Что вас ждет:
🛠 Разбор Ownership в Rust — как система владения предотвращает утечки памяти и гарантирует безопасность.
🔧 Практика работы с unsafe операциями и добавление зависимостей в Rust-проекты.
⚙️ Компиляция Rust-кода в C-библиотеку для интеграции с Dart.
💡 Конвертация данных между Dart и Rust (строки, указатели) без ошибок.
🚫 Важные нюансы управления памятью при работе с FFI.

👉 Смотрите видео здесь:
Расширяем возможности Flutter с помощью FFI. Rust

👀 Уже в следующих выпусках — запуск Flutter-приложений с FFI! Не пропустите практические примеры.

👍 Поддержите Руслана: подписывайтесь на его канал на youtube, ставьте лайк и жмите 🔔!

Хотите больше полезного?
Подписывайтесь на Flutter Pulse и ищите другие крутые туториалы по хэштегу:
#FlutterPulseYoutube

#Flutter #Dart #FFI #Rust #FlutterPulse #FlutterPulseTips #CleanCode #FlutterArchitecture #StatefulWidget #fluttertutorial
🤔1💩1
Глупый и умный: создаём "глупый" конструктор и умные фабрики

Привет, друзья! 👋 Сегодня мы поговорим о том, как улучшить ваш код на Flutter/Dart, используя "глупые" конструкторы и умные фабрики. 📈

Что такое "глупый" конструктор?
"Глупый" конструктор - это конструктор, который только присваивает данные. Он не должен делать ничего другого! 🚫

Пример плохого конструктора:

class Device {
String? id;
String? name;
OperatingSystem? platform;

Device({
this.id,
}) :
// Плохая практика - присвоение значений в теле конструктора
name = null,
platform = null {
final deviceInfo = ...; // Получение информации об устройстве
name = deviceInfo.name;
platform = deviceInfo.platform;
}
}


Пример хорошего "глупого" конструктора:

class Device {
String? id;
String name; // Теперь обязательное поле
OperatingSystem platform; // Теперь обязательное поле

Device({
this.id,
required this.name, // Требуем имя устройства
required this.platform, // Требуем платформу устройства
});
}


Зачем использовать фабрики?
Фабрики идеально подходят для более сложных присвоений значений. Они позволяют создавать объекты более гибко и читаемо. 🌟

Пример фабрики:

factory Device.current() {
final deviceInfo = ...; // Получение информации об устройстве
return Device(
name: deviceInfo.name, // Присваиваем имя устройства
platform: deviceInfo.platform, // Присваиваем платформу устройства
);
}


Вывод:
- Конструкторы должны быть "глупыми" и только присваивать данные.
- Для более сложных операций используйте фабрики.

Оцените нашу новую рубрику! 👍 Нам важно ваше мнение. Оставляйте свои комментарии и предложения. 💬

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #CodingTips #Programming #SoftwareDevelopment
👍31
Избегайте тестирования с помощью моков
Моки отражают вашу реализацию

Лучшие тесты не отражают вашу реализацию.
Они позволяют рефакторить код, не задумываясь о том, как они работают.
Вы тестируете то, что они возвращают, а не как они это делают.
Таким образом, вы можете рефакторить код, пока они продолжают работать.



test('on receive message, should dispatch as a notification', () async {
final repository = AppNotificationsRepository(
notificationsApi: fakeNotificationsApi,
notificationPublisher: dispatcher,
);

Notification? receivedNotification;
dispatcher.subscribe((notification) => receivedNotification = notification);
fakeNotificationsApi.sendForegroundMessage(
const RemoteMessage(
data: {
'title': 'title',
'body': 'example body',
},
),
);
await Future.delayed(const Duration(milliseconds: 100));
expect(receivedNotification, isNotNull);
expect(receivedNotification!.title, 'title');
expect(receivedNotification!.body, 'example body');
});





class FakeNotificationsApi implements NotificationsApi {
OnRemoteMessage? _foregroundHandler;

@override
void setForegroundHandler(OnRemoteMessage handler) {
_foregroundHandler = handler;
}

void sendForegroundMessage(RemoteMessage message) {
_foregroundHandler?.call(message);
}
}



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

👋 Прощай, mockito!

Оцените новую рубрику и напишите своё мнение! 👍
Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #TestingTips #CodingBestPractices
💯2
Добавьте кастомные переходы страниц с GoRouter

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

Добавьте этот небольшой помощник, чтобы определить кастомный переход страницы:


Page<dynamic> Function(BuildContext, GoRouterState) buildPageTransition(
Widget child,
) => (BuildContext context, GoRouterState state) {
return CustomTransitionPage(
key: state.pageKey,
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
FadeThroughTransition( // можно создать свой собственный переход или использовать из пакета animations на pub.dev
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
),
);
};


Теперь оберните свою страницу в переход, используя pageBuilder для вашего маршрута:


GoRoute(
path: '/signin',
builder: (context, state) => const SigninPage(),
pageBuilder: buildPageTransition(const SigninPage()),
),


Вы также можете настроить стандартный переход страницы прямо в вашей теме:


pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: const ZoomPageTransitionsBuilder(),
TargetPlatform.iOS: const CupertinoPageTransitionsBuilder(),
},
),


Оцените нашу новую рубрику! 👍💡
Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #AppDev #UIUX #FlutterTips
👍5
Создание последовательных анимаций
Создайте пользовательскую цепочку эффектов, чтобы повторно использовать ее во всем приложении с помощью flutter_animate.

Создайте виджет с Animate и списком эффектов


import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';

class MoveFadeAnim extends StatelessWidget {
final int? delayInMs;
final Widget child;

const MoveFadeAnim({
super.key,
required this.child,
this.delayInMs,
});

@override
Widget build(BuildContext context) {
return Animate(
effects: [
// Эффект затухания с задержкой и продолжительностью 700 мс
FadeEffect(
delay: Duration(milliseconds: delayInMs ?? 0), // Задержка перед началом анимации
duration: const Duration(milliseconds: 700), // Продолжительность анимации
curve: Curves.easeIn, // Кривая анимации для эффекта затухания
),
// Эффект перемещения с задержкой и продолжительностью 450 мс
MoveEffect(
delay: Duration(milliseconds: delayInMs ?? 0), // Задержка перед началом анимации
duration: const Duration(milliseconds: 450), // Продолжительность анимации
curve: Curves.easeOut, // Кривая анимации для эффекта перемещения
begin: const Offset(0, 50), // Начальное смещение
end: Offset.zero, // Конечное смещение (нет смещения)
),
],
child: child, // Дочерний виджет, к которому применяются эффекты
);
}
}


Повторно используйте свою анимацию везде в приложении для последовательного поведения анимации


return MoveFadeAnim(
delayInMs: index * 150 + 50, // Вычисление задержки на основе индекса элемента
child: MenuCard(
height: 130, // Высота карточки меню
),
);


Оцените новую рубрику! 👍💡
Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileAppDevelopment #Animation #UIUX #FlutterTips
👍2
🔥 Он вам не просто логер! Почему Talker нужен вашему Flutter проекту?

Привет, Flutter-разработчики! Готовы сэкономить часы, дни и даже месяцы на отладке? Мы нашли для вас 🔥горячее видео, которое перевернет ваш подход к работе с ошибками!

Стас (Frezyx) на Flutter Conf в Москве устроил настоящий мастер-класс по своей open-source библиотеке Talker. И знаете что? Даже режиссёр трансляции признался: "Это единственный доклад, который я понял!" 😄

👇 Почему это must-watch?
🚀 Talker — не просто логер. Это "губка", которая впитывает ВСЁ:
- Ошибки виджетов, сети, платформы
- HTTP-запросы, навигацию, стейт-менеджмент
- Кастомные события (даже аналитику!)

💡 Главный секрет: история в runtime! Представьте:
Тестировщик присылает не "у меня сломалось", а готовый файл логов
На проде за 2 минуты находите корень проблемы (и вините бэкендеров 😉)
Показываете ошибки через SnackBar, модалки или даже свой UI

🎁 Что ещё внутри:
- Модульность: подключайте только нужное (Bloc, Dio, Riverpod!)
- Кастомизация логов: от цветов 🎨 до формата
- Интеграция с Firebase, Sentry, Grafana
- Готовый экран логов в приложении

👉 Смотрите запись выступления здесь или тут — без воды, с мемами и реальными кейсами!

Стас показал, как Talker, получивший грант Яндекса (600К ₽ 💰) и скачанный 320К+ раз, делает жизнь разработчика проще. Не верите? Проверьте сами:
📦 pub.dev: talker
🐙 GitHub: Frezyx/talker

💬 P.S. Уже пробовали Talker? Делитесь опытом в комментариях! А если нет — самое время начать. Ваши выходные без дебаггинга начинаются здесь 👇

#Flutter #Dart #FlutterPulse #FlutterPulseTips #FlutterPulseYoutube
Подписывайтесь → @FlutterPulse
Ещё крутые видео → #FlutterPulseYoutube
Создаём кастомную панель приложений
Потому что вы можете 😉

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



class EditorAppBar extends StatelessWidget implements PreferredSizeWidget {
const EditorAppBar({
super.key,
});

@override
Widget build(BuildContext context) {
return Row(...); // Здесь вы можете настроить свой собственный дизайн
}

@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
// Используйте эту константу для рекомендуемой высоты
}



Чтобы использовать эту кастомную AppBar, просто передайте её в свойство appBar виджета Scaffold:


child: Scaffold(
appBar: EditorAppBar(),
...
),



Оцените нашу новую рубрику по Flutter советам 👍! Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #CustomAppBar #AppBarDesign #FlutterTips
👍31
Создай расширение темы
Упростите доступ к свойствам темы вашего приложения 💻

Расширение темы позволяет упростить доступ к свойствам темы вашего приложения. Для этого нужно создать расширение класса BuildContext.



extension ApparenceKitThemeExt on BuildContext {
ApparenceKitColors get colors => Theme.of(this).extension<ApparenceKitColors>()!;
// Получение цветов темы
TextTheme get textTheme => Theme.of(this).textTheme;
// Получение текстовой темы
ApparenceKitTextTheme get fonts => Theme.of(this).extension<ApparenceKitTextTheme>()!;
// Получение шрифтов темы
ThemeData get theme => Theme.of(this);
// Получение данных темы
Brightness get brightness => Theme.of(this).brightness;
// Получение яркости темы
ApparenceKitThemeData get kitTheme => ThemeProvider.of(this).current.data;
// Получение данных темы ApparenceKit
}



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

До: сложный доступ к свойствам темы


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

@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).colorScheme.primary,
);
}
}



После: упрощенный доступ с расширением


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

@override
Widget build(BuildContext context) {
return Container(
color: context.colors.primary,
);
}
}



Оцените новую рубрику по достоинству! 👍💬 Оставляйте ваши отзывы и предложения в комментариях! 💬👇

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #UIUX #CodingTips #AppDevelopment #SoftwareDevelopment
👍3