Flutter Friendly
845 subscribers
143 photos
60 videos
1 file
128 links
Канал Friflex о разработке на Flutter. Обновления, плагины, полезные материалы — превращаем знания в реальный опыт, доступный каждому разработчику.

🔗 Наш канал для разработчиков: @friflex_dev
🔗 Канал о продуктовой разработке: @friflex_product
Download Telegram
Конкурс для легких на подъем. Разыгрываю 2 билета на CrossConf

Это главная конференция по кроссплатформенным технологиям в России и СНГ, которая пройдет уже в эту пятницу, 17 октября, в Москве.

В программе — двойной поток по Flutter от спикеров из Магнит Маркета, Лаборатории Касперского, BetBoom, Яндекс Про, Friflex и других технологичных компаний, доклады по Crossplatform и Al, встреча с самой активной частью Flutter-сообщества. Я тоже выступаю — с докладом «Flutter везде, и даже в чайнике».

Условия конкурса:
⭐️Подписаться на @mobile_developing и @flutterfriendly
⭐️Нажать «участвую» под этим постом

Победителя узнаем 16 октября. Удачи🪩
Please open Telegram to view this post
VIEW IN TELEGRAM
🟢Проблема Scrollable.ensureVisible в ListView: почему она до сих пор не решена

Привет, это Катя, Flutter Dev Friflex. Сегодня расскажу о давней проблеме — ограничении метода Scrollable.ensureVisible, которое не работает для элементов, находящихся за пределами видимости в ListView.

Суть проблемы
Метод Scrollable.ensureVisible должен прокручивать список до нужного виджета, но работает он только для элементов, которые уже отрендерены на экране. Когда элемент находится далеко за пределами видимости, Flutter просто не создает для него контекст, и метод перестает работать.

Как проявляется
Допустим, у вас ListView с 11 элементами. При попытке прокрутить к элементу с индексом 1-7 все работает отлично. Но как только пытаетесь прокрутить к 8-му, 9-му или дальше — ничего не происходит.

// Не работает для элементов вне экрана
WidgetsBinding.instance.addPostFrameCallback((_) {
  Scrollable.ensureVisible(selectedDataKey.currentContext);
  // currentContext будет null для невидимых элементов!
});


Почему до сих пор не исправлено
Команда Flutter признала эту проблему еще в 2018 году. Причина — техническая сложность реализации. Чтобы прокрутить к элементу, Flutter должен знать его позицию и размер. Но для невидимых элементов эта информация недоступна, так как они еще не созданы.

Flutter не знает:
▫️в какую сторону прокручивать список
▫️какой offset использовать
▫️какой высоты будет целевой элемент

Попытка перебрать все элементы для поиска нужного была бы слишком дорогой операцией.

Workaround решения

Вариант 1: SingleChildScrollView для статичных списков

Если список небольшой и не меняется, замените ListView на SingleChildScrollView с Column:

SingleChildScrollView(
  child: Column(
    children: [
      Container(key: firstKey, child: Text('Item 1')),
      Container(key: secondKey, child: Text('Item 2')),
      // Все элементы рендерятся сразу
    ],
  ),
)

// Теперь ensureVisible работает
Scrollable.ensureVisible(firstKey.currentContext);



Минус: не подходит для длинных списков — все элементы рендерятся сразу, что убивает производительность.

Вариант 2: ScrollablePositionedList

Самое популярное решение — использовать пакет scrollable_positioned_list, который добавляет нужную функциональность:

ItemScrollController _scrollController = ItemScrollController();

ScrollablePositionedList.builder(
  itemScrollController: _scrollController,
  itemCount: items.length,
  itemBuilder: (context, index) => items[index],
)

// Прокрутка к любому индексу
_scrollController.scrollTo(
  index: 150,
  duration: Duration(seconds: 1),
  curve: Curves.ease,
);


Это решение работает даже для элементов далеко за пределами экрана.

Вариант 3: Ручная прокрутка жестами

Хелпер для тестов, который эмулирует прокрутку до появления элемента:

Future<void> ensureVisibleByScrolling(
  Finder finder, {
  required Offset scrollFrom,
  Offset scrollBy = const Offset(0, -50),
  int maxScrolls = 100,
}) async {
  final gesture = await widgetTester.startGesture(scrollFrom);
  
  for (var i = 0; i < maxScrolls; i++) {
    await gesture.moveBy(scrollBy);
    await pump();
    
    if (widgetTester.widgetList(finder).length == 1) {
      break;
    }
  }
  
  await gesture.cancel();
  await widgetTester.ensureVisible(finder);
}


Этот метод постепенно прокручивает список, пока элемент не появится на экране.

Статус в 2025 году
К сожалению, официального решения в стандартной библиотеке Flutter до сих пор нет. Issue открыт с 2018 года, и команда Flutter признала, что не планирует в ближайшее время добавлять нативный scrollToIndex для ListView.

А какой вариант решения проблемы используете вы?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍127🔥5
This media is not supported in your browser
VIEW IN TELEGRAM
Для тех, кто говорит на языке Flutter! Сегодня на CrossConf разыгрываем футболки и игру мемори.

Условия простые:
Подписаться на @flutterfriendly

Написать под этим постом: «Я сегодня на CrossConf»

Троих победителей определим с помощью генератора случайных чисел в 17:30. Забрать приз можно будет на стенде Friflex.
Please open Telegram to view this post
VIEW IN TELEGRAM
9👍5🥰3😢1
Всем перевет, Серёже предложили рассказать про CrossConf, его доклады.
Пойду слушать, все расскажу 💼
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8👍5👻2🏆1
Адель рассказал про то, как они пытаются продвинуть Аврору для разработчиков, сделал довольно прикольный uikit, аналогичный Cupertino и Fluent, с поддержкой нативных фич

