Ответ
Да, Riverpod стал моим основным выбором для управления состоянием в новых Flutter-проектах. Я рассматриваю его как эволюцию Provider, исправляющую его ключевые боли и предлагающую более мощную, безопасную и гибкую систему.
Ключевые преимущества, которые я ценю на практике:
- Компиляционно-безопасный: Ошибки, связанные с неправильным типом провайдера или его отсутствием, отлавливаются на этапе компиляции, а не во время выполнения.
- Не зависит от BuildContext: Провайдеры можно читать и слушать в любом месте Dart-кода (в бизнес-логике, сервисах), что сильно упрощает архитектуру.
- Гибкая система провайдеров: Разные типы для разных задач (
Provider,StateProvider,StateNotifierProvider,FutureProvider,StreamProvider). - Встроенные возможности: Автоматическое кэширование, отмена запросов у
FutureProvider/StreamProvider,ref.watchдля реактивности иref.readдля разовых действий.
Пример из реального проекта:
// 1. Провайдер для сервиса (не зависит от UI)
final apiClientProvider = Provider<ApiClient>((ref) {
final dio = ref.read(dioProvider); // Читаем другой провайдер
return ApiClient(dio);
});
// 2. StateNotifierProvider для сложной бизнес-логики
final authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
final api = ref.watch(apiClientProvider); // Реактивная зависимость
return AuthNotifier(api);
});
// 3. FutureProvider для асинхронных данных с кэшированием
final userProfileProvider = FutureProvider<UserProfile>((ref) async {
final userId = ref.watch(authStateProvider.select((s) => s.userId));
final api = ref.read(apiClientProvider);
return await api.fetchUserProfile(userId!);
});
Использование в виджете с ConsumerWidget или ConsumerStatefulWidget:
class ProfileScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// ref заменяет context для доступа к провайдерам
final authState = ref.watch(authStateProvider);
final userProfileAsync = ref.watch(userProfileProvider);
return userProfileAsync.when(
data: (profile) => Scaffold(
appBar: AppBar(title: Text(profile.name)),
body: ...,
),
loading: () => Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('Error: $err')),
);
}
}
Мой опыт: Я использовал Riverpod для построения масштабируемой архитектуры в коммерческом приложении. Возможность легко комбинировать провайдеры, создавать производные состояния с select и иметь всю бизнес-логику, полностью отделённую от UI-слоя, значительно повысило тестируемость и поддерживаемость кода.
Ответ 18+ 🔞
А, Riverpod! Ну это вообще отдельная песня, ёпта. Я на него пересел, как на трон, и теперь смотрю на другие state management'ы, как на допотопные каменные топоры. Это ж не просто следующий шаг после Provider, это как пересесть с запряжённой лошади на реактивный истребитель, блядь.
Что конкретно меня, сука, подкупило:
- Ошибки ловятся ДО того, как приложение начнёт выёбываться. Не будет тебе сюрприза в рантайме, что ты не тот провайдер дернул. Компилятор тебя ещё на берегу отловит и скажет: "Мужик, ты куда? Тут не туда". Компиляционно-безопасный — это пиздец как важно.
- Отвязанность от этого вашего BuildContext. Ну реально, волнение ебать, как же задолбался я его таскать через всю вселенную, чтобы в бизнес-логике к состоянию достучаться. А тут — взял
refи читай где угодно: в сервисе, в репозитории, хоть посреди чистого поля. Удобство — овердохуища. - Провайдеры на все случаи жизни. Нужно просто значение —
Provider. Нужно простейшее состояние —StateProvider. Нужна серьёзная логика с методами —StateNotifierProvider. Асинхронщина?FutureProviderилиStreamProvider, которые ещё и кэшируют, и отменяют запросы за тебя. Не инструмент, а мечта. ref.watchиref.read— это магия. Захотел реактивности —watch. Нужно просто разово прочитать значение и не перестраиваться —read. Всё, конфликта интересов больше нет.
Смотри, как на практике выглядит эта красота:
// 1. Сервис. Сидит себе, не парится о контексте.
final apiClientProvider = Provider<ApiClient>((ref) {
final dio = ref.read(dioProvider); // Подтянул себе Dio на провайдере
return ApiClient(dio);
});
// 2. StateNotifier для авторизации — тут вся мозгоёбная логика.
final authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
final api = ref.watch(apiClientProvider); // Следим за апишкой реактивно
return AuthNotifier(api);
});
// 3. FutureProvider для профиля. Сам кэширует, сам перезапрашивает при смене userId.
final userProfileProvider = FutureProvider<UserProfile>((ref) async {
final userId = ref.watch(authStateProvider.select((s) => s.userId)); // Следим ТОЛЬКО за userId!
final api = ref.read(apiClientProvider); // Читаем апишку разово
if (userId == null) throw Exception('Нет юзера, иди нахуй');
return await api.fetchUserProfile(userId);
});
А в виджетах вообще благодать:
class ProfileScreen extends ConsumerWidget { // Наследуемся от ConsumerWidget
@override
Widget build(BuildContext context, WidgetRef ref) { // Получаем волшебный ref
final authState = ref.watch(authStateProvider);
final userProfileAsync = ref.watch(userProfileProvider); // Следим за будущим профилем
return userProfileAsync.when(
data: (profile) => Scaffold(
appBar: AppBar(title: Text(profile.name)),
body: ...,
),
loading: () => Center(child: CircularProgressIndicator()), // Пока грузится
error: (err, stack) => Center(child: Text('Ошибка, ёпта: $err')), // Если всё накрылось
);
}
}
Мой вердикт, чувак: Я запилил на нём одно коммерческое приложение нехилого масштаба. Так вот, эта штука позволяет строить архитектуру, где бизнес-логика живёт отдельной, сытой жизнью, а UI только рисует. Тестировать — одно удовольствие. Комбинировать состояния через select — вообще песня. После этого возвращаться к чему-то менее мощному — это как добровольно сесть обратно в каменный век, ядрёна вошь.