Flutter Pulse
644 subscribers
403 photos
892 links
На канале будут новости про flutter с сайтов, информация об обновлении пакетов, а также авторский контент.
Download Telegram
Изменение яркости системной панели
Отобразить системную панель с правильной яркостью на iOS и Android 🚀

При разработке мобильных приложений важно обеспечить корректное отображение системной панели на разных платформах. В этой статье мы рассмотрим, как изменить яркость системной панели в приложениях Flutter для iOS и Android.

Код для изменения яркости системной панели:

SystemChrome.setSystemUIOverlayStyle(
SystemUIOverlayStyle(
statusBarColor: Colors.transparent, // Прозрачный цвет статус-бара
statusBarBrightness: // Установка яркости статус-бара в зависимости от темы
mode == ThemeMode.light ? Brightness.light : Brightness.dark,
statusBarIconBrightness: // Установка яркости иконок статус-бара в зависимости от темы
mode == ThemeMode.light ? Brightness.dark : Brightness.light,
),
);


Обратите внимание:
- statusBarBrightness применяется только на Android.
- statusBarIconBrightness также применяется только на Android, но с инвертированными значениями для iOS.

Особенности для iOS: 🤔
На iOS строка состояния может быть изначально скрыта. Чтобы отобразить ее, откройте ваш проект в Xcode и убедитесь, что свойство status bar initially hidden не отмечено.

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

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDev #iOS #Android #FlutterTips #DevTips
👍2
Рисуем и анимируем круглый прогресс-бар с помощью Custom Painter

В этом совете мы рассмотрим, как создать анимированный круглый прогресс-бар, используя виджет CustomPaint во Flutter. Такой прогресс-бар можно использовать, например, для индикации загрузки.

Основные моменты:

1️⃣ Передача анимации в конструктор CustomPainter автоматически вызывает перерисовку.

2️⃣ При обновлении прогресса мы изменяем начало и конец анимации, а затем запускаем её. Поскольку время загрузки предсказать невозможно, анимация прогресс-бара продолжается после обновления прогресса.

Пример кода:



class RoundProgressPainter extends CustomPainter {
final double radius;
final double progress;
final Color color;
final Animation<double> animation;
final double strokeWidth;

RoundProgressPainter({
required this.radius,
required this.progress,
required this.color,
required this.animation,
required this.strokeWidth,
}) : super(repaint: animation);

@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final progressAngle = math.pi * 2 * progress;
final progressPaint = Paint()
..color = color
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;

canvas.drawCircle(
center,
radius - strokeWidth / 2,
Paint()
..color = color.withOpacity(0.1)
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke,
);

canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-math.pi / 2,
progressAngle,
false,
progressPaint,
);
}

@override
bool shouldRepaint(RoundProgressPainter oldDelegate) =>
progress != oldDelegate.progress || color != oldDelegate.color;
}





class RoundProgress extends StatefulWidget {
final double radius;
final double progress;
final Color color;
final Widget? child;
final double strokeWidth;

const RoundProgress({
super.key,
required this.radius,
required this.progress,
required this.color,
this.child,
this.strokeWidth = 4.0,
});

@override
_RoundProgressState createState() => _RoundProgressState();
}

class _RoundProgressState extends State<RoundProgress> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;

@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _controller, curve: Curves.decelerate),
);
_controller.forward(from: 0);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
void didUpdateWidget(covariant RoundProgress oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.progress != oldWidget.progress && widget.progress != null) {
_animation = Tween(begin: oldWidget.progress, end: widget.progress).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn),
);
_controller.forward(from: 0);
}
}

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) => CustomPaint(
size: Size(widget.radius * 2, widget.radius * 2),
painter: RoundProgressPainter(
radius: widget.radius,
progress: widget.progress,
color: widget.color,
animation: _animation,
strokeWidth: widget.strokeWidth,
),
child: widget.child,
),
);
}
}



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

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #UI #Animation #CustomPainter #ProgressBar #LoadingAnimation #FlutterTips
👍2
Тестирование с навигацией GoRouter
Как запускать тесты с навигацией GoRouter

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

Пример теста:



testWidgets('условие защиты возвращает false => переход на ошибочный url',
(WidgetTester tester) async {
final app = MaterialApp.router(
routerConfig: GoRouter(
initialLocation: '/page1',
routes: [
GoRoute(
path: '/page1',
builder: (context, state) => Guard(
canActivate: future.value(false),
fallbackRoute: '/page2',
),
child: const FakePage(msg: 'page1'),
),
GoRoute(
path: '/page2',
builder: (context, state) => const FakePage(msg: 'page2'),
),
],
),
);

await tester.pumpWidget(app);
await tester.pumpAndSettle(const Duration(milliseconds: 100));

expect(find.text('page2'), findsOneWidget);
});



Дополнительный совет:
Вы можете использовать ваш app router, чтобы получить точно такое же поведение маршрутизации, как и в вашем приложении.
Таким образом, вы действительно можете проверить, работает ли навигация корректно.
Встройте создание GoRouter в функцию с параметром initialLocation, чтобы ваш тест мог начинаться прямо с того места, которое вам нужно.



