Flutter用のUIカタログ「Widgetbook」
そろそろちゃんと使いたいなと思い、入門してみたときの備忘録(*´ω`*)
WebフロントエンドのStorybookと同じ感じ
Widgetbookとは
登録したWidgetを一覧にして表示を確認できるツール
公式のデモが用意されているので、これを見るとわかりやすい
いろいろとメリットがあるので便利
- カタログとしてUIコンポーネントを一覧化できたり
- エラーやローディングなどの確認/テストができたり
- Webで出力して開発者以外に共有できたり
コンポーネントのカタログとしてもよいけど、
Widgetの一覧なので、画面とかでもOK
UIプロトとかはWidgetbook上で作ってみてからでもよいかもしれない
簡単な使い方
公式だとこの辺り
Widgetbook用のプロジェクトを追加していく形
最終的なフォルダ構成はこんな感じ
your_app/ # 対象のアプリ ├── pubspec.yaml ├── lib/ └── widgetbook/ # UIカタログ ├── pubspec.yaml └── lib/
プロジェクトの追加/セットアップ
## プロジェクトの追加 $ flutter create widgetbook --empty --platforms=web,macos $ cd widgetbook ## パッケージの追加 $ flutter pub add widgetbook widgetbook_annotation $ flutter pub add dev:widgetbook_generator dev:build_runner
widgetbook/pubspec.yaml
を以下のように変更する
- name: widgetbook + name: widgetbook_workspace + dependencies: + your_app: + path: ../
カタログ/UseCaseの追加
表示したいものを@UseCase
をつけて追加していく感じ
import 'package:flutter/material.dart'; import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; // your_appのUIをインポート import 'package:your_app/cool_button.dart'; // カタログに表示するWidgetを返す関数をUsecaseとして追加 @widgetbook.UseCase(name: 'Default', type: CoolButton) Widget buildCoolButtonUseCase(BuildContext context) { return CoolButton(); }
作成できたら、build_runnerを実行してコードを生成する
dart run build_runner build -d
Widgetbookのmainを追加
あとはWedgetbook用のAppを起動するmain.dart
を用意すればOK
# main.dart import 'package:flutter/material.dart'; import 'package:widgetbook/widgetbook.dart'; import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; import 'main.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( // UseCaseから生成されたコード。カタログ情報の一覧 directories: directories, ); } }
起動すると、公式デモのような画面が表示される
flutter run
ディレクトリ構成例
ディレクトリ構成もいろんなパターンがある
- アプリのプロジェクト参照する、別プロジェクトとして追加
- monorepo構成にして、別プロジェクトとして追加
- アプリのプロジェクトにmainを分けて組み込む
別プロジェクトで参照
Quick Startなど公式で紹介されている構成
your_app/ # 対象のアプリ ├── pubspec.yaml ├── lib/ └── widgetbook/ # UIカタログ ├── pubspec.yaml └── lib/
- 公式Doc: Demo: Widgetbook
- 参考Repo: widgetbook/groceries-demo@GitHub
monorepo構成
Melosを利用したmonorepo構成の例
こちらも公式で紹介されている
./ ├── your_app/ # 対象のアプリ │ ├── pubspec.yaml │ └── lib/ ├── widgetbook/ # UIカタログ │ ├── pubspec.yaml │ └── lib/ └── pubspec.yaml # monorepo root
- 公式Doc: Monorepo | Widgetbook
- 参考Repo: altive/flutter_app_template: Flutter App Templete is a project that introduces an approach to architecture and project structure for developing Flutter apps.
同一プロジェクトの別main
アプリのコードと一緒にしてしまうパターン
your_app/ ├── pubspec.yaml └── lib/ ├── main.dart # 対象のアプリ └── main_widget.dart # UIカタログ
Widgetbookの構成要素
登場人物/構成要素は、そんなに多くなく、
これくらいを覚えておけば、よさそう
- UseCase:
- Knob:
- 直訳すると、ノブ/取っ手
- UseCaseのうち変更できるパラメタの指定
- ex.
text
の文字やisLoading
のON/OFFを変えれるようにしたり
- App:
Widgetbook
を返す専用のApp- 全体の設定など
- Addon:
- 自由に追加できる拡張機能。公式で用意されている
UseCase
ユースケースは、対象のWidgetを返す関数を用意すればOK
細かい設定は、@UseCase
で設定する
import 'package:flutter/material.dart'; import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; @widgetbook.UseCase( type: ElevatedButton, name: 'Default', path: "components/buttons", ) Widget elevatedDefault(BuildContext context) { return ElevatedButton(child: Text("Button"), onPressed: () {}); } @widgetbook.UseCase( type: ElevatedButton, name: 'Secondary', path: "components/buttons", ) Widget elevatedSecondary(BuildContext context) { return ElevatedButton(child: Text("Button"), onPressed: () {}); } @widgetbook.UseCase( type: FilledButton, name: 'Default', path: "components/buttons", ) Widget filledDefault(BuildContext context) { return FilledButton(child: Text("Button"), onPressed: () {}); }
Knob
長い文字を入れたときのデザインなどを確認したいこともある
Widgetbook上で、自由に値を変更にできる機能がKnob
@widgetbook.UseCase( name: 'Default', type: ElevatedButton, path: "components/buttons", ) Widget elevatedDefault(BuildContext context) { return ElevatedButton( child: Text( context.knobs.string(label: "Button Label", initialValue: "Button"), ), onPressed: () {}, ); }
bool/int/stringなど基本的な型のknobは用意されているが、
独自の型などは、Konb自体を実装して、Custom Knobを作ることもできる
App
Appでは、WidgetbookのThemeやAddonなどの全体の設定ができる
@widgetbook.App() class WidgetbookApp extends StatelessWidget { const WidgetbookApp({super.key}); @override Widget build(BuildContext context) { return Widgetbook.material( directories: directories, // theme lightTheme: ThemeData.light(), // Custom light theme darkTheme: ThemeData.dark(), // Custom dark theme themeMode: ThemeMode.light, // Forcing light mode // Home画面のWidget home: SizedBox.shrink(), // custom builder appBuilder: (context, child) { return ProviderScope(child: MaterialApp(home: child)); }, // addon addons: [ ViewportAddon(Viewports.all), AlignmentAddon(), ], ); } }
Addon
Widgetbookでは、Addonという形で機能を追加することができる
公式でもいろいろ用意されて、自作のAddonも作れる
- Alignment Addon: Centerや右寄せなどの変更
- Device Frame Addon: IPhoneなどのフレーム内に表示(deprecated)
- Localization Addon: 言語の変更
- Text Scale Addon: textScaleの変更
- Theme Addon: テーマの切り替え
- Inspector Addon: inspectorの利用
- Zoom Addon: 拡大/縮小の利用
- Grid Addon: gridのガイドラインの表示
- Builder Addon: カスタムWidgetの差し込み
- Accessibility Addon: アクセスビリティの問題検知(deprecated)
- Semantics Addon: semantics treeの可視化(experimental)
- Viewport Addon: Viewportの変更(experimental)
- Custom Addon: 自作のAddon
また、並び順も重要で、先頭のAddonが一番外側(親Widet)になる
そのため、ViewportAddon
やDeviceFrameAddon
は一番先頭に来る必要があるなど、
それぞれのドキュメントに気をつけないといけないことが書かれている
Riverpodとの併用 / Mock Widget
多くの場合、Widget単体ではなく、
Riverpodなどの状態管理ライブラリと一緒に使っている場合が多い
その場合には、テスト時と同様にMock化して、UseCaseを作成するのがよい
以下のドキュメントや記事が参考になる
- Mock Widgets & Screen | Widgetbook
- 【Flutter】Riverpodを使っているウィジェットをWidgetbookでカタログ化する - LIGHT11
- WidgetbookとRiverpodを組み合わせて利用するときのTips
お手軽なのは、UseCaseごとにProviderScope
で囲んで、
overrideWith
で差し替える形
@widgetbook.UseCase( name: 'Default', type: FilledButton, path: "components/buttons", ) Widget filledDefault(BuildContext context) { return ProviderScope( overrides: [ textLabelProvider.overrideWith((ref) => 'Button'), ], child: FilledButton(child: Text("Button"), onPressed: () {}), ); }
全体的にProviderだけであれば、WidgetbookAppの方に設定する形でもOK
@widgetbook.App() class WidgetbookApp extends StatelessWidget { const WidgetbookApp({super.key}); @override Widget build(BuildContext context) { return Widgetbook.material( directories: directories, appBuilder: (context, child) { return ProviderScope( overrides: [ // ], child: MaterialApp(home: child), ); }, ); } }
以上!! 思ったよりも簡単に導入できそう(*´ω`*)
いろんなことをやるとはハマりそうだけど、まずはStatelessなWidgetだけでも!
参考にしたサイト様
- Widgetbook
- KoheiKanagu/blooms: AI エージェントを活用した妊婦の体調の記録と分析を行うアプリケーション
- altive/flutter_app_template: Flutter App Templete is a project that introduces an approach to architecture and project structure for developing Flutter apps.
- widgetbook/groceries-demo: A demo application for Widgetbook and how to implement a design system from Figma.
- WidgetbookとRiverpodを組み合わせて利用するときのTips
- 【Flutter】Riverpodを使っているウィジェットをWidgetbookでカタログ化する - LIGHT11
- WidgetbookとRiverpodを組み合わせて利用するときのTips