Во Flutter размеры задаются в точках, а не в пикселях. Одна точка может соответствовать разному количеству пикселей в зависимости от плотности экрана. Чтобы изображение выглядело одинаково хорошо на всех устройствах, нужно подготовить несколько версий одного и того же файла под разные коэффициенты масштабирования.
Что такое devicePixelRatio:
Коэффициент devicePixelRatio показывает, сколько физических пикселей содержится в одной логической точке. На старых устройствах это 1x. На современных - 2x, 3x и даже 4x. Чем выше коэффициент, тем больше пикселей нужно для отображения одной точки.
Flutter автоматически выбирает нужную версию изображения, основываясь на плотности экрана устройства.
Сколько версий нужно:
Обычно достаточно подготовить изображения для коэффициентов 1x, 1.5x, 2x, 3x и 4x. Больше требуется редко. Меньше - может привести к размытию на устройствах с высокой плотностью экрана.
Каждая версия должна иметь размер, соответствующий ее коэффициенту. Например, если изображение в точках имеет размер 100x100, то для 2x нужно подготовить 200x200 пикселей, для 3x - 300x300.
Как организовать хранение:
Flutter ожидает, что файлы для разных коэффициентов будут лежать в отдельных папках. Имена папок должны соответствовать коэффициенту: x1.5, x2.0, x3.0, x4.0.
assets/
images/
x1.5/
photo.png
x2.0/
photo.png
x3.0/
photo.png
x4.0/
photo.png
photo.png
В pubspec.yaml достаточно указать корневую папку. Flutter сам найдет нужные файлы в зависимости от плотности экрана.
flutter:
assets:
- assets/images/
Подготовка изображений под разные плотности экрана - обязательный шаг для качественного отображения. Несколько версий файлов в папках x1.5, x2.0, x3.0, x4.0 и правильное указание в pubspec.yaml - все, что нужно.
Flutter делает выбор автоматически. Разработчику остается только подготовить ассеты и правильно организовать структуру. Это несложно, но влияет на внешний вид приложения на устройствах с разными экранами.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍1🙏1
В Dart 3.13 появилась фича, которую ждали годами - primary constructors. Теперь класс с полями и конструктором можно объявить в одну строку. Но путь к этому решению был долгим. Команда Dart потратила много времени на обсуждение деталей и принятие компромиссов. Боб Нюстром написал большой разбор о том, через что пришлось пройти. Давайте разберем самые важные моменты из данного разбора.
Почему синтаксический сахар важен:
Primary constructors не добавляют в язык новых возможностей. Все, что можно сделать через них, уже можно было написать старым способом. Это чистый синтаксический сахар.
Вопрос в том, зачем он нужен. Синтаксический сахар оправдан в нескольких случаях.
Зачем нужен primary constructors:
Primary constructors решают конкретную проблему: объявление класса с конструктором, который просто сохраняет параметры в поля.
Без primary constructors код выглядит так:
class Point {
final int x;
final int y;
Point(int x, int y) : x = x, y = y;
}С использованием initializing formals:
class Point {
final int x;
final int y;
Point(this.x, this.y);
}Но разработчики хотели еще короче. Чтобы можно было просто написать так:
class Point(final int x, final int y);
Основная проблема:
Главная сложность: синтаксический клиф. Это когда ты начинаешь с простого синтаксиса, а потом хочешь добавить маленькое изменение, но оно не вписывается, и ты вынужден переписывать все.
Например, у класса есть primary constructor с кучей параметров. Потом нужно добавить логирование в конструктор. Если primary constructor не поддерживает тело, придется переписать все обратно на старый синтаксис.
Чтобы избежать этого, в Dart добавили блоки this { ... } внутри класса. Они позволяют добавить тело или инициализатор для primary constructor без переписывания всего.
class FormatterOptions(
final int indent = 0,
final int pageWidth = 80,
) {
this {
log.write('Created options.');
}
}
Новый синтаксис конструкторов:
Еще одно изменение касается объявления конструкторов. Раньше нужно было повторять имя класса:
class LongClassName {
LongClassName(); // безымянный
LongClassName.create(); // именованный
}Теперь можно использовать ключевое слово new:
class LongClassName {
new(); // безымянный
new create(); // именованный
}Для фабричных конструкторов:
class AnotherLongClass {
factory () { ... } // фабричный
factory create() { ... } // именованный фабричный
}Это короче, особенно для длинных имен классов. И решает проблему с typedef и расширениями, где имя класса не всегда доступно.
Primary constructors в Dart 3.13 - это не просто очередной синтаксический сахар. Это результат долгого размышления о том, как сделать язык удобнее и проще.
Команда языка учла опыт других языков, добавила защиту от синтаксических клифов и изменила объявление конструкторов на более простое. В итоге код стал короче, , а язык чуть более приятным для повседневной работы.
Это не последнее изменение. Язык продолжает развиваться. Но primary constructors - хороший пример того, как дизайн языка может быть одновременно эволюционным и продуманным.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3❤2👍1
Разработка с ИИ-агентами за последний год сделала большой шаг вперед. Но многие все еще используют одного агента для всего: архитектуры, тестов, отладки. На маленьких задачах это работает. Когда проект становится сложнее, агент теряет контекст, начинает галлюцинировать и допускать ошибки.
Недавно в блоге Flutter вышла статья, в которой автор экспериментирует с мультиагентным подходом. Вместо одного универсального агента он создал команду из четырех специализированных ролей: архитектор, тестировщик, кодер и координатор. Задача была не простая: портировать популярную Python-библиотеку python-statemachine в статически типизированный Dart-пакет без использования рефлексии (dart:mirrors во Flutter запрещены).
Как устроена мультиагентная команда:
Основа эффективной агентной команды - четко определенные роли с ограничениями на то, что агент может делать.
Автор создал общий воркфлоу-скилл (tdd-dart-workflow) с правилами TDD и разрешениями, а также четыре ролевых скилла:
Строгое разделение ролей не дает ни одному агенту изменять и тесты и код одновременно. Координатор получает только краткий отчет о завершении задачи. Это создает когнитивный файрвол: история проб и ошибок суб-агента отбрасывается после завершения его задачи. Контекст координатора остается чистым, без переполнения токенами.
Для предотвращения гонок и непроверенных коммитов доступ к файловой системе ограничен:
Статическая типизация и TDD:
В динамических языках TDD начинается с падающего теста, который выбрасывает рантайм-ошибку. В статических языках тест для несуществующего метода не скомпилируется, и тест-раннер даже не запустится.
Решение: сначала тестировщик пишет тесты по спецификации. Затем кодер создает скелеты в lib/src/ с заглушками, которые возвращают dummy-значения или бросают UnimplementedError. Когда скелет удовлетворяет компилятору, координатор запускает dart analyze && dart test и проверяет, что тесты падают по правильным причинам. Комментарии отделяют временные заглушки от постоянных тестовых утилит.
Результат:
Итоговый Dart-пакет state_machine сохранил полную функциональность оригинальной Python-библиотеки. Кодер реализовал fluent-билдеры для поддержки Dart-синтаксиса.
Мультиагентный подход с разделением ролей работает для Flutter-разработки. Он предотвращает потерю контекста, уменьшает галлюцинации и повышает качество кода. Когнитивный файрвол позволяет сохранять чистоту контекста координатора, отбрасывая историю суб-агентов после завершения задач.
Но подход не без проблем. Необходимы четкие роли, ограничения на запись и навыки, описывающие процессы. Требуется постоянная доработка скиллов по мере возникновения новых ситуаций.
В итоге автор получил не просто Dart-пакет, а набор переиспользуемых навыков для мультиагентной разработки. Вместо одного агента, который пытается делать все, команда из четырех специализированных агентов работает эффективнее. И это, вероятно, будущее Flutter-разработки с ИИ.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍2🔥1
Вышло обновление плагина Flutter для VS Code. Изменений немного, но есть полезные: deep-link в DevTools вернулись, рефакторинги теперь запрашивают имя, а мелкие исправления делают работу чуть стабильнее.
Deep-link в DevTools вернулись:
Плагин снова поддерживает deep-link в DevTools. Теперь при возникновении ошибок вроде Overflow появляется уведомление с кнопкой, которая открывает нужную страницу DevTools и переходит к проблемному виджету или месту в коде.
Это экономит время: не нужно вручную искать проблему в Widget Inspector. Система сама подсказывает, где смотреть.
Рефакторинги запрашивают имя:
С выходом стабильной версии Flutter рефакторинги «Add a name to the constructor» и «Add a prefix to the import» теперь запрашивают имя у пользователя. Раньше плагин подставлял имя, сгенерированное сервером. Теперь можно ввести свое.
Также в будущих версиях SDK появится валидация при вводе. Ошибки будут блокировать подтверждение, а предупреждения - нет. Это позволит избежать опечаток и некорректных имен.
Мелкие исправления:
Обновление плагина - это набор точечных улучшений. Deep-link в DevTools возвращают удобство при отладке Overflow и других проблем. Рефакторинги с запросом имени дают больше контроля. Мелкие исправления делают работу стабильнее.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
3👍3❤1🔥1
Можно ли ускорить Flutter-приложение, просто выбрав другой state management? Автор данной статьи решил проверить это не на искусственном тесте, а на реальном приложении. Результат оказался неожиданным.
Как проводили тестирование:
Для эксперимента использовалось специально подготовленное Android-устройство с контролем температуры, CPU/GPU scaling и других факторов. Тестирование длилось пять дней, было проведено 2100 испытаний и 300 раундов на каждый подход.
Сравнивались семь подходов: setState как базовое решение, ChangeNotifier + Provider, Freezed + Provider + ValueNotifier, Bloc, Riverpod, GetX и Signals.
Измерялось прежде всего время build, а не искусственная скорость изменения состояния.
Результаты:
Provider + ChangeNotifier практически не отличается от обычного setState, как и Freezed + Provider + ValueNotifier.
GetX, Riverpod и Signals дают примерно +5-6 микросекунд к времени сборки. Звучит неплохо для бенчмарка, но в реальном приложении это практически ничего.
При 120 Гц бюджет одного кадра составляет примерно 8333 микросекунды. Дополнительные 6 микросекунд - это около 0,07% бюджета кадра. Разница находится далеко за пределами того, что имеет практическое значение для пользователя.
Что насчет Bloc:
Интереснее всего оказался результат Bloc. В тесте он позволил сократить среднее время сборки примерно на 21 микросекунду. Это по-прежнему ничтожно малая величина (0,263% при частоте обновления экрана 120 Гц), но, если рассматривать общую картину, это уже тот показатель, который теоретически может иметь значение.
Проблема в том, что реализация на Bloc отличалась не только библиотекой. В ней было больше мелких обновлений виджетов, а некоторые части иерархия виджетов располагалась иначе. И здесь всплывает главная сложность любых бенчмарков: мы сравниваем не state management, а архитектуру приложения.
На основании этого эксперимента нельзя честно сказать, что Riverpod быстрее Provider или Bloc быстрее GetX. В реальном приложении разница между популярными state management решениями оказалась настолько маленькой, что выбирать библиотеку только по производительности практически бессмысленно.
Гораздо важнее, сколько виджетов обновляется, насколько большая часть иерархии виджетов пересобирается, что выполняется внутри метода build, сколько времени занимает расчет расположения и отрисовка, какие операции выполняются в главном потоке.
То есть проблема обычно не в том, выбрали вы Riverpod или Provider. Проблема в том, что именно вы заставляете Flutter перестраивать.
Выбор state management должен основываться на удобстве, читаемости и поддерживаемости кода. Производительность здесь - не главный критерий. Если ваше приложение тормозит, проблема почти наверняка не в библиотеке, а в том, как организована иерархия виджетов и сколько лишних обновлений виджетов происходит.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤1🙏1
В Dart есть инструмент, который многие недооценивают. Это оператор async*. Он позволяет создавать потоки данных так же легко, как обычные функции. Без лишних классов, ручного управления подписками и бесконечных циклов с обработчиками ошибок.
Что такое генераторы:
Генераторы - это функции, которые возвращают данные по частям, а не все сразу. Они позволяют экономить память и время выполнения. Но обычно такие функции синхронные. А что делать, если нужна асинхронность?
Здесь и вступает async*. Вместо Iterable такая функция возвращает Stream. Внутри нее доступны все возможности генератора плюс await.
Как это выглядит на практике:
Вот пример функции, которая генерирует числа с задержкой:
Stream<int> generateNumbers() async* {
for (var i = 0; i < 5; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
Все просто. Мы используем привычные конструкции: цикл, await, yield. Никаких StreamController, никаких подписок, никаких ручных закрытий.
Что делает ключевое слово yield:
yield - это оператор, который возвращает очередное значение из генератора или асинхронного генератора. Когда функция помечена как async*, она не выполняется целиком сразу. Вместо этого она возвращает Stream. Каждый раз, когда выполнение доходит до yield, значение отправляется в поток и функция приостанавливается до следующего запроса.
Простыми словами: yield - это как return, но не завершает функцию, а отдает одно значение и ждет, когда попросят следующее. Функция продолжает работу с того же места, а не начинает заново.
Есть еще yield*, который передает управление другому генератору и отдает все его значения подряд. Это удобно, когда нужно объединить несколько источников данных в один поток.
async* - это не просто синтаксический сахар. Это способ писать потоки данных чисто и предсказуемо. Он экономит код, упрощает чтение и делает логику более явной. Логика генерации данных находится в одном месте и выглядит как обычный код. Все, что связано с созданием потока, обработкой ошибок и закрытием, берет на себя язык.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3🔥1👀1
Forwarded from Кот Денисова
Последние несколько лет за развитием генеративного ИИ было удобно следить как за спортивной таблицей. OpenAI выпускала GPT-4, Google отвечала Gemini, Anthropic показывала новую Claude, затем начинался следующий круг. Каждая компания приносила набор бенчмарков и объясняла, где ее модель теперь первая.
В 2026 году эта логика начала ломаться. Не потому, что модели перестали становиться мощнее. Наоборот, 1 сентября Anthropic выпустила Claude Fable 5.1 - новую топовую модель для программирования и сложной интеллектуальной работы. Проблема в другом: возможность построить более сильную модель больше не означает, что большинство клиентов захотят использовать ее для большинства задач.
Что показывают цифры:
Financial Times обратила внимание на показательный пример. Fable 5 вышла 9 июня 2026 года как одна из самых мощных моделей Anthropic. Спустя примерно два месяца на нее приходилось около 11% расходов на продукты Anthropic среди корпоративных клиентов.
Это не 11% всей выручки Anthropic, а показатель внутри конкретной выборки корпоративных расходов. Кроме того, доступ к модели был отключен 12 июня из-за американских экспортных ограничений, а глобальный релиз восстановили 1 июля. Цифра не идеальна. Но сама тенденция интереснее конкретных 11%. Компании все чаще выбирают более дешевые модели, если дополнительное качество frontier-модели не влияет на результат.
Что изменилось к 2026 году:
Разница между классами моделей стала огромной. Claude Sonnet 5 стоит $2 за миллион входных и $10 за миллион выходных токенов. Fable 5.1 - $10 и $50 соответственно.
Fable нужна там, где дополнительная вычислительная мощность действительно меняет результат: сложное программирование, длительная агентная работа, научные задачи. Но использовать ее для классификации обращений или извлечения данных из документов экономически бессмысленно, если Sonnet справляется достаточно хорошо.
В релизе Fable 5.1 Anthropic рассказывает не только о новых результатах на тестах, но и о снижении стоимости cache reads на 75%. То есть даже релиз самой мощной модели теперь приходится объяснять через экономику ее эксплуатации.
На смену лидербордам приходит оркестрация:
Perplexity делает ставку на multi-model orchestration. Разные модели используются внутри одной системы и получают те части работы, для которых подходят лучше. В 2026 году Perplexity развивает Model Council - один запрос может параллельно отправляться нескольким моделям, после чего отдельный слой сравнивает ответы и формирует итоговый результат.
Для прода именно оркестрация становится важнее, чем выбор модели. Нужно решить, какие запросы требуют дорогого inference, когда включать reasoning, сколько контекста передавать, где использовать кеш, когда переключаться на другого провайдера.
Хорошая оркестрация может влиять на стоимость и качество продукта сильнее, чем очередная замена одной frontier-модели на другую.
Так гонка закончилась или нет:
На исследовательском уровне нет. OpenAI, Google, Anthropic, xAI, DeepSeek продолжат выпускать более сильные модели. Frontier по-прежнему важен: именно там появляются возможности, которые позднее становятся дешевле и переходят в массовые модели.
Заканчивается другая гонка: попытка использовать одну максимально мощную модель как универсальный ответ на все задачи.
К 2026 году рынок раздробился. Есть быстрые модели, reasoning-модели, системы с огромным контекстом, open-weight решения, специализированные модели и архитектуры, которые автоматически переключаются между несколькими вариантами.
Позиция в одном лидерборде все хуже описывает реальную ценность модели. Для прода гораздо важнее соотношение качества, стоимости и задержки.
Максимальный интеллект остается ценным ресурсом, но использовать его на каждом запросе больше нет смысла. Вопрос сместился с «какая модель лучше?» на «какая минимальная модель достаточно хороша для этой задачи?». И это, вероятно, главный итог четырехлетней гонки.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3👀2
Flutter отлично справляется с интерфейсом, навигацией и логикой приложения. Но он не умеет в 3D, физику, AR и рендеринг в реальном времени. Зато это умеет Unity. Многие команды хотят иметь оба инструмента в одном приложении: Flutter-оболочку и Unity-сцену внутри нее.
Автор статьи делится современным способом такой интеграции. Раньше это требовало ручного экспорта Unity-библиотеки, правки settings.gradle, написания method channels, отдельной UnityPlayerActivity и повторения всего этого для iOS. Работало, но было хрупко и переделывалось при каждом изменении Unity-проекта.
Что предлагается:
Автор предложил Game Framework - преемник flutter_unity_widget от той же команды. Это CLI и SDK, которые автоматизируют экспорт и синхронизацию Unity-билда под обе платформы. Unity-сцена превращается в обычный Flutter-виджет с типобезопасным обменом сообщениями в обе стороны.
Как это работает:
Процесс состоит из нескольких шагов:
Установка CLI через Homebrew:
brew tap xraph/tap
brew install game-cli
Добавление пакетов в pubspec.yaml:
gameframework: ^0.0.3
gameframework_unity: ^0.0.4
Инициализация в main():
UnityEnginePlugin.initialize();
Экспорт Unity-проекта одной командой:
game export unity --platform android,ios
Синхронизация с Flutter-проектом:
game sync unity --platform android,ios
Эта команда сама подключает Gradle-модуль для Android и iOS-framework, без ручного редактирования settings.gradle и шаблонного кода method channels.
Unity как виджет:
После синхронизации Unity-сцена становится обычным виджетом:
GameWidget(
engineType: GameEngineType.unity,
onEngineCreated: (controller) => _controller = controller,
onMessage: _onMessage,
config: const GameEngineConfig(
fullscreen: false,
runImmediately: true,
unloadOnDispose: true,
),
)
Обмен сообщениями:
Flutter может отправлять команды в Unity:
await _controller?.sendMessage('GameManager', 'SetSpeed', '2.5');
await _controller?.sendJsonMessage('GameManager', 'UpdateScore', {
'score': 100,
'stars': 3,
});
Unity может отправлять данные обратно через небольшой мост на C#. В Flutter эти сообщения приходят в обработчик onMessage.
Интеграция Unity и Flutter перестала быть рутинной работой с Gradle и method channels. Game Framework автоматизирует экспорт, синхронизацию и обмен сообщениями. Unity-сцена становится обычным виджетом, который можно встроить в любое Flutter-приложение.
Проект бесплатный и с открытым исходным кодом. Если вы хотите добавить 3D, физику или AR в свое Flutter-приложение, стоит посмотреть на этот инструмент.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥2❤1
В Flutter с GenUI агент может собирать экраны во время работы приложения. Но арифметику лучше не передавать ему: это тратит токены, требует ответа по сети и делает результат менее предсказуемым. Клиентские функции A2UI позволяют зарегистрировать Dart-функцию, ее параметры и тип результата. Агент вызывает ее из сообщения, а приложение выполняет код на устройстве.
Что решают клиентские функции:
Отправлять простые математические запросы в LLM неэффективно. Это добавляет задержку на сеть и расходует лишние токены. Клиентские функции позволяют агенту вычислять значения локально на устройстве: стоимость ингредиентов, налоги, конвертацию единиц.
Разделение труда выглядит так. Flutter-клиент объявляет доступные функции в каталоге GenUI: сообщает агенту, какие локальные операции можно вызвать, какие параметры они ожидают и что возвращают. Агент решает, когда и где показать компонент, и отправляет A2UI-выражение с вызовом функции и нужными аргументами. Клиент выполняет выражение локально и сразу отображает результат.
Это избавляет от лишних сетевых запросов, обеспечивает единообразное форматирование и снижает расход токенов.
Как устроена клиентская функция:
Синхронные клиентские функции наследуются от SynchronousClientFunction. Вот пример функции для расчета стоимости ингредиента:
class CalculateCostFunction extends SynchronousClientFunction {
const CalculateCostFunction();
@override
String get name => 'calculateCost';
@override
String get description =>
'Calculates the cost for a certain quantity of an ingredient. '
'Returns a formatted dollar string (for example, \$4.50).';
@override
ClientFunctionReturnType get returnType => ClientFunctionReturnType.string;
@override
Schema get argumentSchema => S.object(
properties: {
'ingredient_id': S.string(description: 'The ID of the ingredient.'),
'quantity': S.number(description: 'The quantity of the ingredient.'),
},
required: ['ingredient_id', 'quantity'],
);
@override
Object? executeSync(JsonMap args, ExecutionContext context) {
final ingredientId = args['ingredient_id'].toString();
final quantity = num.tryParse(args['quantity'].toString())?.toDouble();
if (quantity == null || quantity < 1) {
return '\$0.00';
}
final cost = CostService().fetchPrice(ingredientId, quantity);
return '\$${cost.toStringAsFixed(2)}';
}
}
Ключевые моменты:
Этот паттерн похож на тот, что используется для элементов каталога UI-компонентов. Оба предоставляют агенту метаданные для рассуждений и Dart-логику, которая делает что-то полезное: создает виджеты или вычисляет значение.
Регистрация в каталоге:
Чтобы агент знал о функции, она регистрируется в каталоге GenUI:
final commisCatalog = Catalog(
[
cateringJobItem,
recipeLineCatalogItem,
ingredientLineCatalogItem,
navigationCardCatalogItem,
simpleCardCatalogItem,
],
functions: [
CalculateCostFunction(),
],
catalogId: 'commis_catalog',
);
При инициализации сессии PromptBuilder из genui инспектирует каталог и автоматически извлекает объявления клиентских функций, добавляя их имена, описания и схемы в системный промпт для Gemini.
Клиентские функции A2UI позволяют вынести вычисления из ИИ-агента в Dart-код. Это снижает задержку, экономит токены и делает результат предсказуемым.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥1
Вышла первая стабильная версия Genkit Dart - фреймворка для разработки ИИ-функционала и ИИ-агентов на Dart. Теперь можно строить приложения с поддержкой разных моделей, инструментов и интеграций, не выходя из экосистемы Dart и Flutter.
Раньше фреймворк был в статусе preview. Обратная связь от сообщества помогла доработать API и расширить набор инструментов для задач на проде. Теперь релиз готов к использованию в реальных проектах.
Как установить:
Добавить Genkit в проект можно одной командой:
dart pub add genkit
Также можно установить скиллы для ИИ-ассистентов, чтобы они знали актуальные API и практики Genkit Dart:
npx skills add genkit-ai/skills
Единый API для разных моделей:
Genkit поддерживает Google Gemini, Anthropic Claude, OpenAI и модели, совместимые с OpenAI, через один интерфейс. Провайдеры регистрируются как плагины и их можно менять без переписывания логики приложения.
final ai = Genkit(plugins: [googleAI(), anthropic()]);
final prompt = 'Suggest a weekend getaway from San Francisco.';
final fromGemini = await ai.generate(
model: googleAI.gemini('gemini-flash-latest'),
prompt: prompt,
);
final fromClaude = await ai.generate(
model: anthropic.model('claude-sonnet-5-5'),
prompt: prompt,
);
Типобезопасность через flows:
Genkit позволяет оборачивать ИИ-логику в flows - строго типизированные функции, которые легко тестировать и разворачивать как HTTP-эндпоинты. Схемы данных описываются один раз на Dart и используются и на сервере и в приложении.
@Schema()
abstract class $TripRequest {
String get destination;
int get days;
}
Это дает сквозную типобезопасность между бэкендом и Flutter-приложением.
Где выполняется код:
ИИ-логика пишется на стандартном Dart, поэтому ее можно запускать где угодно:
Инструменты и отладка:
Genkit включает локальный Developer UI для тестирования flows, экспериментов с промптами и пошаговой проверки трассировок выполнения.
genkit start -- dart run server/bin/server.dart
Агентские сценарии:
С момента preview Genkit Dart расширился возможностями для многошаговых агентских рабочих процессов: прерывания с участием человека, middleware для генерации, управление промптами, телеметрией на проде.
Инструменты позволяют моделям вызывать Dart-функции для получения данных или выполнения действий. Когда действие требует подтверждения пользователя, инструмент может приостановить генерацию, вернув .interrupt(...) вместо .response(...). Это гарантирует, что модель не обойдет проверку.
Middleware подключается напрямую в цикл генерации, чтобы перехватывать вызовы модели, внедрять инструменты или изменять запросы и ответы. Можно подключать готовые возможности вроде автоматических повторов и правил подтверждения инструментов.
Genkit Dart 1.0 - это стабильный инструмент для построения ИИ-функционала и агентов на Dart. Единый API для разных моделей, сквозная типобезопасность, поддержка агентских сценариев и телеметрии на проде.
Раньше для подобных задач приходилось уходить в другие языки или собирать все вручную. Теперь ИИ-логика остается в знакомой экосистеме Dart и Flutter. Это упрощает разработку, тестирование и поддержку.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1🔥1
Бесконечная лента - привычная часть многих приложений. Но реализация пагинации часто превращается в повторяющийся код. Хочется один раз собрать универсальный механизм и переиспользовать его в разных экранах. В статье на примере Riverpod рассказывается, как это сделать без сторонних пакетов.
В чем суть проблемы:
Готовые пакеты для пагинации существуют. Но многие из них устарели, используют устаревшие классы или предполагают, что номер страницы всегда число. Это не всегда так. Например в банковском приложении транзакции могут группироваться по датам. Тогда индекс страницы - это дата или строка, а не целое число.
Решение - построить собственный контроллер поверх AsyncNotifier. Он будет хранить текущий индекс, загружать первую страницу и добавлять следующую к уже полученным элементам.
Как устроен контроллер:
Контроллер принимает два типа: T для данных и I для индекса страницы. Ему нужны три вещи: начальный индекс, способ загрузки страницы и способ вычисления следующего индекса.
abstract class PaginationController<T, I> {
I get initialPage;
late I currentPage = initialPage;
FutureOr<List<T>> loadPage(I page);
Future<void> loadNextPage();
I nextPage(I currentPage);
}
Затем создается механизм, который реализует этот интерфейс поверх AsyncNotifier.
mixin AsyncPaginationController<T, I> on AsyncNotifier<List<T>>
implements PaginationController<T, I> {
@override
late I currentPage = initialPage;
@override
FutureOr<List<T>> build() async => loadPage(initialPage);
@override
Future<void> loadNextPage() async {
final newState = await AsyncValue.guard(() async {
currentPage = nextPage(currentPage);
final elements = await loadPage(currentPage);
return [...?state.valueOrNull, ...elements];
});
state = newState;
}
}
Механизм берет на себя хранение текущего индекса, загрузку первой страницы и добавление новых элементов. Конкретному экрану остается только определить начальный индекс, запрос данных и следующий индекс.
Как выглядит использование:
class RepositoriesViewModel extends AsyncNotifier<List<Repository>>
with AsyncPaginationController<Repository, int> {
@override
int get initialPage => 1;
@override
FutureOr<List<Repository>> loadPage(int page) async {
final (_, items) = await repository.fetchRepositories(page: page);
return items;
}
@override
int nextPage(int currentPage) => currentPage + 1;
}
Все, что требуется от экрана - задать три параметра.
Как подключить в интерфейсе:
Осталось отслеживать конец прокрутки. Для этого используется NotificationListener.
NotificationListener(
onNotification: (ScrollNotification info) {
if (info is ScrollEndNotification &&
info.metrics.pixels >= info.metrics.maxScrollExtent) {
viewModel.loadNextPage();
}
return true;
},
child: CustomScrollView(...),
)
Когда пользователь прокручивает список до конца, вызывается загрузка следующей страницы.
Пагинация на Riverpod может быть переиспользуемой. Контроллер поверх AsyncNotifier хранит индекс, загружает страницы и добавляет новые элементы. Конкретный экран определяет только начальный индекс, запрос и следующий индекс.
Такой подход дает чистую архитектуру, поддержку нечисловых индексов и легкое расширение поиском, фильтрацией и обновлением жестом.
Подписаться на канал:
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤1