河荚 |真正需要多少个 Provider 才能只观察 class 的一个状态
Riverpod | How many Providers are really needed to watch only a single state of a class
我遵循了 this 优秀 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
和 _timeLeftProvider
,据我所知,应用程序运行正常。所以,我的问题是:
- 创建和使用
buttonProvider
和 timeLeftProvider
有什么必要?
- 真正需要多少个提供商?
非常感谢!
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 秒过去,最终我在两种情况下得到相同的结果:
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),
);
}
}
我做错了什么?
这些提供程序用于防止不必要的重建,但根本不是必需的。只创建你需要的提供者——特别是因为这些提供者永远不会在应用程序生命周期中被丢弃,它们只是被浪费了 space。但是,防止不必要的重建应该是重中之重。
在链接的文章中,作者正在使用包作者推荐的 workaround 来防止在侦听 StateNotifier 的特定属性时重建。所以,就目前而言,这是完成任务的最有效方法。如果引入新功能来解决它,我将尝试更新此答案。
我会参考 package creator's examples 了解更多上下文。
这是一个简单的例子,说明为什么您可以使用多个提供程序来缓存来自外部 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
提供程序,它将 return 之前获取的结果。这可以在没有多个提供者的情况下实现吗?是的 - 在大多数情况下,这都是正确的。创建供应商是为了方便和高效。所以不要害怕使用许多提供程序,但不要为了创建它们而创建它们。
也就是说,您链接的文章内容丰富,值得赞赏。
我遵循了 this 优秀 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
和 _timeLeftProvider
,据我所知,应用程序运行正常。所以,我的问题是:
- 创建和使用
buttonProvider
和timeLeftProvider
有什么必要? - 真正需要多少个提供商?
非常感谢!
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 秒过去,最终我在两种情况下得到相同的结果:
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),
);
}
}
我做错了什么?
这些提供程序用于防止不必要的重建,但根本不是必需的。只创建你需要的提供者——特别是因为这些提供者永远不会在应用程序生命周期中被丢弃,它们只是被浪费了 space。但是,防止不必要的重建应该是重中之重。
在链接的文章中,作者正在使用包作者推荐的 workaround 来防止在侦听 StateNotifier 的特定属性时重建。所以,就目前而言,这是完成任务的最有效方法。如果引入新功能来解决它,我将尝试更新此答案。
我会参考 package creator's examples 了解更多上下文。
这是一个简单的例子,说明为什么您可以使用多个提供程序来缓存来自外部 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
提供程序,它将 return 之前获取的结果。这可以在没有多个提供者的情况下实现吗?是的 - 在大多数情况下,这都是正确的。创建供应商是为了方便和高效。所以不要害怕使用许多提供程序,但不要为了创建它们而创建它们。
也就是说,您链接的文章内容丰富,值得赞赏。