KAKEHASHI Tech Blog

カケハシのEngineer Teamによるブログです。

Flutter Widget Book で UIコンポーネントの管理

はじめに

Flutter開発において、UIコンポーネントの管理は常に大きな課題です。複数の画面サイズへの対応、ダークモードと通常モードの切り替え、多言語対応、コンポーネントの一貫性維持、そしてデザイナーとの効率的な協業など、様々な問題に直面します。これらの課題に対して、WebフロントエンドではStorybookが広く採用されていますが、Flutterエコシステムでは、Widgetbookが注目を集めています。

この記事は秋の技術特集 2024の 14 記事目です。

Widgetbookとは

Widgetbookは、FlutterアプリケーションのUIコンポーネントカタログを作成するためのオープンソースフレームワークです。Widgetbook社によって開発・維持されており、以下のような特徴があります:

  • Widgetのカタログ化
    • すべてのUIコンポーネントを一箇所で管理・閲覧可能。
    • コンポーネントの異なる状態やバリエーションを容易に確認
    • Widgetbook上でコンポーネントを検索できる。
  • 視覚的な差分検出(Widgetbook Cloud利用時)
  • FigmaとFlutterウィジェットの連携(Widgetbook Cloud利用時)

demo page

導入方法

Widgetbook の導入方法を下記を参考に紹介します。

プロジェクト構成

Widgetbook を導入するため、まずはウィジェットカタログ用に新しいFlutterアプリを作成します。

flutter create widgetbook --empty

構成としては下記のようになります。

your_app/
├── pubsepc.yaml
├── lib/
├── ...
└── widgetbook/
    ├── pubsepc.yaml
    ├── lib/
    └── ...

この記事では公式ドキュメントの流れに沿ってサブプロジェクトとしてWidgetbookを作るようにしています(separate app)が、メインのプロジェクトに含める(single app)ことも可能です。 その場合には、メインのプロジェクトのコードにwidgetbookのためのコードを書いていく必要があったり、Widgetbookの生成時間にも影響があるので、どういう構成でいくかはチームによって判断が必要です。

// single app
flutter_app
└─── lib
| └─── feature.dart
│ └─── main.dart
│ └─── main.widgetbook.dart
└─── pubspec.yaml

// separate app
flutter_app
└─── feature_1
└─── app
|    └───lib
|    |    └─── main.dart
|    └─── pubspec.yaml
└─── widgetbook_app
|    └─── lib
|    |    └─── main.widgetbook.dart
|    └─── pubspec.yaml

パッケージの追加

次に作成したWidgetbookアプリのpubspec.yamlに以下の依存関係を追加します。 この際、Widgetbook pub パッケージとの名前の競合を避けるために、widgetbook/pubspec.yaml ファイル内のプロジェクト名を widgetbook_workspace に変更します。

name: widgetbook_workspace
# ...

dependencies:
  widgetbook_annotation: ^3.2.0
  widgetbook: ^3.9.0
  your_app:
    path: ../

dev_dependencies:
  build_runner:
  widgetbook_generator: ^3.9.0

追加したパッケージは以下の役割を果たします:

widgetbook:コアライブラリ。Widget Bookの基本機能を提供します。 widgetbook_annotation:アノテーションを使用してWidget Book構造を自動生成するためのツール。

lib/main.dartの修正

次に、widgetbook/lib/main.dartWidgetbook クラスをrootに追加します。

import 'package:flutter/material.dart';
import 'package:widgetbook/widgetbook.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

// This file does not exist yet,
// it will be generated in the next step
import 'main.directories.g.dart';

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

@App()
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook.material(
      // The [directories] variable does not exist yet,
      // it will be generated in the next step
      directories: directories,
    );
  }
}

Widgetの作成

実際にWidgetbookの生成するために widgetbook/lib/cool_button.dart を追加します。 Widgetbookの生成対象にするには、annotationを利用しUseCaseとして指定します。

import 'package:flutter/material.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';

// Import the widget from your app
import 'package:your_app/cool_button.dart';

@UseCase(name: 'Default', type: CoolButton)
Widget buildCoolButtonUseCase(BuildContext context) {
  return CoolButton();
}

コード生成

以上で準備が整ったので、次のコマンドを実行することでファイルが生成されます。

dart run build_runner build -d

Widgetbookの実行

あとは下記のようなコマンドでアプリを実行すれば、Widgetbookを確認できます。

flutter run -d chrome

Widgetbookの機能

Addonsで実行環境の設定を変更

Addonsを使用することで、開発環境の設定を動的に変更できます。主なAddonsには以下があります。

  • DeviceFrameAddon: 異なるデバイスでのUIをプレビュー
  • MaterialThemeAddon: マテリアルデザインテーマの切り替え
  • TextScaleAddon: テキストスケールの調整

