![[Flutter] WidgetBook 도입](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdna%2FBTLM5%2FbtsQMpJj0aj%2FAAAAAAAAAAAAAAAAAAAAAIOgYFAO1PxmXU27BVKNVy7f2921wrXsocHBqEttMkcQ%2Fimg.png%3Fcredential%3DyqXZFxpELC7KVnFOS48ylbz2pIh7yKj8%26expires%3D1759244399%26allow_ip%3D%26allow_referer%3D%26signature%3DihhWxcJzgxB3ExNoOaRNKJ%252BrG8Q%253D)
개요
Flutter로 개발하다 위젯 관리에 대한 이야기가 나왔다. "위젯들을 어떻게 체계적으로 관리할 수 있을까? 프론트 내부 QA를 빠르게 하는 방법이 없을까?" 하는 의문들도 같이 나왔다.
프로젝트가 커지면서 버튼 하나만 해도 여러 variants가 생기고, 각각 다른 상태들을 확인해야 하는 상황이 생긴다. 그런데 매번 앱을 실행해서 특정 화면으로 가서 버튼을 확인하는 건 꽤나 비효율적이다. 더 나아가 디자이너나 기획자가 "이 버튼의 disabled 상태는 어떻게 보이는 거죠?"라고 물어보면 설명하기가 난감하다. 직접 켜서 보여줘야하는 부분이 상당히 번거롭다.
이런 문제를 해결하기 위해 Storybook의 Flutter 버전이라고 할 수 있는 WidgetBook을 프로젝트에 도입해봤다. 결론부터 말하자면, 꽤나 만족스러웠지만 몇 가지 주의할 점들이 있었다.
Widgetbook | Build, organise & test your Flutter widgets
Widgetbook is a Flutter package to build and test widgets in isolation. Over 2000 teams around the world use it in their projects. It’s open source and free.
www.widgetbook.io
WidgetBook이란?
WidgetBook은 Flutter 위젯들을 격리된 환경에서 개발하고 테스트할 수 있게 해주는 도구다. 쉽게 말해서 각각의 위젯을 독립적으로 확인하고, 다양한 상태와 속성들을 실시간으로 조작하면서 볼 수 있게 해준다. 웹 개발에서 Storybook을 써본 개발자라면 바로 이해할 수 있을 것이다.
가장 큰 장점은 격리다. 복잡한 앱 전체를 실행하지 않고도 개별 위젯만 확인할 수 있어서, 개발 속도가 확실히 빨라진다. 또한 디자이너나 기획자와의 소통에서도 엄청나게 유용하다. '이 컴포넌트 이렇게 구현했어요'라고 말로 설명하는 대신, WidgetBook 링크 하나만 보내주면 된다.
WidgetBook 적용하기
기본 설정
이 문서를 보고 따라하면 쉽게 가능하다. ▼
Widgetbook Docs
Build your Design System's widgets in isolation.
docs.widgetbook.io
1. WidgetBook 설치
먼저 현재 프로젝트 내부에서 아래의 명령어를 수행해준다. ▼
flutter create widgetbook --empty
WidgetBook 패키지와 이름 충돌이 생길 수 있기 때문에 pubspec.yaml에서 이름을 변경해준다. ▼
name: widgetbook_workspace
description: "A new Flutter project."
publish_to: "none"
version: 0.1.0
2. WidgetBook 프로젝트에 패키지 추가
이제 WidgetBook 프로젝트로 가서 아래의 명령어를 실행해 패키지를 추가해준다. ▼
flutter pub add widgetbook widgetbook_annotation dev:widgetbook_generator dev:build_runner
WidgetBook의 pubspec.yaml은 아래와 같이 보여야한다. ▼
name: widgetbook_workspace
# ...
dependencies:
widgetbook_annotation: ^3.7.0
widgetbook: ^3.17.0
your_app:
path: ../
dev_dependencies:
build_runner:
widgetbook_generator: ^3.17.0
WidgetBook 스토리 작성 방법
WidgetBook에서 위젯의 다양한 상태를 보여주기 위한 스토리를 작성하는 방법을 알아보자.
기본 스토리 작성
가장 기본적인 버튼 컴포넌트의 스토리 예시다. ▼
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:my_app/core/enum/component_state.dart';
import 'package:my_app/shared/component/button/app_button.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
@UseCase(name: 'AppButton', type: AppButton)
AppButton appButtonStory(BuildContext context) {
return AppButton(
margin: EdgeInsets.zero,
type: AppButtonType.MAIN,
state: ComponentState.DEFAULT,
buttonWidget: Text(
'버튼',
style: TextStyle(color: Colors.white, fontSize: 16.sp),
),
callback: () {},
);
}
여러 상태를 가진 스토리
하나의 컴포넌트가 여러 상태를 가질 때는 각 상태별로 별도의 `@UseCase`를 만든다. ▼
import 'package:flutter/material.dart';
import 'package:my_app/feature/product/presentation/route/product_list_route/widget/product_list_item.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart';
@UseCase(name: 'Default', type: ProductListItem)
ProductListItem productListItemDefault(BuildContext context) {
return ProductListItem(
title: '아이폰 15 Pro Max',
price: '1,990,000원',
status: '판매중',
productType: '스마트폰',
imageUrl: 'https://images.unsplash.com/photo-1678685888221-cda773a3dcdb?w=400',
option: '256GB, 티타늄 블루',
createdAt: DateTime.parse('2024-01-15'),
);
}
@UseCase(name: 'Edit Mode', type: ProductListItem)
ProductListItem productListItemEditMode(BuildContext context) {
return ProductListItem(
title: '갤럭시 S24 Ultra',
price: '1,690,000원',
status: '판매완료',
productType: '스마트폰',
imageUrl: 'https://images.unsplash.com/photo-1610945265064-0e34e5519bbf?w=400',
option: '512GB, 티타늄 그레이',
createdAt: DateTime.parse('2024-01-10'),
isEditMode: true,
onTap: () {},
);
}
@UseCase(name: 'Selected', type: ProductListItem)
ProductListItem productListItemSelected(BuildContext context) {
return ProductListItem(
title: '맥북 프로 14인치',
price: '3,290,000원',
status: '판매중',
productType: '노트북',
imageUrl: 'https://images.unsplash.com/photo-1541807084-5c52b6b3adef?w=400',
option: 'M3 Pro, 18GB RAM',
createdAt: DateTime.parse('2024-01-12'),
isEditMode: true,
isSelected: true,
onTap: () {},
);
}
WidgetBook 메인 앱 설정
ScreenUtil과 같은 글로벌 설정이 필요한 경우, 메인 앱에서 설정할 수 있다. ▼
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.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(
addons: [
ViewportAddon(Viewports.all),
AlignmentAddon(),
BuilderAddon(
name: 'ScreenUtil',
builder: (context, child) {
return ScreenUtilInit(
designSize: const Size(390, 844),
minTextAdapt: true,
splitScreenMode: true,
useInheritedMediaQuery: true,
builder: (context, child) => child!,
child: child,
);
},
),
],
directories: directories,
);
}
}
작성 팁
- @UseCase의 name은 각 상태를 명확히 구분할 수 있는 이름으로 작성
- 실제 사용될 수 있는 현실적인 데이터를 사용
- 콜백 함수는 빈 함수로 처리하여 오류 방지
- 이미지는 Unsplash 같은 무료 이미지 서비스 활용
이렇게 작성하면 각 컴포넌트의 다양한 상태를 WidgetBook에서 쉽게 확인하고 테스트할 수 있다.
4. 실행
빌드 실행 `annotation`을 사용했기 때문에 코드 생성이 필요하다. ▼
dart run build_runner build
그리고 WidgetBook을 실행한다. ▼
flutter run -t widgetbook/widgetbook.dart -d chrome
그러면 이렇게 위젯들을 미리 볼 수 있다. ▼
추가설정
물론 위와 같이 설정한다고 다 되는 것은 아니다. `assets`를 WidgetBook에서 사용하려면 별도의 설정이 필요하다. 이는 공식 문서에도 나와있어서 공식 문서에서 시키는 대로 따라하면 잘 된다. 다만, 우리의 경우 공식 문서에서 시키는 대로 했으나 경로에 문제가 있어 조금 다르게 설정했다. ▼
Widgetbook Docs
Build your Design System's widgets in isolation.
docs.widgetbook.io
1. Assets 패키지 생성
먼저 `assets` 전용 패키지를 만든다. ▼
# assets/pubspec.yaml
name: assets
description: >
Holds the assets for the app. The assets are included in a
separate package, to be able to share it with Widgetbook app.
version: 0.0.0
publish_to: none
environment:
sdk: ">=3.1.0 <4.0.0"
flutter:
assets:
- config/
- image/
2. 메인 앱에서 Assets 패키지 참조
이제 메인 앱에서 `assets`를 `dependencies`에 넣어준다. ▼
# pubspec.yaml (메인 앱)
name: my_app
description: "My Flutter App"
environment:
sdk: ^3.9.0
dependencies:
flutter:
sdk: flutter
assets:
path: ./assets
# 기타 의존성들...
flutter:
uses-material-design: true
# assets 선언은 assets 패키지에서 관리하므로 주석 처리
# assets:
# - assets/config/
# - assets/image/
3. WidgetBook에서 Assets 패키지 참조
WidgetBook에서도 마찬가지로 Assets 패키지를 참조시켜준다. ▼
# widgetbook/pubspec.yaml
name: widgetbook_workspace
description: "WidgetBook for My App"
environment:
sdk: ^3.9.0
dependencies:
my_app:
path: ../
assets:
path: ../assets
flutter:
sdk: flutter
widgetbook: ^3.17.0
# 기타 의존성들...
flutter:
uses-material-design: true
4. 코드에서 Assets 사용
이제 메인 앱과 WidgetBook 모두에서 동일한 방식으로 assets를 사용할 수 있다. 다만, 우리의 경우 package 키워드가 잘 적용되지 않아, 에셋 이미지 경로를 아래와 같이 수정했다. ▼
abstract class AppAssetPath {
static const String image = 'packages/assets/image';
static const String arrow = '$image/arrow.png';
//static const String logo = '$image/logo.png';
}
앞에 `packages/assets` 라는 prefix를 붙여 사용했다. config파일도 마찬가지로 `packages/assets` 경로를 붙여 사용했다.
이렇게 assets를 패키지화하면 메인 앱과 WidgetBook 간의 assets 공유가 간편해지고, 관리도 훨씬 쉬워진다. 특히 대규모 프로젝트에서 assets가 많을 때 이 방식이 매우 유용하다.
실제로 사용해본 소감
확실히 편하다
처음에는 "이런 것까지 필요할까?" 싶었는데, 막상 써보니 확실히 편했다. 특히 버튼의 여러 상태들(normal, pressed, disabled, loading 등)을 한 번에 확인할 수 있어서 QA 시간이 확실히 줄었다. 이전에는 앱을 여러 번 실행해서 각각의 상태를 재현해야 했는데, 이제는 WidgetBook에서 몇 번의 클릭만으로 모든 상태를 확인할 수 있다.
협업에서의 장점
디자이너나 기획자와의 소통에서 엄청나게 유용했다. "이 컴포넌트가 이렇게 보여야 하는 거 맞나요?"라는 질문에 말로 설명하는 대신, WidgetBook 링크를 보내주면 바로 확인할 수 있었다. 특히 다크 테마나 다양한 디바이스에서의 모습을 실시간으로 확인할 수 있어서, 반응형 디자인 검토가 훨씬 수월해졌다.
개발 속도 향상
위젯을 개발할 때 전체 앱을 실행할 필요 없이 해당 위젯만 독립적으로 확인할 수 있어서 개발 속도가 확실히 빨라졌다. hot reload도 더 빠르게 작동하고, 메모리 사용량도 적다. 복잡한 네비게이션이나 상태 관리 없이 순수하게 위젯 자체에만 집중할 수 있다는 점이 가장 큰 장점이었다.
주의해야 할 점들
flutter_screenutil 호환성 문제
프로젝트에서 flutter_screenutil 패키지를 사용하고 있었는데, 5.9.2 버전부터 WidgetBook과 호환이 안 되는 문제가 있었다. flutter_screenutil이 MediaQuery.of 대신 View.of를 사용하기 시작하면서 생긴 문제였다.
screenutil 패키지 자체가 웹 호환성이 안 좋다는 이야기도 있어서 아예 사용하지 않는 것을 고려해봤지만, 결국 flutter_screenutil 버전을 5.9.2 이전 버전으로 다운그레이드해서 WidgetBook과의 호환성 문제를 해결했다.
최신 버전을 사용하지 못하는 아쉬움은 있지만, 당장은 프로젝트의 일관성을 유지하는 것이 더 중요했다. 추후 패키지가 업데이트되어 호환성 문제가 해결되면 다시 최신 버전으로 업그레이드할 예정이다.
웹 뷰포트 문제
WidgetBook을 웹에서 실행할 때 뷰포트 관련 이슈가 간혹 있었다. 이는 WidgetBook의 문제라기 보다는 ScreenUtil의 문제라고 생각이 된다. 웹 뷰포트를 확인할 때 글씨 크기가 제대로 적용되지 않는 문제가 있다.
배포 비용
WidgetBook 클라우드 배포 서비스는 완전 무료가 아니다. 일주일에 위젯 1000건까지는 무료인데, 애드온이 붙으면 위젯 수 × 애드온 개수로 계산된다. 이전 프로젝트의 위젯 개수를 생각해보면 꽤나 빠듯할 것 같다. 아마 일주일에 한 번 정도 배포가 가능할 거라고 예상된다.
물론 로컬에서만 사용한다면 비용 걱정은 없지만, 팀 전체가 공유해서 사용하려면 배포가 필요한데, 이 부분은 조금 고민이 된다. 그래도 개발 생산성 향상을 생각하면 충분히 투자 가치가 있다고 생각한다.
러닝 커브
annotation 방식으로 스토리를 작성하는 것이 처음에는 조금 낯설었다. 기존의 Flutter 개발 방식과는 다른 접근이라서, 배우는데 시간이 조금 소모됐다. 하지만 생각보다는 그렇게 어려운 내용은 아니기 때문에 금방 배울 수 있다.
마무리
WidgetBook은 확실히 Flutter 개발 생산성을 높여주는 훌륭한 도구다. 특히 디자인 시스템을 체계적으로 관리하고, 팀 간 소통을 원활하게 해주는 부분에서는 말할 것도 없이 유용하다. 위젯을 격리된 환경에서 개발하고 테스트할 수 있다는 점만으로도 도입할 가치가 충분하다.
물론 flutter_screenutil 호환성 문제나 배포 비용 등 고려해야 할 점들이 있지만, 장기적으로 보면 분명히 투자 대비 효과가 있는 도구라고 생각한다. 특히 컴포넌트가 많고 복잡한 프로젝트일수록 그 진가를 발휘할 것 같다.
앞으로는 좀 더 체계적으로 스토리를 작성하고, 팀 내 WidgetBook 사용 가이드라인을 정립해서 더 효율적으로 활용해볼 계획이다. 결국 도구는 어떻게 사용하느냐가 중요한데, WidgetBook도 잘 활용하면 Flutter 개발을 한층 더 체계적으로 만들어줄 수 있을 것 같다.
'Develop > Flutter' 카테고리의 다른 글
[Flutter][Issue] 이전 포커스로 돌아가는 현상 해결 (0) | 2025.05.16 |
---|---|
[Flutter][Issue] iOS 카카오 로그인과 버전 관리 이슈 (0) | 2025.04.24 |
[Flutter][Issue] 구글 맵 버벅임 문제 해결 (0) | 2025.04.23 |
[Flutter][Error] 난독화와 enum.name (0) | 2025.03.10 |
[Flutter] SizedBox를 Padding 대신 사용하지 말아야 하는 이유 (0) | 2025.02.08 |