Chuyển đến nội dung chính

Flutter Widget Previews đã stable: hướng dẫn thực chiến với @Preview

Widget Previews ra mắt dạng thử nghiệm từ 3.35, và khá nhiều lập trình viên thử một lần, thấy chậm, rồi quay về hot reload. Flutter 3.47 đánh dấu nó stable, kèm cache build cục bộ và một API theme trừu tượng khiến tính năng này đáng nhìn lại. Nếu bạn làm design system, thư viện component, hay bất cứ thứ gì có hơn ba trạng thái hiển thị, thứ này thay đổi vòng lặp làm việc của bạn.

Lời hứa của nó hẹp và trung thực: previewer render từng widget riêng lẻ, không cần khởi động app, không cần điều hướng tới đúng màn hình, không cần giả lập đúng state. Đó là việc khác với hot reload, và là việc hot reload xưa nay vẫn làm dở.

Khởi động previewer

Từ IDE — Android Studio, IntelliJ, hay VS Code — previewer tự chạy; mở tab Flutter Widget Preview ở sidebar. Từ terminal:

flutter widget-preview start

Lệnh này dựng một server cục bộ và mở môi trường xem trước trực tiếp trong trình duyệt. Build được cache trong thư mục .widget_preview/ của project — cache này chính là lý do khởi động nhanh lên rõ rệt ở 3.47, và là thứ bạn nên thêm vào .gitignore nếu tooling chưa tự làm.

Bạn có thể annotate cái gì

@Preview đến từ package:flutter/widget_previews.dart và áp dụng được cho:

  • hàm top-level trả về Widget hoặc WidgetBuilder
  • static method trong class trả về Widget hoặc WidgetBuilder
  • constructor và factory public của widget không có tham số bắt buộc

Trường hợp đơn giản nhất:

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

@Preview(name: 'My Sample Text')
Widget mySampleText() {
  return const Text('Hello, World!');
}

Không app, không MaterialApp, không route. Lưu file là preview cập nhật.

Đầy đủ tham số, và chúng thực sự dùng để làm gì

Class Preview nhỏ gọn, đó là dấu hiệu tốt. Đây là toàn bộ bề mặt API:

Tham sốKiểuTác dụng
groupStringGom nhóm các preview liên quan. Mặc định 'Default'.
nameString?Nhãn hiển thị cạnh preview.
sizeSize?Ràng buộc kích thước nhân tạo áp lên widget.
textScaleFactordouble?Tỉ lệ phóng chữ, để kiểm tra accessibility.
wrapperWidgetWrapper?Bọc widget trong một cây widget — scaffold, provider, InheritedWidget.
themePreviewTheme?Trả về dữ liệu theme Material và Cupertino để áp dụng.
brightnessBrightness?Độ sáng sáng/tối ban đầu.
localizationsPreviewLocalizations?Cấu hình localization cho preview.

wrapper là thứ khiến preview dùng được trong codebase thật. Phần lớn widget không đứng một mình — chúng cần Scaffold, cần theme, hoặc cần một repository được inject. Bạn cung cấp một lần:

@Preview(
  name: 'Submit button — trạng thái đang bấm',
  group: 'Form Controls',
  size: Size(240, 56),
  textScaleFactor: 1.5,
  wrapper: _inScaffold,
)
Widget submitButtonPreview() => const SubmitButton(isBusy: true);

Widget _inScaffold(Widget child) => MaterialApp(
      home: Scaffold(body: Center(child: child)),
    );

Render cùng một widget theo nhiều cách

Đây là chỗ preview thắng hot reload thẳng thừng. Xếp chồng annotation để có một widget được render dưới nhiều cấu hình cùng lúc:

@Preview(group: 'Brightness', name: 'Light', brightness: Brightness.light)
@Preview(group: 'Brightness', name: 'Dark', brightness: Brightness.dark)
Widget buttonPreview() => const ButtonShowcase();

Khi cùng ba bốn biến thể đó lặp lại qua hàng chục component, hãy nâng chúng thành một MultiPreview:

final class MultiBrightnessPreview extends MultiPreview {
  const MultiBrightnessPreview();

  @override
  List<Preview> get previews => const [
        Preview(group: 'Brightness', name: 'Light', brightness: Brightness.light),
        Preview(group: 'Brightness', name: 'Dark', brightness: Brightness.dark),
      ];
}

