Riverpod | Сколько провайдеров действительно необходимо для просмотра только одного состояния класса

#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 and 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 секунд, в итоге я получу тот же результат в 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. Я думаю, что это поможет понять.