Skip to content

Commit 4e027f7

Browse files
authored
[ Widget Preview ] Link to placeholder documentation when no previews are defined (#166869)
Fixes flutter/flutter#166443 ![image](https://github.com/user-attachments/assets/04870f1c-109d-4aa8-aebf-d204e44250df)
1 parent ac1d861 commit 4e027f7

8 files changed

Lines changed: 206 additions & 55 deletions

File tree

packages/flutter_tools/lib/src/commands/widget_preview.dart

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,14 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
315315
/// The resulting binary is used to speed up subsequent widget previewer launches
316316
/// by acting as a basic scaffold to load previews into using hot reload / restart.
317317
Future<void> initialBuild({required FlutterProject widgetPreviewScaffoldProject}) async {
318+
// Generate initial package_config.json, otherwise the build will fail.
319+
await pub.get(
320+
context: PubContext.create,
321+
project: widgetPreviewScaffoldProject,
322+
offline: offline,
323+
outputMode: PubOutputMode.summaryOnly,
324+
);
325+
318326
// TODO(bkonyi): handle error case where desktop device isn't enabled.
319327
await widgetPreviewScaffoldProject.ensureReadyForPlatformSpecificTooling(
320328
releaseMode: false,
@@ -324,14 +332,6 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
324332
webPlatform: isWeb,
325333
);
326334

327-
// Generate initial package_config.json, otherwise the build will fail.
328-
await pub.get(
329-
context: PubContext.create,
330-
project: widgetPreviewScaffoldProject,
331-
offline: offline,
332-
outputMode: PubOutputMode.summaryOnly,
333-
);
334-
335335
if (isWeb) {
336336
return;
337337
}
@@ -577,22 +577,34 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
577577
// Adds a path dependency on the parent project so previews can be
578578
// imported directly into the preview scaffold.
579579
const String pubAdd = 'add';
580+
// Use `json.encode` to handle escapes correctly.
581+
final String pathDescriptor = json.encode(<String, Object?>{
582+
// `pub add` interprets relative paths relative to the current directory.
583+
'path': rootProject.directory.fileSystem.path.relative(rootProject.directory.path),
584+
});
585+
580586
await pub.interactively(
581587
<String>[
582588
pubAdd,
583589
if (offline) '--offline',
584590
'--directory',
585591
widgetPreviewScaffoldProject.directory.path,
586592
// Ensure the path using POSIX separators, otherwise the "path_not_posix" check will fail.
587-
'${rootProject.manifest.appName}:{"path":${rootProject.directory.path.replaceAll(r"\", "/")}}',
593+
'${rootProject.manifest.appName}:$pathDescriptor',
588594
],
589595
context: PubContext.pubAdd,
590596
command: pubAdd,
591597
touchesPackageConfig: true,
592598
);
593599

594-
// Adds a dependency on flutter_lints, which is referenced by the
595-
// analysis_options.yaml generated by the 'app' template.
600+
// Adds dependencies on:
601+
// - dtd, which is used to connect to the Dart Tooling Daemon to establish communication
602+
// with other developer tools.
603+
// - flutter_lints, which is referenced by the analysis_options.yaml generated by the 'app'
604+
// template.
605+
// - stack_trace, which is used to generate terse stack traces for displaying errors thrown
606+
// by widgets being previewed.
607+
// - url_launcher, which is used to open a browser to the preview documentation.
596608
await pub.interactively(
597609
<String>[
598610
pubAdd,
@@ -601,6 +613,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
601613
widgetPreviewScaffoldProject.directory.path,
602614
'flutter_lints',
603615
'stack_trace',
616+
'url_launcher',
604617
],
605618
context: PubContext.pubAdd,
606619
command: pubAdd,

packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils.dart.tmpl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ TextStyle fixBlurryText(TextStyle style) {
1212
);
1313
}
1414

15+
final TextStyle linkTextStyle = fixBlurryText(
16+
TextStyle(
17+
decoration: TextDecoration.underline,
18+
// TODO(bkonyi): this color scheme is from DevTools and should be responsive
19+
// to changes in the previewer theme.
20+
color: const Color(0xFF1976D2),
21+
),
22+
);
23+
1524
/// A basic vertical spacer.
1625
class VerticalSpacer extends StatelessWidget {
1726
/// Creates a basic vertical spacer.

packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:flutter/rendering.dart';
1111
import 'package:flutter/services.dart';
1212

1313
import 'package:stack_trace/stack_trace.dart';
14+
import 'package:url_launcher/url_launcher.dart';
1415

1516
import 'controls.dart';
1617
import 'generated_preview.dart';
@@ -79,15 +80,6 @@ class _WidgetPreviewErrorWidget extends StatelessWidget {
7980
.map((frame) => frame.location.length)
8081
.fold(0, math.max);
8182

82-
final TextStyle linkTextStyle = fixBlurryText(
83-
TextStyle(
84-
decoration: TextDecoration.underline,
85-
// TODO(bkonyi): this color scheme is from DevTools and should be responsive
86-
// to changes in the previewer theme.
87-
color: const Color(0xFF1976D2),
88-
),
89-
);
90-
9183
// Print out the stack trace nicely formatted.
9284
return frames.map<TextSpan>((frame) {
9385
if (frame is UnparsedFrame) return TextSpan(text: '$frame\n');
@@ -111,6 +103,50 @@ class _WidgetPreviewErrorWidget extends StatelessWidget {
111103
}
112104
}
113105

106+
/// Displayed when no @Preview() annotations are detected in the project.
107+
///
108+
/// Links to documentation.
109+
class NoPreviewsDetectedWidget extends StatelessWidget {
110+
const NoPreviewsDetectedWidget({super.key});
111+
112+
// TODO(bkonyi): update with actual documentation on flutter.dev.
113+
static Uri documentationUrl = Uri.https(
114+
'github.com',
115+
'flutter/flutter/blob/master/packages/flutter/'
116+
'lib/src/widgets/widget_preview.dart',
117+
);
118+
119+
@override
120+
Widget build(BuildContext context) {
121+
// TODO(bkonyi): base this on the current color theme (dark vs light)
122+
final style = fixBlurryText(TextStyle(color: Colors.black));
123+
return Center(
124+
child: Column(
125+
children: <Widget>[
126+
Text(
127+
'No previews detected',
128+
style: style.copyWith(fontWeight: FontWeight.bold),
129+
),
130+
const VerticalSpacer(),
131+
Text('Read more about getting started with widget previews at:'),
132+
Text.rich(
133+
TextSpan(
134+
text: documentationUrl.toString(),
135+
style: linkTextStyle,
136+
recognizer:
137+
TapGestureRecognizer()
138+
..onTap = () {
139+
launchUrl(documentationUrl);
140+
},
141+
),
142+
style: style,
143+
),
144+
],
145+
),
146+
);
147+
}
148+
}
149+
114150
class WidgetPreviewWidget extends StatefulWidget {
115151
const WidgetPreviewWidget({super.key, required this.preview});
116152

@@ -444,13 +480,17 @@ class PreviewAssetBundle extends PlatformAssetBundle {
444480
/// the preview scaffold project which prevents us from being able to use hot
445481
/// restart to iterate on this file.
446482
Future<void> mainImpl() async {
447-
runApp(_WidgetPreviewScaffold());
483+
runApp(WidgetPreviewScaffold(previews: previews));
448484
}
449485

450486
/// Define the Enum for Layout Types
451487
enum LayoutType { gridView, listView }
452488

453-
class _WidgetPreviewScaffold extends StatelessWidget {
489+
class WidgetPreviewScaffold extends StatelessWidget {
490+
WidgetPreviewScaffold({super.key, required this.previews});
491+
492+
final List<WidgetPreview> Function() previews;
493+
454494
// Positioning values for positioning the previewer
455495
final double _previewLeftPadding = 60.0;
456496
final double _previewRightPadding = 20.0;
@@ -555,16 +595,7 @@ class _WidgetPreviewScaffold extends StatelessWidget {
555595
if (previewList.isEmpty) {
556596
previewView = Column(
557597
mainAxisAlignment: MainAxisAlignment.center,
558-
children: <Widget>[
559-
Center(
560-
// TODO: consider including details on how to get started
561-
// with Widget Previews.
562-
child: Text(
563-
'No previews available',
564-
style: fixBlurryText(TextStyle(color: Colors.white)),
565-
),
566-
),
567-
],
598+
children: <Widget>[NoPreviewsDetectedWidget()],
568599
);
569600
} else {
570601
previewView = LayoutBuilder(

packages/flutter_tools/templates/widget_preview_scaffold/pubspec.yaml.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies:
1414
# These will be replaced with proper constraints after the template is hydrated.
1515
flutter_lints: any
1616
stack_trace: any
17+
url_launcher: any
1718

1819
flutter:
1920
uses-material-design: true

packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ TextStyle fixBlurryText(TextStyle style) {
1212
);
1313
}
1414

15+
final TextStyle linkTextStyle = fixBlurryText(
16+
TextStyle(
17+
decoration: TextDecoration.underline,
18+
// TODO(bkonyi): this color scheme is from DevTools and should be responsive
19+
// to changes in the previewer theme.
20+
color: const Color(0xFF1976D2),
21+
),
22+
);
23+
1524
/// A basic vertical spacer.
1625
class VerticalSpacer extends StatelessWidget {
1726
/// Creates a basic vertical spacer.

packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:flutter/rendering.dart';
1111
import 'package:flutter/services.dart';
1212

1313
import 'package:stack_trace/stack_trace.dart';
14+
import 'package:url_launcher/url_launcher.dart';
1415

1516
import 'controls.dart';
1617
import 'generated_preview.dart';
@@ -79,15 +80,6 @@ class _WidgetPreviewErrorWidget extends StatelessWidget {
7980
.map((frame) => frame.location.length)
8081
.fold(0, math.max);
8182

82-
final TextStyle linkTextStyle = fixBlurryText(
83-
TextStyle(
84-
decoration: TextDecoration.underline,
85-
// TODO(bkonyi): this color scheme is from DevTools and should be responsive
86-
// to changes in the previewer theme.
87-
color: const Color(0xFF1976D2),
88-
),
89-
);
90-
9183
// Print out the stack trace nicely formatted.
9284
return frames.map<TextSpan>((frame) {
9385
if (frame is UnparsedFrame) return TextSpan(text: '$frame\n');
@@ -111,6 +103,50 @@ class _WidgetPreviewErrorWidget extends StatelessWidget {
111103
}
112104
}
113105

106+
/// Displayed when no @Preview() annotations are detected in the project.
107+
///
108+
/// Links to documentation.
109+
class NoPreviewsDetectedWidget extends StatelessWidget {
110+
const NoPreviewsDetectedWidget({super.key});
111+
112+
// TODO(bkonyi): update with actual documentation on flutter.dev.
113+
static Uri documentationUrl = Uri.https(
114+
'github.com',
115+
'flutter/flutter/blob/master/packages/flutter/'
116+
'lib/src/widgets/widget_preview.dart',
117+
);
118+
119+
@override
120+
Widget build(BuildContext context) {
121+
// TODO(bkonyi): base this on the current color theme (dark vs light)
122+
final style = fixBlurryText(TextStyle(color: Colors.black));
123+
return Center(
124+
child: Column(
125+
children: <Widget>[
126+
Text(
127+
'No previews detected',
128+
style: style.copyWith(fontWeight: FontWeight.bold),
129+
),
130+
const VerticalSpacer(),
131+
Text('Read more about getting started with widget previews at:'),
132+
Text.rich(
133+
TextSpan(
134+
text: documentationUrl.toString(),
135+
style: linkTextStyle,
136+
recognizer:
137+
TapGestureRecognizer()
138+
..onTap = () {
139+
launchUrl(documentationUrl);
140+
},
141+
),
142+
style: style,
143+
),
144+
],
145+
),
146+
);
147+
}
148+
}
149+
114150
class WidgetPreviewWidget extends StatefulWidget {
115151
const WidgetPreviewWidget({super.key, required this.preview});
116152

@@ -444,13 +480,17 @@ class PreviewAssetBundle extends PlatformAssetBundle {
444480
/// the preview scaffold project which prevents us from being able to use hot
445481
/// restart to iterate on this file.
446482
Future<void> mainImpl() async {
447-
runApp(_WidgetPreviewScaffold());
483+
runApp(WidgetPreviewScaffold(previews: previews));
448484
}
449485

450486
/// Define the Enum for Layout Types
451487
enum LayoutType { gridView, listView }
452488

453-
class _WidgetPreviewScaffold extends StatelessWidget {
489+
class WidgetPreviewScaffold extends StatelessWidget {
490+
WidgetPreviewScaffold({super.key, required this.previews});
491+
492+
final List<WidgetPreview> Function() previews;
493+
454494
// Positioning values for positioning the previewer
455495
final double _previewLeftPadding = 60.0;
456496
final double _previewRightPadding = 20.0;
@@ -555,16 +595,7 @@ class _WidgetPreviewScaffold extends StatelessWidget {
555595
if (previewList.isEmpty) {
556596
previewView = Column(
557597
mainAxisAlignment: MainAxisAlignment.center,
558-
children: <Widget>[
559-
Center(
560-
// TODO: consider including details on how to get started
561-
// with Widget Previews.
562-
child: Text(
563-
'No previews available',
564-
style: fixBlurryText(TextStyle(color: Colors.white)),
565-
),
566-
),
567-
],
598+
children: <Widget>[NoPreviewsDetectedWidget()],
568599
);
569600
} else {
570601
previewView = LayoutBuilder(

packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/pubspec.yaml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: widget_preview_scaffold
22
description: Scaffolding for Flutter Widget Previews
3-
publish_to: 'none'
3+
publish_to: "none"
44
version: 0.0.1
55

66
environment:
@@ -14,6 +14,7 @@ dependencies:
1414
# These will be replaced with proper constraints after the template is hydrated.
1515
flutter_lints: 5.0.0
1616
stack_trace: 1.12.1
17+
url_launcher: 6.3.1
1718

1819
async: 2.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
1920
boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -29,15 +30,24 @@ dependencies:
2930
material_color_utilities: 0.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
3031
meta: 1.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
3132
path: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
33+
plugin_platform_interface: 2.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
3234
source_span: 1.10.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
3335
stream_channel: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
3436
string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
3537
term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
3638
test_api: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
39+
url_launcher_android: 6.3.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
40+
url_launcher_ios: 6.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
41+
url_launcher_linux: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
42+
url_launcher_macos: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
43+
url_launcher_platform_interface: 2.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
44+
url_launcher_web: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
45+
url_launcher_windows: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
3746
vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
3847
vm_service: 15.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
48+
web: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
3949

4050
flutter:
4151
uses-material-design: true
4252

43-
# PUBSPEC CHECKSUM: 367e
53+
# PUBSPEC CHECKSUM: 7e9e

0 commit comments

Comments
 (0)