В принципе прикольно 🤔

Уже вижу как флаттер разработчики пишут псевдонативные прилки под Аврору 😁
Please open Telegram to view this post
VIEW IN TELEGRAM
4👍2
This media is not supported in your browser
VIEW IN TELEGRAM
😁5
Flutter Friendly
Video message
Стас Чернышев жалуется (объективно) на Dart 🎉
Please open Telegram to view this post
VIEW IN TELEGRAM
Станислав Ильин рассказал что сложные вещи в начале лучше не делать, да и вообще пишите по простому, чтобы всем было понятно 🤙

Пришел в этот раз без костюма Марио, я расстроен 😭
Please open Telegram to view this post
VIEW IN TELEGRAM
6🔥5
Юра показал, что флаттер может быть везде, даже можно под чайник собрать ui 🥳

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

Хотя, как фишка, вдруг кому то пригодиться, недавно под Tizen и webos делал мини кинотеатр с стримингом, было больно интересно 😁 (
Please open Telegram to view this post
VIEW IN TELEGRAM
👍117👾4
На пятых докладах из двух потоков у меня начала плавиться голова 🫠

Андрей рассказал про то, как ПО для управления дрона писали на флухтере, много низкоуровневых штук, как будто оказался на парах в вузе по ассемблеру 💀

Надо потыкать дрона, попробовать поуправлять 🙈
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍3
Никита уже рассказывал про свой пакет для BDUi (Duit), как делал оптимизацию новой версию

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

Конструктор как у divkit от Яндекса, но и написание без jsonов 🔔
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥64👍3
This media is not supported in your browser
VIEW IN TELEGRAM
Ценный артефакт: книга «Основы Flutter» c афтографами авторов. Разыграем в канале?
👍20💯6
Подвели итоги конкурса на CrossConf. Поздравляем:

@malou1337
@Suneru
@swogf

Ждем победителей у стенда Friflex
5👏4
@floral_whale (Роза) ❤️, рассказывала про их сервис локализации с OTA обновлениями, прикольно

Из известных мне вроде знаю только о phrase

Ждем когда доработают до конца 👍
Выглядит вроде как production-ready
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥97
Всем привет, это Роза, Flutter dev Friflex!

Сегодня мы разберем, как быстро поднять и использовать собственное облачное хранилище MinIO S3.

MinIO — это полностью совместимый с S3-протоколом объектный сторедж для хранения файлов, резервных копий и больших датасетов.

MinIO поддерживает S3 API (операции GET, PUT, DELETE ), а также:
✔️Управление бакетами: Bucket policy, versioning и lifecycle rules
✔️Безопасность: шифрование на стороне сервера и клиента
✔️Поддержку Multi-user / Multi-tenant окружений
✔️Простую интеграцию с любыми S3-SDK и библиотеками

Он совместим с разными S3-инструментами, легко масштабируется благодаря архитектуре Shared-Nothing, обрабатывает запросы параллельно, использует Erasure Coding для надежности и восстановления данных, а целостность хранения обеспечивается криптографическими хэшами.

Для чего можно использовать MinIO:
▪️Приватное облако — хранение изображений и файлов пользователей
▪️AI/ML и Big Data — хранение больших наборов данных
▪️Бэкапы — резервное копирование баз данных и виртуальных машин

Использование MinIO во Flutter
Благодаря S3-совместимости, MinIO можно использовать как локальное S3 для быстрой разработки приложений на Flutter.

Для работы подходит пакет minio, совместимый с MinIO, AWS S3 и Firebase Storage.

Чтобы начать работу, достаточно создать клиент, передав данные S3-сервера:

import 'package:minio/minio.dart';

MinioClient minio = MinioClient (
  endPoint : 'your-s3-endpoint-url.com', // Замените на адрес вашего MinIO
  accessKey : 'your-access-key',        // Лучше передавать через переменные окружения (env)
  secretKey : 'your-secret-key',        // Лучше передавать через переменные окружения (env)
  useSSL : false, // Установите true, если ваш сервер S3 использует HTTPS
);


Библиотека предоставляет удобные методы для работы с объектами. Рассмотрим некоторые из них:

◽️Загрузка объекта в S3
Метод putObject позволяет загружать объекты из потока байтов (Stream<Uint8List>).

Future<AppFileLink> uploadFile({
  required String bucket,
  required String object,
  required Stream<Uint8List> data,
}) async {
  // putObject выполняет загрузку
  return minio 
    .putObject(
      bucket,
      object,
      data,
    )
    // После успешной загрузки получаем публичную ссылку
    .then((_) => getPublicUrl(bucket: bucket, object: object)); 
}

   
◽️Загрузка с прогрессом
Для больших файлов можно отслеживать ход загрузки с помощью колбэка onProgress:

await minio.putObject(
  'mybucket', 
  'myobject', 
  Stream<Uint8List>.value(Uint8List(1024)),
  onProgress: (bytes) {
    print('$bytes uploaded');
  },
);


◽️Получение временно подписанной ссылки (Presigned URL)
Для безопасного предоставления доступа к файлу без публичного доступа к бакету используется временно подписанная ссылка.

Future<AppFileLink> getPublicUrl({
  required String bucket,
  required String object,
  Duration expires = const Duration(days: 7), // Срок действия ссылки
}) async {
  final link = await minio.presignedGetObject(
    bucket,
    object,
    expires: expires.inSeconds, // Срок действия в секундах
  );
  // Возвращаем ссылку
  // ...
}


📎С полным списком методов можно ознакомиться в документации библиотеки

Продолжение — в комментариях 👇
🔥84👍4