@MultiBrightnessPreview()
Widget buttonPreview() => const ButtonShowcase();

Nhúng design system vào một annotation riêng

API theme trừu tượng thêm ở 3.47 là mảnh ghép giúp thứ này mở rộng được. Thay vì lặp theme: trên mọi preview, hãy kế thừa Preview và cấp builder một lần:

final class MyCustomPreview extends Preview {
  const MyCustomPreview({
    super.name,
    super.group,
    super.size,
    super.textScaleFactor,
    super.wrapper,
    super.brightness,
    super.localizations,
  }) : super(theme: MyCustomPreview.themeBuilder);

  static PreviewThemeData themeBuilder() {
    return PreviewThemeData(
      materialLight: ThemeData.light(),
      materialDark: ThemeData.dark(),
    );
  }
}

Giờ mọi @MyCustomPreview(...) trong codebase đều render theo token thật của bạn. PreviewThemeData mang cả dữ liệu Material lẫn Cupertino, điều này ở 3.47 quan trọng hơn trước — khi design system chuyển sang package độc lập material_uicupertino_ui, preview là cách rẻ để xem một component có thực sự sống sót ở cả hai hay không.

Còn có transform(), cho phép annotation tuỳ biến viết lại preview lúc chạy — hữu ích khi muốn thêm tiền tố vào tên hoặc đổi theme cho cả một lớp preview mà không đụng vào chỗ gọi.

Preview không phải golden test

Cần nói thẳng, vì nhiều nhóm nhầm hai thứ này. Preview là công cụ soạn thảo: nhanh, trực quan, có con người trong vòng lặp, không có assertion. Golden test là công cụ chống hồi quy: chậm, headless, và làm đỏ CI khi một pixel xê dịch. Preview không thay thế golden, và một preview “chạy được” chẳng chứng minh gì ngoài việc nó render ra.

Cặp đôi hữu ích là: preview trong lúc xây component, golden khi các trạng thái đã ổn định.

Những giới hạn bạn sẽ gặp

Previewer chạy trong môi trường nền web, nên có ranh giới cứng:

  1. Không dùng được native plugin, và không có dart:io hay dart:ffi. Mọi thứ chạm tới filesystem, platform channel, hay FFI đều không render — hãy inject một bản giả qua wrapper.
  2. Tham số callback phải public và constant. Closure private sẽ không được nhận diện.
  3. Đường dẫn asset phải theo package: dùng 'packages/my_package_name/assets/my_image.png', không phải 'assets/my_image.png'.
  4. Widget không có ràng buộc kích thước sẽ tự bị giới hạn ở khoảng 50% chiều cao và chiều rộng previewer. Truyền size khi điều đó bóp méo layout.
  5. Chỉ hỗ trợ một project hoặc một Pub workspace. Hỗ trợ đa project trong IDE vẫn đang được nghiên cứu, nên một monorepo lớn có thể không sáng đèn hết.

Áp dụng mà không phải viết lại

  1. Nâng lên 3.47 và chạy flutter widget-preview start một lần để chắc previewer build được project.
  2. Thêm .widget_preview/ vào .gitignore.
  3. Chọn một component lá — nút bấm, badge, list tile — và thêm đúng một @Preview. Đừng bắt đầu từ cả màn hình.
  4. Viết một wrapper cài theme và provider của app, rồi tái sử dụng.
  5. Nâng các biến thể lặp lại (sáng/tối, text scale 1.0/2.0, LTR/RTL) thành MultiPreview.
  6. Kế thừa Preview với PreviewThemeData của design system và chuẩn hoá theo annotation đó.
  7. Thêm golden test cho những trạng thái bạn đã biết là đúng.

Kết luận

Widget Previews thôi là bản demo ở 3.47. Cache khiến thời gian khởi động chấp nhận được, và các API theme là ranh giới giữa một món đồ chơi với thứ mà một nhóm design system có thể chuẩn hoá. Các giới hạn là thật — không native code, asset phải theo package, chỉ một workspace — nhưng đó là giới hạn về phạm vi, không phải về độ chín. Nếu bạn từng thử preview ở 3.35 rồi nhún vai, lời khuyên thành thật là thử lại: đây là bản phát hành mà tính năng này xứng đáng có chỗ trong vòng lặp làm việc.


Bài viết gốc đăng tại FlutterCook. Bản trên đó là bản được cập nhật mới nhất.

