Статья для начинающих в 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, который получает уведомления о изменении состоянии мы меняем наш язык.
nikita_dol
Зачем добавлять новые при каждом вызове
get
? Лучше поместить это вdioProvider
Зачем создавать контейнер для 1 зависимости?
sharedPreference
лучше брать из контейнераGaryshker Автор
Спасибо, исправлю)
Но как по-другому, без контейнера получить доступ к провайдеру риверпода?
nikita_dol
В целом в корне дерева создаётся контейнер с помощью
ProviderScope
или если нужно, то создаётся ранее и потом вставляется в корень дерева с помощьюUncontrolledProviderScope
Так же, обычно провайдеры создают как top-level variables, что бы они не пересоздавались.
Так же достаточно удобная возможность - использование
riverpod
как DI:Кстати, я пересмотрел статью ещё раз и не понял как связан
LocaleNotifier
иHttpQuery
Garyshker Автор
Спасибо, понятнее стало)
Возможно я неправильно объяснял в начале, там изменение языка это по сути лишь пример. Для рядового, кто только щупает риверпод, то для них при его использовании могут быть проблемы с мульти-запросами, во-о-от я и попытался своим решением поделиться, так как в интернете ничего особо не нашел. Поэтому рад к корректировкам и благодарен тебе.