FlutterでRiverpodを使っているウィジェットをWidgetbookでカタログ化する方法です。
flutter_riverpod 2.5.1
widgetbook 3.7.1
はじめに
本記事ではRiverpodを使っているウィジェットをWidgetbookでカタログ化する方法についてまとめます。
Widgetbook を使うとUIのカタログを作ることができますが、Riverpod を使うとUIが状態(やロジック)を参照することになります。
本記事では、Riverpod のプロバイダーをモッキングすることでWidgetbookを使ってUIをカタログ化する方法について紹介します。
なお、WidgetbookやRiverpodの基本的な使い方については以下の記事にまとめていますので、必要に応じて参照してください。
Riverpodを使ったウィジェットを作る
まず実行確認のため、Riverpodを使って簡単なアプリケーションを作ります。
まず以下の通り、int型の状態を保持してそれを増加させるメソッドを持つだけの簡単なプロバイダーを作ります。
// count.dart import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'count.g.dart'; @riverpod class Count extends _$Count { int _multiplier; // multiplierで値を設定すると、addする値にこの値が乗算される(モッキングの説明で使用します) set multiplier(int value) { _multiplier = value; } Count() : _multiplier = 1; @override int build(int initialValue) { return initialValue * _multiplier; } // 値を増加 void add(int value) { state = state + value * _multiplier; } }
コード生成機能を使っているので以下のコマンドで生成します。
$ dart run build_runner build
次にこれを使ったウィジェットを作ります。
// home.dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'count.dart'; class Home extends ConsumerWidget { const Home({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final count = ref.watch(countProvider(123)); return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('$count'), TextButton( onPressed: () => ref.read(countProvider(123).notifier).add(1), child: const Text("Increment"), ) ], ); } }
動作確認のためにこれを使ったアプリケーションも作っておきます。
// main.dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'home.dart'; void main() { runApp( const ProviderScope(child: MyApp()), ); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(home: Home()); } }
実行結果は下図のとおりです。
appBuilderを使ってProviderScopeで囲う
Riverpod を使うためには、対象のウィジェットを ProviderScope
で囲む必要があります。
Widgetbook のルートウィジェットのファクトリ(Widgetbook.material()
など)にはappBuilder
という引数があり、これを使うことで全てのウィジェットを任意のウィジェットの子孫にすることができます。
これを使って以下のようにProviderScope
で囲むことでRiverpodによるDIを可能にします。
// widgetbook.dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:widgetbook/widgetbook.dart'; import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; import 'widgetbook.directories.g.dart'; void main() { runApp(const WidgetbookApp()); } @widgetbook.App() class WidgetbookApp extends StatelessWidget { const WidgetbookApp({super.key}); @override Widget build(BuildContext context) { return Widgetbook.material( appBuilder: (context, child) { return ProviderScope( // RiverpodのProviderScopeで囲む child: MaterialApp( home: child, ), ); }, addons: [AlignmentAddon()], directories: directories, ); } }
これを実行すると、下図の結果が得られます。
Providerを使ったウィジェットがWidgetbookに表示できました。
ProviderScopeのoverrideにプロバイダーのモックを設定
さて、実際には Provider は通信を行ったりしますが、Widgetbook はUIのカタログなので、Widgetbook に表示するときにはこのような処理はするべきではありません。
このような場合には、Provider のモックを作り動作をオーバーライドする必要があります。
モックの作り方は Riverpod におけるウィジェットテストと同様で、以下の記事にまとめていますので必要に応じて参照してください。
この方針に基づいて前節のクラスを修正したものが以下です。
// widgetbook.dart import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:widgetbook/widgetbook.dart'; import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; import 'count.dart'; import 'widgetbook.directories.g.dart'; void main() { runApp(const WidgetbookApp()); } @widgetbook.App() class WidgetbookApp extends StatelessWidget { const WidgetbookApp({super.key}); @override Widget build(BuildContext context) { return Widgetbook.material( appBuilder: (context, child) { // ProviderのOverrideを作成 // 今回はテスト用に値が2倍になるProviderを作成 var override = countProvider(123).overrideWith(() => Count()..multiplier = 2); return ProviderScope( // overridesに設定 overrides: [ override, ], child: MaterialApp( home: child, ), ); }, addons: [AlignmentAddon()], directories: directories, ); } }
今回はテスト用に値を2倍にするようにプロバイダーをオーバーライドしました。
実際には、通信する代わりに通信をシミュレートしたJsonを返すリポジトリを持つプロバイダーにオーバーライドするなど、適宜必要な処理をします。
これを実行すると以下の結果が得られます。
正常にプロバイダーをオーバーライドできていることを確認できました。