【Flutter】Storybookライクに使えるMonarchを試してみる
Build high-quality UIs with ease | Monarch
MonarchはFlutterでStorybookみたく、Widgetの確認やデバッグ、テストを行う為のツールで、
色々充実してそうなので、試してみたいと思います。
試した環境
$ sw_vers
ProductName: macOS
ProductVersion: 12.4
BuildVersion: 21F79
テスト用のプロジェクト作成
$ mkdir monarch_sample
$ cd monarch_sample
$ fvm use 3.0.5 --force
$ fvm flutter create --org com.sample.monarch .
インストール
こちらを参考にインストールしていきます。
※ macosの場合は、事前にXcodeが必要です。
$ curl -O https://d2dpq905ksf9xw.cloudfront.net/macos/monarch_macos_1.7.8.zip
$ unzip monarch_macos_1.7.8.zip
monarch/bin
にパスを通して、monarch
コマンドが使えるようにしておく。
初期化して起動
先程の monarch_sample
プロジェクト直下に移動し、以下コマンドを実施します。
$ monarch init
↑を実施すると pubspec.yaml
には以下が追加されていました。
dev_dependencies:
monarch: ^2.3.0
build_runner: ^2.1.0
また、以下の build.yaml
とサンプルの stories
ディレクトリが作成されていました。
targets:
$default:
sources:
- $package$
- lib/**
- stories/**
サンプルとして作成された、stories/sample_button.dart
と stories/sample_button_stories.dart
は以下の内容で作成されていました。
ソースコードはこちら
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
enum ButtonStyles { primary, secondary, disabled }
class Button extends StatelessWidget {
final String text;
final ButtonStyles style;
Button(this.text, this.style);
Widget build(BuildContext context) {
return Center(
child: TextButton(
onPressed: () => null,
style: TextButton.styleFrom(
primary: getPrimaryColor(),
backgroundColor: getBackgroundColor(),
side: style == ButtonStyles.secondary
? BorderSide(width: 0, color: Colors.black87)
: null),
child: Text(this.text)));
}
Color getPrimaryColor() {
switch (style) {
case ButtonStyles.primary:
return Colors.white;
case ButtonStyles.secondary:
return Colors.black87;
case ButtonStyles.disabled:
return Colors.white;
default:
return Colors.white;
}
}
Color getBackgroundColor() {
switch (style) {
case ButtonStyles.primary:
return Colors.green;
case ButtonStyles.secondary:
return Colors.white;
case ButtonStyles.disabled:
return Color(0xFFE0E0E0);
default:
return Colors.green;
}
}
}
import 'package:flutter/material.dart';
import 'sample_button.dart';
Widget primary() => Button(
'Button', ButtonStyles.primary);
Widget secondary() => Button(
'Button', ButtonStyles.secondary);
Widget disabled() => Button(
'Button', ButtonStyles.disabled);
早速、起動してみます。
$ monarch run
Using flutter sdk at /Users/xxxx/fvm/versions/3.0.5/bin/flutter
Enabling Flutter for desktop
Downloading the Monarch UI for this project's flutter version...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 31.4M 100 31.4M 0 0 6578k 0 0:00:04 0:00:04 --:--:-- 6750k
Extracting Monarch UI zip... Done.
Starting Monarch.
Preparing stories...
Preparing stories completed, took 47.6sec.
Launching Monarch app...
Launching Monarch app completed, took 2.6sec.
Attaching to stories...
Attaching to stories completed, took 16.5sec.
Setting up stories watch...
3.1sec elapsed
Setting up stories watch completed, took 4.5sec.
Monarch key commands:
r Hot reload (default).
R Hot restart.
h Show this list of commands.
⌃C Quit.
Monarch is ready. Project changes will reload automatically with hot reload.
Press "R" to force a hot restart.
fvm
を使用していたら fvm
のflutter sdkを使用するようです。また、flutter sdk の desktop を有効化しています。
実行すると専用のデスクトップアプリが起動し、Storiesを選択したり挙動やデバイスの切り替え等直感的に操作できる感じです ✨
ちなみに↑のデスクトップアプリですが、
monarch/bin/cache/monarch_ui/flutter_macos_3.0.5-stable
配下にインストールされているようでした。
試しに何かStoriesを書いてみる
よくありそうな、Iconの上に何かの件数を表すbadgeを表示する事ができる、BadgeIcon
Widgetを作成しそのStoriesを書いてみたいと思います。
↓まずは BadgeIcon
の実装になります。(細かいリファクタ箇所はありそうですが、今回仮で、、)
import 'package:flutter/material.dart';
class BadgeIcon extends StatelessWidget {
const BadgeIcon({Key? key, required this.icon, this.badgeCount = 0})
: super(key: key);
final Widget icon;
final int badgeCount;
Widget build(BuildContext context) {
return Stack(
children: <Widget>[icon, _badge()],
);
}
Widget _badge() {
return Positioned(
top: 0,
right: 0,
child: Container(
padding: const EdgeInsets.all(1),
constraints: const BoxConstraints(minHeight: 13, minWidth: 13),
decoration: BoxDecoration(
color: Colors.red, borderRadius: BorderRadius.circular(7)),
child: Text(
badgeCount.toString(),
style: const TextStyle(fontSize: 8, color: Colors.white),
textAlign: TextAlign.center,
),
));
}
}
まずは、0
と100
をバッジ表示する BadgeIcon
の Storiesを書いてみます。
import 'package:flutter/material.dart';
import 'badge_icon.dart';
Widget countZero() => const BadgeIcon(icon: Icon(Icons.mail));
Widget countOneHundred() => const BadgeIcon(
icon: Icon(Icons.mail),
badgeCount: 100,
);
早速起動してみると、Storiesが増えていて BadgeIcon
関連の見た目を確認できます。
Riverpodと一緒に使う場合
いいサンプルかは分かりませんが、、先程の BadgeIcon
とRiverpodを使うWidgetを作成してみたいと思います。
まずは、flutter_riverpod
をインストールします。
dependencies:
flutter_riverpod: ^1.0.4
新規に BadgeIconWithProvider
というWidgetを以下内容で作成します。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'badge_icon.dart';
final countProvider = StateProvider.autoDispose<int>((ref) => 0);
class BadgeIconWithProvider extends ConsumerWidget {
const BadgeIconWithProvider({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(countProvider);
return Container(
padding: const EdgeInsets.all(8),
child: BadgeIcon(
icon: const Icon(Icons.mail),
badgeCount: count,
),
);
}
}
やっている事は countProvider
から渡ってくる値を BadgeIcon
のカウントとして渡しているだけのWidgetになります。
次にStoriesを書いてみます。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'badge_icon_with_provider.dart';
class CounterBody extends ConsumerWidget {
const CounterBody({Key? key}) : super(key: key);
Widget build(BuildContext context, WidgetRef ref) {
final notifier = ref.watch(countProvider.notifier);
return Column(
children: <Widget>[
const BadgeIconWithProvider(),
const SizedBox(height: 5),
ElevatedButton(
child: const Text('カウントアップ'),
onPressed: () => notifier.update((state) => state + 1),
),
const SizedBox(height: 5),
ElevatedButton(
child: const Text('カウントダウン'),
onPressed: () => notifier.update((state) => state - 1),
),
],
);
}
}
Widget counter() => const ProviderScope(child: CounterBody());
本家のStorybookだとパラメータを入力してコンポーネントを確認できたりすると思うのですが、
Monarchの場合そういった事ができなさそうなので、カウントアップ、カウントダウンするボタンを設けました。
ボタン押下で StateProvider
の値を変化させています。
まとめ
StorybookみたくWidget単体でフォーカスして作っていけそうなので、今後も使っていこうと思います!
記事内には書いてないですが、カスタムテーマの切り替えや複数Locale切り替えも対応しているので、ここら辺もまた使って行きたいと思います。
Discussion