GoRouter createRouter(final String initialLocation) {
return GoRouter(
initialLocation: initialLocation,
routes: [
GoRoute(
path: '/page1',
builder: (context, state) => const FakePage(msg: 'page1'),
),
GoRoute(
path: '/page2',
builder: (context, state) => const FakePage(msg: 'page2'),
),
],
);
}



Оцените новую рубрику по тестированию Flutter-приложений! 👍
Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileAppDevelopment #FlutterTips #Testing #GoRouter
2👍2
Как протестировать Изолят
Запуск функции изолята в модульных тестах

При написании модульных тестов для 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
Добавьте кастомные переходы страниц с 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
Создаём кастомную панель приложений
Потому что вы можете 😉

Вы можете создать кастомную 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
Загрузка байтов изображения и отображение на холсте
Не так сложно сделать с помощью Flutter 😉

Вы когда-нибудь задумывались, как можно загрузить изображение и отобразить его на холсте в Flutter? 🤔 Давайте разберемся в этом вместе! 💡

Шаг 1: Загрузка изображения
Сначала нам нужно загрузить изображение из наших ресурсов. Для этого мы используем метод load класса rootBundle:


import 'dart:ui' as ui;
import 'package:flutter/services.dart';

// Загружаем изображение из ресурсов
final ByteData data = await rootBundle.load(path);
// Преобразуем в Uint8List
final Uint8List bytes = data.buffer.asUint8List();
// Преобразуем в ui.Image
final ui.Image image = await decodeImageFromList(bytes);



Шаг 2: Отображение на холсте
Теперь, когда у нас есть изображение в формате ui.Image, мы можем отобразить его на холсте. Для этого создадим собственный класс CustomPainter:


// Создаем новый CustomPainter
class MyPainter extends CustomPainter {
final ui.Image image;

MyPainter(this.image);

@override
void paint(Canvas canvas, Size size) {
// Рисуем изображение на холсте
canvas.drawImageRect(image, srcRect, dstRect, Paint());
}
}



Вот и все! Теперь вы знаете, как загрузить байты изображения и отобразить их на холсте во Flutter. Просто и эффективно, не правда ли? 😊

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

Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #mobiledev #appdev #codingtips #FlutterTips
👍3🔥1
Локальная база данных
Когда вы хотите, чтобы ваше приложение работало офлайн 🔄

Шаг 1: Использование пакета Drift
Для работы с локальной базой данных мы будем использовать пакет Drift. 📦


dart pub add drift
dart pub add drift_flutter
dart pub add drift_dev



Шаг 2: Создание базы данных


@DriftDatabase(tables: [TaskTable])
class Database extends $Database {
Database([QueryExecutor? e]) : super(e ?? driftDatabase(
name: 'todo-app',
native: const DriftNativeOptions(),
databaseDirectory: getApplicationSupportDirectory,
));

@override
int get schemaVersion => 2; // Версия базы данных

@override
MigrationStrategy get migration {
return MigrationStrategy(
onCreate: (m) async {
await m.createAll();
// Добавьте миграции здесь, если версия новая
},
);
}
}



Шаг 3: Создание таблицы


@DataClassName('TaskEntry')
class TaskTable extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get description => text()();

// Добавьте ваши запросы здесь
static Stream<List<TaskEntry>> getAllItems(Database database) =>
database.select(database.taskTable).watch();
}



Шаг 3: Создание или редактирование build.yaml
в корневой папке вашего Flutter-приложения 📁


targets:
$default:
builders:
drift_dev:
# Эти опции изменяют способ генерации кода
options:
databases:
default: lib/modules/drift/database.dart
sql:
dialect: sqlite
options:
version: "3.38"
modules: [fts5]


Запустите сборщик, чтобы регенерировать схему базы данных 🔄

Оцените новую рубрику! 👍💬
Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #LocalDatabase #DriftPackage #FlutterTips #AppDevelopment
👍2🔥1
Сделайте текст выбираемым

По умолчанию текст не является выбираемым. 🤔

Почему? Виджет SelectionArea позволяет выбирать текст, указывая Flutter обрабатывать отрисовку и взаимодействие для выбора текста. 📝


return MaterialApp(
home: SelectionArea(
child: Scaffold(
appBar: AppBar(title: const Text('SelectionArea Sample')),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Row 1'),
Text('Row 2'),
Text('Row 3'),
],
),
),
),
),
);


ИЛИ 🔄


const SelectableText(
'Hello! How are you?',
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
)


Используйте SelectableText вместо Text 🔁

Как выбрать между SelectionArea и SelectableText? 🤔
Используйте selectionArea, если вы хотите включить выбор на нескольких виджетах, а не только на тексте. 📚
Для одного текста просто используйте SelectableText 👍

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

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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #UIUX #CodingTips #AppDevelopment #FlutterTips
6👍1
Вложенная навигация с Go_Router
Давайте создадим боковую панель с Flutter Web и go_router 🤔

Вместо того, чтобы напрямую добавлять все маршруты (GoRoute), мы обернём их в поднавигацию 📦

