#flutter #flutter-provider #state-management #riverpod
#flutter #flutter-provider #управление состоянием #riverpod
Вопрос:
Я следовал этому превосходному руководству Riverpod. На последних шагах автор использует следующий код:
final _buttonState = Provider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
final buttonProvider = Provider<ButtonState>((ref) {
return ref.watch(_buttonState);
});
и
final _timeLeftProvider = Provider<String>((ref) {
return ref.watch(timerProvider.state).timeLeft;
});
final timeLeftProvider = Provider<String>((ref) {
return ref.watch(_timeLeftProvider);
});
Я попытался использовать _buttonState
and _timeLeftProvider
и, насколько я вижу, приложение работает правильно. Итак, мои вопросы:
- Какая необходимость в создании и использовании
buttonProvider
andtimeLeftProvider
? - Сколько провайдеров действительно необходимо?
Большое вам спасибо!
ОБНОВЛЕНИЕ 2020-10-26 ( main.dart
код и выходное изображение)
Мой main.dart
код:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_timer_app/timer.dart';
final timerProvider = StateNotifierProvider<TimerNotifier>(
(ref) => TimerNotifier(),
);
final _buttonState = Provider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
final buttonProvider = Provider<ButtonState>((ref) {
return ref.watch(_buttonState);
});
final _timeLeftProvider = Provider<String>((ref) {
return ref.watch(timerProvider.state).timeLeft;
});
final timeLeftProvider = Provider<String>((ref) {
return ref.watch(_timeLeftProvider);
});
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('building MyHomePage');
return Scaffold(
appBar: AppBar(title: Text('My Timer App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TimerTextWidget(),
SizedBox(height: 20),
ButtonsContainer(),
],
),
),
);
}
}
class TimerTextWidget extends HookWidget {
const TimerTextWidget({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final timeLeft = useProvider(timeLeftProvider);
print('building TimerTextWidget $timeLeft');
return Text(
timeLeft,
style: Theme.of(context).textTheme.headline2,
);
}
}
class ButtonsContainer extends HookWidget {
const ButtonsContainer({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('building ButtonsContainer');
final state = useProvider(buttonProvider);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (state == ButtonState.initial) ...[
StartButton(),
],
if (state == ButtonState.started) ...[
PauseButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.paused) ...[
StartButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.finished) ...[
ResetButton(),
],
],
);
}
}
class StartButton extends StatelessWidget {
const StartButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('building StartButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).start,
child: Icon(Icons.play_arrow),
);
}
}
class PauseButton extends StatelessWidget {
const PauseButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('building PauseButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).pause,
child: Icon(Icons.pause),
);
}
}
class ResetButton extends StatelessWidget {
const ResetButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('building ResetButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).reset,
child: Icon(Icons.replay),
);
}
}
Если я нажму на кнопку ‘Воспроизвести’, а затем подожду 10 секунд, в итоге я получу тот же результат в 2 случаях:
ОБНОВЛЕНИЕ 2020-10-27 ( main.dart
код без использования buttonProvider
и timeLeftProvider
)
Это результат, даже если buttonProvider
и timeLeftProvider
не используются, как в следующем main.dart
:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_timer_app/timer.dart';
final timerProvider = StateNotifierProvider<TimerNotifier>(
(ref) => TimerNotifier(),
);
final _buttonState = Provider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
// final buttonProvider = Provider<ButtonState>((ref) {
// return ref.watch(_buttonState);
// });
final _timeLeftProvider = Provider<String>((ref) {
return ref.watch(timerProvider.state).timeLeft;
});
// final timeLeftProvider = Provider<String>((ref) {
// return ref.watch(_timeLeftProvider);
// });
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('building MyHomePage');
return Scaffold(
appBar: AppBar(title: Text('My Timer App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TimerTextWidget(),
SizedBox(height: 20),
ButtonsContainer(),
],
),
),
);
}
}
class TimerTextWidget extends HookWidget {
const TimerTextWidget({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
final timeLeft = useProvider(_timeLeftProvider);
print('building TimerTextWidget $timeLeft');
return Text(
timeLeft,
style: Theme.of(context).textTheme.headline2,
);
}
}
class ButtonsContainer extends HookWidget {
const ButtonsContainer({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('building ButtonsContainer');
final state = useProvider(_buttonState);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (state == ButtonState.initial) ...[
StartButton(),
],
if (state == ButtonState.started) ...[
PauseButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.paused) ...[
StartButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.finished) ...[
ResetButton(),
],
],
);
}
}
class StartButton extends StatelessWidget {
const StartButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('building StartButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).start,
child: Icon(Icons.play_arrow),
);
}
}
class PauseButton extends StatelessWidget {
const PauseButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('building PauseButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).pause,
child: Icon(Icons.pause),
);
}
}
class ResetButton extends StatelessWidget {
const ResetButton({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('building ResetButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).reset,
child: Icon(Icons.replay),
);
}
}
Что я делаю не так?
Комментарии:
1.
buttonProvider
иtimeLeftProvider
созданы для предотвращения ненужных перестроек времени, оставленного строкой, и различных кнопок.
Ответ №1:
Эти провайдеры используются для предотвращения ненужных перестроек, но в принципе не являются необходимыми. Создавайте только тех поставщиков, которые вам нужны, тем более что эти поставщики никогда не будут удалены в жизненном цикле приложения, они просто потрачены впустую. пространство. Однако предотвращение ненужных перестроек должно быть главным приоритетом.
В связанной статье автор использует обходной путь, рекомендованный автором пакета, для предотвращения перестроек при прослушивании определенного атрибута StateNotifier . Итак, на данный момент это наиболее эффективный способ выполнения задачи. Я постараюсь обновить этот ответ, если для его решения будут введены новые функциональные возможности.
Я бы сослался на примеры создателя пакета для получения большего контекста.
Вот краткий пример того, почему вы можете использовать несколько провайдеров для кэширования ответов от внешнего API:
class ExampleApiRepository {
ExampleApiRepository(this._read);
static final provider = Provider((ref) => ExampleApiRepository(ref.read));
final Reader _read;
Future<Example> search(String query) async {
final response = await _call('api/example/$query');
return Example.fromJson(response.data);
}
}
final searchExample = FutureProvider.family<Example, String>((ref, query) async {
return ref.watch(ExampleApiRepository.provider).search(query);
});
В этом примере, если тот же запрос передается searchExample
поставщику, он вернет ранее полученный результат. Может ли это быть достигнуто без нескольких провайдеров? Да — и для большинства случаев это будет справедливо. Создание провайдера — это удобство и эффективность. Так что не бойтесь использовать много провайдеров, но не создавайте их ради их создания.
Тем не менее, статья, на которую вы ссылались, является информативной и ценной.
Комментарии:
1. Большое вам спасибо!
2. Эти провайдеры фактически предложены создателем Riverpod для предотвращения ненужных перестроек.
3. Привет, @gegobyte, я обновил свой ответ, чтобы отразить это. Спасибо, что указали на это.
4. Я решил вытащить репозиторий и проверить себя. @Marco прав. Я не уверен, что здесь думать, но эти провайдеры вообще не влияют на перестроения.
5. Я думаю, что это поможет понять.