Nhận xét

Bài đăng phổ biến từ blog này

5 concepts every Flutter dev should know

  Phụ lục: State management architecture Testing IDE Shortcuts Platform channel Maintaining a project Tôi đã làm việc với Flagship trong một thời gian dài, và đây là những điều mà tôi phát hiện ra là điều cần phải có đối với bất kỳ nhà phát triển Flagship nào, về tổng thể nó sẽ khiến bạn trở thành một nhà phát triển Flagship giỏi trong thời gian dài. 1. State management architecture Đây là một trong những chủ đề quan trọng nhất trong cộng đồng thiết bị rung, nó khá quan trọng nếu bạn muốn duy trì một dự án rung kích thước trung bình hoặc lớn. Nó sẽ giúp tạo một dự án suôn sẻ và thêm các tính năng mới một cách hoàn hảo.  2. Testing Đây là một chủ đề duy nhất mà tôi không hiểu tại sao nó lại quan trọng trước đó trong sự nghiệp của tôi, nhưng khi tôi tiến lên trong sự nghiệp của mình và có kinh nghiệm với nhiều dự án và vấn đề xảy ra trong môi trường sản xuất. Tôi đã nhận ra một cách khó khăn, tại sao điều này lại quan trọng như vậy. Nếu bạn vẫn muốn có thêm lý do để cân nhắc thử...

Thiết kế giao diện với DotNetBar (Phần 1)

Đây là phiên bản DotNetBar hỗ trợ C# và Visual Basic https://www.dropbox.com/s/wx80jpvgnlrmtux/DotNetBar.rar  , phiên bản này hỗ trợ giao diện Metro cực kỳ “dễ thương” Các bạn load về và cài đặt, khi cài đặt xong sẽ có source code mẫu của tất cả các control. Để sử dụng được các control của DotNetBar các bạn nhớ add item vào controls box. Thiết kế giao diện với DotNetBar, giao diện sẽ rất đẹp. Link các video hướng dẫn chi tiết cách sử dụng và coding: http://www.devcomponents.com/dotnetbar/movies.aspx Hiện tại DotNetBar có rất nhiều công cụ cực mạnh, trong đó có 3 công cụ dưới đây: DotNetBar for Windows Forms Requires with Visual Studio 2003, 2005, 2008, 2010 or 2012.   DotNetBar for WPF Requires with Visual Studio 2010 or 2012 and Windows Presentation Foundation.   DotNetBar for Silverlight Requires with Visual Studio 2010 or 2012 and Silverlight. Dưới đây là một số hình ảnh về các control trong DotnetBar.   Metro User Interface  controls with Metro Tiles, toolba...

2026 👏 Google miễn phí một năm gói AI Plus cho sinh viên Việt Nam 🇻🇳🇻🇳🇻🇳

Chúc mừng anh em 👏 Google miễn phí một năm gói AI Plus cho sinh viên Việt Nam 🇻🇳🇻🇳🇻🇳 Đối tượng là sinh viên đại học từ 18–24 tuổi (bao gồm sinh viên mới và sinh viên từng dùng gói AI Pro 2025). Lưu ý là cần xác minh tư cách sinh viên hàng năm. Khi nhận gói, anh em có được các ưu đãi sau: 🔖 Nâng cấp bộ nhớ đám mây lên 400GB. 🔖 Nhân đôi hạn mức truy cập mô hình AI. 🔖 Mở quyền trải nghiệm tính năng tạo video bằng Gemini Omni. Lưu ý chương trình chỉ dành cho sinh viên đủ điều kiện. Hạn nhận ưu đãi: 31 tháng 12, 2026. Các bạn cần có phương thức thanh toán hợp lệ khi đăng ký. Google AI Plus sẽ tự động tính phí 132.000 ₫/tháng sau khi thời gian dùng thử kết thúc, trừ phi bạn đã huỷ trước đó. Huỷ bất cứ lúc nào. CÁC BƯỚC THỰC HIỆN CHI TIẾT 🔹Bước 1: Truy cập cổng đăng ký chương trình 🔖 Mở trình duyệt và truy cập vào trang chính thức: gemini.google/students/ 🔖 Đăng nhập vào Tài khoản Google cá nhân của bạn. 🔖 Nhấn vào nút "Claim your student plan at no cost" (hoặc Nhận gó...