Есть ли у вас опыт работы с Riverpod во Flutter?

Ответ

Да, Riverpod стал моим основным выбором для управления состоянием в новых Flutter-проектах. Я рассматриваю его как эволюцию Provider, исправляющую его ключевые боли и предлагающую более мощную, безопасную и гибкую систему.

Ключевые преимущества, которые я ценю на практике:

  1. Компиляционно-безопасный: Ошибки, связанные с неправильным типом провайдера или его отсутствием, отлавливаются на этапе компиляции, а не во время выполнения.
  2. Не зависит от BuildContext: Провайдеры можно читать и слушать в любом месте Dart-кода (в бизнес-логике, сервисах), что сильно упрощает архитектуру.
  3. Гибкая система провайдеров: Разные типы для разных задач (Provider, StateProvider, StateNotifierProvider, FutureProvider, StreamProvider).
  4. Встроенные возможности: Автоматическое кэширование, отмена запросов у 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, это как пересесть с запряжённой лошади на реактивный истребитель, блядь.

Что конкретно меня, сука, подкупило:

  1. Ошибки ловятся ДО того, как приложение начнёт выёбываться. Не будет тебе сюрприза в рантайме, что ты не тот провайдер дернул. Компилятор тебя ещё на берегу отловит и скажет: "Мужик, ты куда? Тут не туда". Компиляционно-безопасный — это пиздец как важно.
  2. Отвязанность от этого вашего BuildContext. Ну реально, волнение ебать, как же задолбался я его таскать через всю вселенную, чтобы в бизнес-логике к состоянию достучаться. А тут — взял ref и читай где угодно: в сервисе, в репозитории, хоть посреди чистого поля. Удобство — овердохуища.
  3. Провайдеры на все случаи жизни. Нужно просто значение — Provider. Нужно простейшее состояние — StateProvider. Нужна серьёзная логика с методами — StateNotifierProvider. Асинхронщина? FutureProvider или StreamProvider, которые ещё и кэшируют, и отменяют запросы за тебя. Не инструмент, а мечта.
  4. 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 — вообще песня. После этого возвращаться к чему-то менее мощному — это как добровольно сесть обратно в каменный век, ядрёна вошь.