Flutter Riverpod StateNotifierProvider: отслеживать изменения только для части модели

У меня такая модель:

class TimerModel {
  const TimerModel(this.timeLeft, this.buttonState);
  final String timeLeft;
  final ButtonState buttonState;
}

enum ButtonState {
  initial,
  started,
  paused,
  finished,
}

А вот StateNotifierProvider:

class TimerNotifier extends StateNotifier<TimerModel> {
  TimerNotifier() : super(_initialState);

  static const int _initialDuration = 10;
  static final _initialState = TimerModel(
    _durationString(_initialDuration),
    ButtonState.initial,
  );

  final Ticker _ticker = Ticker();
  StreamSubscription<int> _tickerSubscription;

  void start() {
    if (state.buttonState == ButtonState.paused) {
      _tickerSubscription?.resume();
      state = TimerModel(state.timeLeft, ButtonState.started);
    } else {
      _tickerSubscription?.cancel();
      _tickerSubscription =
          _ticker.tick(ticks: _initialDuration).listen((duration) {
        state = TimerModel(_durationString(duration), ButtonState.started);
      });
      _tickerSubscription.onDone(() { 
        state = TimerModel(state.timeLeft, ButtonState.finished);
      });
      state =
          TimerModel(_durationString(_initialDuration), ButtonState.started);
    }
  }

  static String _durationString(int duration) {
    final String minutesStr =
        ((duration / 60) % 60).floor().toString().padLeft(2, '0');
    final String secondsStr =
        (duration % 60).floor().toString().padLeft(2, '0');
    return '$minutesStr:$secondsStr';
  }

  void pause() {
    _tickerSubscription?.pause();
    state = TimerModel(state.timeLeft, ButtonState.paused);
  }

  void reset() {
    _tickerSubscription?.cancel();
    state = _initialState;
  }

  @override
  void dispose() {
    _tickerSubscription?.cancel();
    super.dispose();
  }
}

class Ticker {
  Stream<int> tick({int ticks}) {
    return Stream.periodic(Duration(seconds: 1), (x) => ticks - x - 1)
        .take(ticks);
  }
}

Я могу прослушивать все изменения в состоянии вот так:

final timerProvider = StateNotifierProvider<TimerNotifier>((ref) => TimerNotifier());

Однако я хочу создать другого провайдера, который будет отслеживать только изменения в ButtonState. Это не работает:

final buttonProvider = StateProvider<ButtonState>((ref) {
  return ref.watch(timerProvider.state).buttonState;
});

потому что он по-прежнему возвращает все изменения состояния.

Это тоже не работает:

final buttonProvider = StateProvider<ButtonState>((ref) {
  return ref.watch(timerProvider.state.buttonState);
});

Потому что объект state не имеет свойства buttonState.

Как мне наблюдать только за изменениями состояния кнопки?


person Suragch    schedule 14.10.2020    source источник
comment
Я не уверен, что это возможно. Я бы порекомендовал сделать StateNotifier ‹TimerModel› StateNotifier ‹ButtonState› и просто отслеживать оставшееся время в StateNotifier (вне состояния), поскольку в любом случае не похоже, что вы принимаете параметр времени.   -  person Alex Hartford    schedule 14.10.2020
comment
@AlexHartford, я нашел решение (см. Ниже), но я также думаю о том, чтобы сделать что-то вроде того, что вы предложили в качестве альтернативного решения.   -  person Suragch    schedule 15.10.2020


Ответы (1)


Использование watch дает новое состояние при изменении наблюдаемого состояния. Таким образом, можно решить проблему в двух частях, например так:

final _buttonState = Provider<ButtonState>((ref) {
  return ref.watch(timerProvider.state).buttonState;
});

Использование этого провайдера будет вызывать перестройку каждый раз, когда timerProvider.state изменяется. Однако хитрость заключается в следующем:

final buttonProvider = Provider<ButtonState>((ref) {
  return ref.watch(_buttonState);
});

Поскольку _buttonState будет одинаковым для большинства timerProvider.state изменений, просмотр _buttonState вызовет перестройку только тогда, когда _buttonState действительно изменится.

Благодарим этот пост за ответ. В этом сообщении также указывается, что скоро будет упрощенный синтаксис:

final buttonState = ref.watch(timerProvider.state.select((state) => state.buttonState));
person Suragch    schedule 15.10.2020
comment
Замечательно, да, в этом есть смысл. Рад, что ты разобрался! - person Alex Hartford; 15.10.2020