Flutterの画面やWidgetをカタログ化できるWidgetbookを使ってみた

# Widgetbookとは？
[Widgetbook公式](https://www.widgetbook.io/)

**Widgetbook**とは名前からも想像できるとおり、FlutterのWidgetをカタログのように確認することができるライブラリです。

以下のようにブラウザでSimulatorと同じ画面を確認したり、設定によっては各デバイスごとのレイアウトやテーマ別の見た目を簡単に確認することができます。
ホットリロードにも対応しているようですね。
![スクリーン ショット 2022-06-25 に 20.37.19 午後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1263604/fb67d456-d8cd-0b10-d861-f374d5bea0ac.png)

簡単なサンプルも作ってみたので気になる方はクローンして実際に動かして見て下さい。
https://github.com/Ryota-Nakamura-317/flutter_widgetbook_sample

# 手順
簡単に流れを説明します。

### ライブラリの追加
https://pub.dev/packages/widgetbook
```yaml
dependencies:
  widgetbook: ^2.4.1
```

### フォルダの追加
ルートに`widgetbook`というフォルダを作成
![スクリーン ショット 2022-06-25 に 19.27.43 午後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1263604/a61915ac-6cc1-cd87-1c23-e68b970e8b6b.png)

### main.dartの作成
先程の`widgetbook`配下に`main.dart`を追加します。
`devices`には結構いろんな種類のデバイスを予め選択することができます。

```dart:widgetbook/main.dart
import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_sample/theme.dart';

import 'components.dart';

void main() {
  runApp(
    const WidgetBookHotReload(),
  );
}

class WidgetBookHotReload extends StatelessWidget {
  const WidgetBookHotReload({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Widgetbook.material( // appInfo, themes, cateforiesがrequired
      appInfo: AppInfo(name: 'WidgetBook'),
      devices: const [ // ここでいろんなデバイスの指定が可能
        Apple.iPhone13Mini,
        Samsung.s21ultra,
        Desktop.desktop1080p,
      ],
      themes: [],
      categories: [],
    );
  }
}

```
これでセットアップ完了。

### 画面作成
`widgetbook`に反映させるために適当な画面を作成します。
```dart:lib/main.dart
import 'package:flutter/material.dart';
import 'package:widgetbook_sample/theme.dart';
import 'package:widgetbook_sample/widgets.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'WidgetBook',
      theme: lightTheme,
      darkTheme: darkTheme,
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('WidgetBook'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            CircleWidget(), // component
            SizedBox(height: 12),
            ButtonWidget(), // component
          ],
        ),
      ),
    );
  }
}

```
Widgetbookでテーマ切り替えの状態が確認できるように以下のようにテーマも定義しました。
```dart:theme.dart
import 'package:flutter/material.dart';

ThemeData get lightTheme {
  final base = ThemeData(
    colorScheme: const ColorScheme.light(),
    useMaterial3: true,
  );
  return base;
}

ThemeData get darkTheme {
  final base = ThemeData(
    colorScheme: const ColorScheme.dark(),
    useMaterial3: true,
  );
  return base;
}

```

componentは以下の簡単なWidgetを用意。
```dart:widgets.dart
import 'package:flutter/material.dart';

class CircleWidget extends StatelessWidget {
  const CircleWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 50,
      height: 50,
      decoration: const BoxDecoration(
        shape: BoxShape.circle,
        color: Colors.amber,
      ),
      child: const Center(child: Text('circle')),
    );
  }
}

class ButtonWidget extends StatelessWidget {
  const ButtonWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {},
      child: const Text('button'),
    );
  }
}

```

出来上がった画面がこちら。
![スクリーンショット 2022-06-25 20.56.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1263604/fe1564e7-e324-e5a8-de26-4424476677e0.png)


### Widgetbookに追加
では、これらをWidgetbookに反映させていきます。
先程の`widgetbook/main.dart`にthemeとcategoryの追加をしました。

categoryはWidgetbookで左側のメニューにカテゴリとして表示されます。

![スクリーン ショット 2022-06-25 に 21.01.04 午後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1263604/a45f45a1-c169-c7e9-39ca-f50cbc42198d.png)
※↑赤いアイコンのところがカテゴリ

```dart:widgetbook/main.dart
class WidgetBookHotReload extends StatelessWidget {
  const WidgetBookHotReload({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Widgetbook.material(
      appInfo: AppInfo(name: 'WidgetBook'),
      devices: const [
        Apple.iPhone13Mini,
        Samsung.s21ultra,
        Desktop.desktop1080p,
      ],
      themes: [ // theme追加
        WidgetbookTheme(
          name: 'light',
          data: lightTheme,
        ),
        WidgetbookTheme(
          name: 'dark',
          data: darkTheme,
        ),
      ],
      categories: [ // category追加
        WidgetbookCategory(
          name: 'Screen',
          widgets: [
            screenComponent,
          ],
        ),
        WidgetbookCategory(
          name: 'Widgets',
          widgets: [
            widgetsComponent,
          ],
        ),
      ],
    );
  }
}
```

このあたりの運用はプロジェクトごとでやりやすいように決めるといいかもしれません。
`categories`には`WidgetbookCategory`を配列で定義し、`WidgetbookCategory`内のwidgetsにコンポーネントを渡します。以下コンポーネントです。

```dart:components.dart
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_sample/main.dart';
import 'package:widgetbook_sample/widgets.dart';

final screenComponent = WidgetbookComponent(
  name: 'screen',
  useCases: [
    WidgetbookUseCase(
      name: 'MyHomePage',
      builder: (_) => const MyHomePage(),
    ),
  ],
);

final widgetsComponent = WidgetbookComponent(
  name: 'widgets',
  useCases: [
    WidgetbookUseCase.center(
      name: 'Circle',
      child: const CircleWidget(),
    ),
    WidgetbookUseCase.center(
      name: 'Button',
      child: const ButtonWidget(),
    ),
  ],
);

```
`WidgetBookComponent`を定義し`useCases`に`WidgetbookUseCase`を配列で定義していきます。
`WidgetbookUseCase`のbuilderに今回作成した画面を、`WidgetbookUseCase.center`はwidgetbookで勝手に中心配置してくれるWidgetなのでこちらの場合はchildに切り出したWidgetを定義していきます。


さて、ここまでできたら`widgetbook/main.dart`をビルドします。


### `widgetbook/main.dart`をビルド
ビルドすると以下のような画面が立ち上がります。
左側には、`WidgetbookCategory/WidgetbookComponent/WidgetbookUseCase`の順でディレクトリが表示されおり、
右側には、テーマの変更やデバイスの変更などがボタンひとつで操作できるパネルがあります。
![スクリーンショット 2022-06-25 21.09.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1263604/31a3bb56-648c-ae10-258a-b5a55dcf7f35.png)

ボタン
![スクリーンショット 2022-06-25 21.26.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1263604/2532688f-0165-1151-8b60-5c056455820f.png)
円
![スクリーンショット 2022-06-25 21.25.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1263604/fbdbf4c7-c749-ec68-5ea7-47992f40aa5b.png)
画面
![スクリーンショット 2022-06-25 21.25.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1263604/5df28541-e9cd-0993-7a55-6bf85ea0da4d.png)
こんな感じで左側ディレクトリをポチポチするだけで見た目がどんなものなのかを確認できます。
ダークモードもワンクリックで確認可能。
![スクリーンショット 2022-06-25 21.27.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1263604/f0a5b8b1-f092-d131-72ee-aa21192f3ea1.png)

# さいごに
以上、Widgetbookの紹介でした。

導入により一手間増えますが、デザインの確認はとてもしやすくなるなと感じました。

## 参考
https://www.widgetbook.io/open-core