Статья для начинающих в Riverpod

До этого пользовался Provider совместно с BLoC и недавно решился попробовать Riverpod в одном из проектов. В ходе работы столкнулся с проблемой, которую многие могут не замечать.

При использовании Logger для Http запросов он отправлял несколько запросов, даже если был отправлен лишь 1. Для устранения проблемы нужно будет создать собственный провайдер для Dio и работать уже с ним.

Первым делом: создаем localeProvider

final localeProvider = StateNotifierProvider<LocaleNotifier, Locale>((ref) {
  return LocaleNotifier();
});

class LocaleNotifier extends StateNotifier<Locale> {
  LocaleNotifier() : super(sharedPreference.locale) {
    onAppStart();
  }

  void changeLanguage(Locale locale) {
    try {
      if (!L10n.all.contains(locale)) return;
      state = locale;
    } catch (error) {
      state = sharedPreference.locale;
    }
  }

  void onAppStart() {
    try {
      final locale = sharedPreference.locale;
      state = locale;
    } catch (error) {
      state = const Locale('ru');
    }
  }
}

2. Соединяем его с MaterialApp

Обертываем наш MyApp в ProviderScope() и через метод watch "слушаем" изменение языка. И добавляем ProviderContainer в нашем main.dart файле.

final ProviderContainer container = ProviderContainer();

  runApp(
    ProviderScope(
      child: WorkspaceApp(),
    ),
  );

  Widget build(BuildContext context, WidgetRef ref) {
    final locale = ref.watch(localeProvider);
    return MaterialApp.router(
      title: "Workspace",
      locale: locale,
      routerConfig: _router,
      supportedLocales: L10n.all,
      theme: LightTheme,
      darkTheme: null,
      debugShowCheckedModeBanner: false,
      themeMode: ThemeMode.light,
      localizationsDelegates: const [
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      builder: EasyLoading.init(),
    );
  }

3. HttpQuery для get запроса

Создаем http_query.dart файл и вставляем код ниже:

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logger/logger.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';

class HttpQuery {

  final dioProvider = Provider((ref) {
    PrettyDioLogger logger = PrettyDioLogger(
      requestHeader: true,
      requestBody: true,
      responseBody: false,
      responseHeader: true,
      error: true,
      compact: true,
      maxWidth: 100,
    );
    Dio dio = Dio(BaseOptions(baseUrl: '${sharedPreference.chosenServer}/api/'));
    dio.interceptors.add(ErrorInterceptor());
    dio.interceptors.add(logger);
    return dio;
  });
  
  /* ---------------------------------- HttpQuery ---------------------------------- */

  Future<dynamic> get(
      {required String url, Map<String, dynamic>? queryParameters, Map<String, dynamic>? headerData}) async {
    try {
      container.read(dioProvider).interceptors.add(ErrorInterceptor());
      container.read(dioProvider).interceptors.add(logger);
      Map<String, dynamic> header = {
        "Content-Type": "application/json",
      };

      Map<String, dynamic> queryParameters1 = queryParameters ?? {};
      String? token = await UserSecureStorage.getToken();
      if (token != "") header.addAll({"Authorization": 'Bearer $token'});
      if (headerData != null) header.addAll(headerData);
      final Response result = await container.read(dioProvider).get(
            url,
            options: Options(
              sendTimeout: 30000,
              receiveTimeout: 60000,
              headers: header,
            ),
            queryParameters: queryParameters1,
          );
      return result.data;
    } on DioError catch (error) {
      if (error.type == DioErrorType.connectTimeout) {
        debugPrint(error.type.name);
      }
      if (error.type == DioErrorType.receiveTimeout) {
        debugPrint(error.type.name);
      }
      return error;
    }
  }
}

Наш dioProvider мы можем использовать за место getIt. В нем описываем базовые параметры для Dio, в моем случае я вставил туда базовое начало моих url запросов.

И создаем providerContainer для доступа к провайдера без WidgetRef. Далее для получения результата Response мы отправляем запрос через наш dioProvider получая доступ к нему через наш providerContainer.

4. Изменение языка

Используем вместо StatelessWidget - ConsumerWidget

class LanguagePickerWidget extends ConsumerWidget {
  const LanguagePickerWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final locale = ref.read(localeProvider);

    return DropdownButtonHideUnderline(
      child: DropdownButton(
        value: locale,
        icon: Container(width: 12),
        items: L10n.all.map(
          (locale) {
            final flag = L10n.getFlag(locale.languageCode);

            return DropdownMenuItem(
              value: locale,
              onTap: () {
                ref.read(localeProvider.notifier).changeLanguage(locale);
              },
              child: Center(
                child: Text(
                  flag,
                  style: const TextStyle(fontSize: 32),
                ),
              ),
            );
          },
        ).toList(),
        onChanged: (_) {},
      ),
    );
  }
}

Получаем доступ к нашему localeProvider и через метод notifier, который получает уведомления о изменении состоянии мы меняем наш язык.