Efficiently Using Widgetbook in a Monorepo Setting
In the diverse Flutter ecosystem, it's commonplace for projects to segregate their UI components into distinct packages. Although these UI component packages are pivotal, they seldom form a standalone desktop application. A common hurdle for developers arises when wanting to leverage Widgetbook for these packages.
This document shows a streamlined approach to seamlessly integrate Widgetbook within a monorepo structure. The doc considers a tool-agnostic approach where you can take any part of it and integrate it into your monorepo structure and use any tool that you are comfortable with.
A Primer on Monorepo Structure#
To commence, we present an elementary monorepo framework for a Flutter project which can serve as a foundation.
1. Unpacking the Monorepo Blueprint#
Here's an ideal monorepo configuration tailored for Widgetbook:
- apps: Houses independent applications.
- packages: Stores shared Dart/Flutter modules.
- widgetbook: Incorporates the Widgetbook configurations and previews.
monorepo_example/
apps/
main_app/
admin_app/
packages/
shared_ui/
widgetbook/
An alternative structure situates the widgetbook
within the apps
directory. This organization depends on the developer's preference.
However, isolating widgetbook
from apps implies its unique purpose
— a platform designed exclusively to refine UI development.
2. Orchestrating Dependencies#
Every package or application's pubspec.yaml
should specify dependencies, which
allows referencing between packages.
For instance, within main_app
, widgetbook
, or admin_app
, the
shared_ui
can be referenced as:
dependencies:
shared_ui:
path: ../../packages/shared_ui
For clarity, the complete pubspec.yaml
of the widgetbook
directory is
illustrated below:
name: widgetbook_workspace # cannot be named just "widgetbook"
description: A playground to streamline shared_ui with Widgetbook
publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ">=3.1.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
shared_ui:
path: ../packages/shared_ui
flutter:
uses-material-design: true
This configuration facilitates the sharing of shared_ui
across multiple
projects, optimizing it further with Widgetbook during the development phase.
3. Creating a Widget#
For a hands-on experience, let's formulate a custom button within shared_ui
:
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
const CustomButton({
super.key,
required this.text,
required this.onPressed,
this.color = Colors.blue,
this.textColor = Colors.white,
this.borderRadius = 8.0,
});
final String text;
final VoidCallback onPressed;
final Color color;
final Color textColor;
final double borderRadius;
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: textColor,
backgroundColor: color,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(borderRadius),
),
),
onPressed: onPressed,
child: Text(text),
);
}
}
4. Configuring Widgetbook#
Within the widgetbook
directory, update the pubspec.yaml
to
include the requisite dependency
and dev_dependencies
, ensuring you
incorporate shared_ui
:
dependencies:
flutter:
sdk: flutter
widgetbook_annotation: ^{{ versions.annotation }}
widgetbook: ^{{ versions.widgetbook }}
shared_ui:
path: ../../packages/shared_ui
dev_dependencies:
widgetbook_generator: ^{{ versions.generator }}
build_runner:
5. Showcasing Widget Previews#
Go into widgetbook/lib
to commence drafting use cases and embarking
on other widgetbook-focused assignments.
For our earlier defined CustomButton
within the shared_ui
library, we can
craft two distinct use cases:
// lib/custom_button.dart <- File name
import 'package:flutter/material.dart';
import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook;
import 'package:shared_ui/shared_ui.dart';
@widgetbook.UseCase(
name: 'with green color',
type: CustomButton,
)
Widget greenContainerUseCase(BuildContext context) {
return Center(
child: CustomButton(
text: 'Text',
onPressed: () {},
color: Colors.green,
),
);
}
@widgetbook.UseCase(
name: 'with red color',
type: CustomButton,
)
Widget redContainerUseCase(BuildContext context) {
return Center(
child: CustomButton(
text: 'Text',
onPressed: () {},
color: Colors.red,
),
);
}
To render your widgets, create an app within the main.dart
file:
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({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Widgetbook.material(
// Use the generated directories variable
directories: directories,
addons: [],
);
}
}
6. Launching Widgetbook#
Navigate to the widgetbook
directory and run:
flutter pub run build_runner watch
This command initiates the generator in real-time. Subsequently, execute:
flutter run
See source code
This will showcase the CustomButton
widget in the Widgetbook environment.
Awesome, this was just the beginning, now you can start building upon this approach.