Мы обернём все подмаршруты в StatefulShellRoute, чтобы управлять состоянием поднавигации 🔄

Для каждого подмаршрута у нас будет StatefulShellBranch, который может содержать несколько маршрутов 🌐

Каждый элемент будет иметь свой собственный стек навигации 📚



import 'package:go_router/go_router.dart'; // Импорт библиотеки go_router

GoRouter generateRouter() {
return GoRouter(
routes: [
// Страница без боковой панели
GoRoute(
name: 'signin',
path: '/signin',
builder: (context, state) => const SignInPage(),
),
// Страница с боковой панелью
StatefulShellRoute(
parentNavigatorKey: navigatorKey, // Ключ навигатора родительского маршрута
// Боковая панель будет отображаться слева
builder: (context, state, navigationShell) => Row(
children: [
SideBar(state: state), // Боковая панель
Expanded(child: navigationShell), // Содержимое навигации
],
),
// builder будет вызван, когда маршрут активируется
// navigationShell - виджет, который отображает содержимое маршрута
// Он будет показывать текущую последнюю страницу стека навигации для каждой ветки
navigatorContainerBuilder: (
BuildContext context,
StatefulNavigationShell navigationShell,
List<Widget> children,
) {
if (children.isEmpty) {
return SizedBox(); // Пустой контейнер, если нет дочерних элементов
}
return Scaffold(
body: children[navigationShell.currentIndex], // Отображение текущего дочернего элемента
);
},
branches: [
StatefulShellBranch(
routes: [
GoRoute(
name: 'users',
path: '/users',
builder: (context, state) => const UsersPage(),
),
GoRoute(
name: 'user profile',
path: '/users/:userId',
builder: (context, state) => const UserProfilePage(),
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
name: 'notifications',
path: '/notifications',
builder: (context, state) => const NotificationsPage(),
),
],
),
],
),
],
);
}



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

#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevelopment #FlutterTips #GoRouter #NestedNavigation #FlutterWeb
👍4👨‍💻1
Ускорьте Firebase Firestore на iOS

Firestore долго компилируется каждый раз, когда вы запускаете приложение с нуля...

...Хорошая новость в том, что есть решение, предоставленное invertase 🙌

Откройте файл podfile для iOS
Добавьте pod 'FirebaseFirestore'... следующим образом:


target 'Runner' do
use_frameworks!
use_modular_headers!
# ДОБАВЬТЕ ЭТО
pod 'FirebaseFirestore', :git => 'https://github.com/invertase/firestore-ios-sdk-frameworks.git', :tag => '11.2.0'


Вам нужно добавить соответствующую версию, используемую в вашем Flutter-зависимости.

Оцените новую рубрику лайком 👍! Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #Firebase #iOS #Firestore #MobileDevelopment #FlutterTips
👍4
Детали для улучшения формы

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


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
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
👍4
Как задать высоту строки внутри колонки

Привет, 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
Null-Aware элементы
Dart 3.8 ввёл действительно практичную новую функцию языка, называемую null-aware элементами. Она позволяет уменьшить количество шаблонного кода для проверки null элементов перед их отображением.

До:


@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
if (title != null)
title
...
]
)
);
}



После:


@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
?title <-----
...
]
)
);
}



Оцените новую рубрику лайком 👍, если считаете её полезной! 💡
Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #DartLanguage #NullAware #CodingTips #MobileDevelopment #FlutterTips
👍11🔥1
1..2..3... Гонка Future

Представьте, что вы ждёте ответ от нескольких источников. Но вам нужен только один ответ. Ладно, такое случается не часто, но представьте...

Ждём только 1 ответ

Будьте осторожны, если первый запрос выдаёт ошибку до того, как придёт второй ответ... Future завершится с ошибкой. Значит, вам нужно игнорировать эту ошибку в вашем будущем запросе.


import 'dart:async';
import 'package:http/http.dart' as http;

void main() async {
await Future.any([
getPost(SERVER_1), // первый сервер
getPost(SERVER_2), // второй сервер
]);
}

Future<String> getPost(String url) async {
final response = await http.get(Uri.parse('....'));
if (response.statusCode == 200) {
return response.body; // возвращаем тело ответа
} else {
throw Exception('Не удалось загрузить пост'); // выбрасываем исключение
}
}


Что возвращает Future.any?
Он возвращает Future, который завершается с первым результатом. Вы можете передать несколько Future разных типов.

Оцените новую рубрику! 👍💡 Все подобные новости можно найти по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #FutureAny #AsyncProgramming #FlutterTips #MobileDevelopment #ProgrammingTips
👍4
Ждём готовности представления

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

Чтобы добиться желаемого результата, следуйте простым шагам:
1. Создайте StatefulWidget.
2. В методе initState вызовите WidgetsBinding.instance.addPostFrameCallback.


@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// ваш код здесь
});
}


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

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

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

Все подобные советы ищите по хэштегу #FlutterPulseTips
#flutter #dart #flutterpulse #FlutterPulseTips #MobileDevTips #AppDev #FlutterTips #DartLang
👍5