河荚 |真正需要多少个 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 ,据我所知,应用程序运行正常。所以,我的问题是:

非常感谢!

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 代码未使用 buttonProvidertimeLeftProvider

即使未使用 buttonProvidertimeLeftProvider,这也是输出,如以下 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 之前获取的结果。这可以在没有多个提供者的情况下实现吗?是的 - 在大多数情况下,这都是正确的。创建供应商是为了方便和高效。所以不要害怕使用许多提供程序,但不要为了创建它们而创建它们。

也就是说,您链接的文章内容丰富,值得赞赏。