他にも準備されているAddonsは下記から確認できます。

カスタムAddonの作成も可能です。例えば、AlignmenのAddonを作成する場合は下記のように実装できます。

Addonsの設定を Widgetbook に表示させるには、以下のようにlib/main.dartで addons: [ ... ] の部分に利用したいAddonsを設定します。

@App()
class WidgetbookApp extends StatelessWidget {
  const WidgetbookApp({super.key});

  @override
  Widget build(BuildContext context) {
    return Widgetbook.material(
      directories: directories,
      addons: [
        // ここに追加する
        MaterialThemeAddon(
          themes: [
            WidgetbookTheme(
              name: 'Light',
              data: yourCustomLightTheme
            ),
            WidgetbookTheme(
              name: 'Dark',
              data: yourCustomTheme
            ),
          ],
        ),
        TextScaleAddon(
          scales: [1.0, 2.0],
        ),
        LocalizationAddon(
          locales: [
            const Locale('en', 'US'),
          ],
          localizationsDelegates: [
            DefaultWidgetsLocalizations.delegate,
            DefaultMaterialLocalizations.delegate,
          ],
        ),
        DeviceFrameAddon(
          devices: [
            Devices.ios.iPhoneSE,
            Devices.ios.iPhone13,
          ],
        ),
        GridAddon(),
      ],
      ...
    );
  }
}

上記の設定変更をすると下記のように右のサイドメニューにAddonsの機能が表示されます。

Knobsでユースケースの設定を変更

Knobsを利用することで Usecase に渡されるパラメータを Widgetbook 上から動的に変更できます。

Knobsの設定は、knobsのbuilderを利用することで設定できます。

@UseCase(name: 'Custom', type: CoolButton)
Widget buildCustomCoolButtonUseCase(BuildContext context) {
  return CoolButton(
    content: context.knobs.string(
      label: 'Content',
      description: 'ボタンのテキスト',
      initialValue: 'カスタムボタン',
    ),
    color: context.knobs.color(
      label: 'Button Color',
      initialValue: Colors.blue,
    ),
    fontSize: context.knobs.slider(
      label: 'Font Size',
      min: 12,
      max: 32,
      initialValue: 16,
    ),
  );
}

Knobs設定の際には以下のような値を利用できます。

Knobsについてもに独自の設定を作成することが可能です。

Widgetbook Cloudの機能

Widgetbook Cloudを利用すると、チーム全体でWidgetbookを共有し、以下の機能を活用できます。

Widgetbook Cloud のセットアップは、下記から登録を進めれば行うことができます。

Widgetbook Cloud のセットアップが完了すると以下のような機能を利用することもできます。

UI Regression

異なるブランチまたはコミット間でビルドを比較することにより、Widgetbook ビルドの UI Regressionを自動的に検出してくれる機能です。

注意が必要な点として、差分確認機能を利用するには比較元であるbase branchのWidgetbook ビルドもアップロードしておく必要があります。

Figma と Flutter実装の比較

開発者はWidgetを Figma の対応するデザインコンポーネントに簡単にリンクできます。 これにより、デザインの一貫性が確保され、開発者とデザイナー間のコラボレーションが向上します。

注意が必要な点としては、事前にFigmaのurlをUsecaseに紐づけておく必要があります。

Usecaseへの紐付けは以下のようにアノテーションを利用して行うことができます。

@UseCase(
  name: 'Default',
  type: CoolButton,
  designLink:
      'https://www.figma.com/design/HsANkdhbsCNTkXBzNJRNLD/Groceries-Demo?node-id=7235-4663&t=N6qwmLP7MP59ClWB-4',
)
Widget buildCoolButtonUseCase(BuildContext context) {
  // ...
}

課題

ツールを触っていく中で以下のような課題も感じました。

  • Usecaseを作る際のモックの設定が面倒。
  • UseCaseは差分を確認できるだけでVRTとしての利用はできない。
  • 大きなプロジェクトだとWidgetbookの生成時間が長くなる。

開発中のVer4でいくつか解決予定の課題もあります。 気になる方は下記にVer4のrepositoryがあるので確認してみてください。

最後に

Widgetbookは、Flutter開発におけるUIコンポーネント管理の課題を解決します。以下の条件に当てはまる場合、特に導入を検討する価値があります。

  • 中〜大規模のFlutterプロジェクト
  • 複数の開発者やデザイナーが関わるプロジェクト
  • 頻繁なUIの更新や多言語対応が必要なプロジェクト
  • デザインシステムの構築と維持が重要なプロジェクト

この記事がチームでツール利用を検討するきっかけになれば幸いです。