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

Bài đăng

Theming Material 3 trong Flutter: vai trò màu, không phải giá trị màu

Việc chuyển sang Material 3 trong Flutter tạo ra một mô-típ rất dễ đoán. Đội ngũ gọi ColorScheme.fromSeed(seedColor: brandPurple) , nhìn kết quả, kết luận “đây không phải màu thương hiệu của mình”, rồi quay lại truyền màu tường minh cho từng widget. Sáu tháng sau, ứng dụng có hai trăm giá trị hex cứng, dark mode là một danh sách hai trăm giá trị khác, và đổi màu thương hiệu tốn cả tuần. Thứ bị bỏ qua chính là ý tưởng mà Material 3 được xây dựng trên đó: bạn không tô widget bằng màu, bạn gán cho nó một vai trò . Một FilledButton không có nền màu tím — nó có nền primary với nội dung onPrimary . Khi điều đó đúng trên toàn ứng dụng, bảng màu trở thành một object duy nhất mà bạn có thể thay. Các vai trò và mục đích của từng cái ColorScheme có khoảng ba mươi thành viên. Đó không phải ba mươi lựa chọn độc lập; đó là các cặp và các họ. Vai trò Dùng cho Cặp “on” của nó primary Hành động chính, nút filled, trạng thái active onPrimary primaryCon...
Các bài đăng gần đây

Golden tests for design systems — hướng dẫn Flutter

Design systems need visual regression more than feature apps. Bài này chuyển quan sát đó thành một mô hình nhỏ để bạn có thể kiểm thử, đo lường và giữ cho code dễ bảo trì khi app lớn lên. Vấn đề cốt lõi Font loading, surface sizes, and review workflow for visual diffs. Câu hỏi hữu ích không phải API hay pattern có đẹp riêng lẻ hay không, mà là state nằm ở đâu, boundary nào chịu trách nhiệm khi lỗi xảy ra, và người dùng phục hồi thế nào khi happy path biến mất. Mô hình thực tế Load real fonts in tests or goldens will lie. Hãy bắt đầu bằng một owner rõ ràng cho behavior. Widget chỉ nên render và phát intent; IO, persistence, permission và retry nên nằm sau một interface nhỏ. Nhờ vậy bạn có seam để fake trong test và một chỗ ghi lại các dữ kiện cần theo dõi ở production. Trong app Flutter, boundary thường có dạng: Widget phát intent như load, submit, refresh hoặc retry. Controller hoặc use case validate intent rồi gọi boundary. Boundary trả về data có kiểu hoặc failure có kiểu,...

TestFlight internal vs external testing that finds real bugs — hướng dẫn Flutter

External testers find environment bugs internal teams never see. Bài này chuyển quan sát đó thành một mô hình nhỏ để bạn có thể kiểm thử, đo lường và giữ cho code dễ bảo trì khi app lớn lên. Vấn đề cốt lõi Groups, feedback, and build expiry planning. Câu hỏi hữu ích không phải API hay pattern có đẹp riêng lẻ hay không, mà là state nằm ở đâu, boundary nào chịu trách nhiệm khi lỗi xảy ra, và người dùng phục hồi thế nào khi happy path biến mất. Mô hình thực tế Keep a always-green internal build for demos. Hãy bắt đầu bằng một owner rõ ràng cho behavior. Widget chỉ nên render và phát intent; IO, persistence, permission và retry nên nằm sau một interface nhỏ. Nhờ vậy bạn có seam để fake trong test và một chỗ ghi lại các dữ kiện cần theo dõi ở production. Trong app Flutter, boundary thường có dạng: Widget phát intent như load, submit, refresh hoặc retry. Controller hoặc use case validate intent rồi gọi boundary. Boundary trả về data có kiểu hoặc failure có kiểu, không trả string c...

Material 3 theming in Flutter: colour roles, not colour values

The Material 3 migration in Flutter produced a predictable pattern. Teams call ColorScheme.fromSeed(seedColor: brandPurple) , look at the result, decide it is not their brand, and start passing explicit colours to every widget again. Six months later the app has two hundred hard-coded hex values, dark mode is a separate list of two hundred more, and changing the brand colour is a week of work. The thing that was skipped is the idea Material 3 is actually built on: you do not style widgets with colours, you assign them roles . A FilledButton does not have a purple background — it has a primary background with onPrimary content. Once that is true across the app, the palette becomes one object you can swap. The roles, and what each one is for ColorScheme has about thirty members. They are not thirty independent choices; they are pairs and families. Role Used for Its “on” pair primary The main action, filled buttons, active states onPrim...

Golden tests for design systems

Design systems need visual regression more than feature apps. This article turns that observation into a small implementation model you can test, measure, and keep boring when the app grows. The problem in one sentence Font loading, surface sizes, and review workflow for visual diffs. The useful question is not whether the API or pattern looks elegant in isolation. It is where state lives, which boundary owns failure, and how a user recovers when the happy path disappears. A practical model Load real fonts in tests or goldens will lie. Start with one explicit owner for the behavior. Keep widgets responsible for rendering and user intent; keep IO, persistence, permissions, and retries behind a small interface. That gives you a seam for a fake in tests and a place to record the facts that matter in production. For a Flutter app, the boundary usually looks like this: The widget emits an intent such as load, submit, refresh, or retry. A controller or use case validates the intent...

TestFlight internal vs external testing that finds real bugs

External testers find environment bugs internal teams never see. This article turns that observation into a small implementation model you can test, measure, and keep boring when the app grows. The problem in one sentence Groups, feedback, and build expiry planning. The useful question is not whether the API or pattern looks elegant in isolation. It is where state lives, which boundary owns failure, and how a user recovers when the happy path disappears. A practical model Keep a always-green internal build for demos. Start with one explicit owner for the behavior. Keep widgets responsible for rendering and user intent; keep IO, persistence, permissions, and retries behind a small interface. That gives you a seam for a fake in tests and a place to record the facts that matter in production. For a Flutter app, the boundary usually looks like this: The widget emits an intent such as load, submit, refresh, or retry. A controller or use case validates the intent and calls the boun...

A mobile technical debt inventory that leaders fund — hướng dẫn Flutter

Debt persists when it is invisible to product leadership. Bài này chuyển quan sát đó thành một mô hình nhỏ để bạn có thể kiểm thử, đo lường và giữ cho code dễ bảo trì khi app lớn lên. Vấn đề cốt lõi Quantify risk, user impact, and paydown capacity. Câu hỏi hữu ích không phải API hay pattern có đẹp riêng lẻ hay không, mà là state nằm ở đâu, boundary nào chịu trách nhiệm khi lỗi xảy ra, và người dùng phục hồi thế nào khi happy path biến mất. Mô hình thực tế Score debt by user impact × failure likelihood. Hãy bắt đầu bằng một owner rõ ràng cho behavior. Widget chỉ nên render và phát intent; IO, persistence, permission và retry nên nằm sau một interface nhỏ. Nhờ vậy bạn có seam để fake trong test và một chỗ ghi lại các dữ kiện cần theo dõi ở production. Trong app Flutter, boundary thường có dạng: Widget phát intent như load, submit, refresh hoặc retry. Controller hoặc use case validate intent rồi gọi boundary. Boundary trả về data có kiểu hoặc failure có kiểu, không trả string c...

A 2026 skills roadmap for Flutter teams — hướng dẫn Flutter

Frameworks move. Teams need a deliberate learning backlog. Bài này chuyển quan sát đó thành một mô hình nhỏ để bạn có thể kiểm thử, đo lường và giữ cho code dễ bảo trì khi app lớn lên. Vấn đề cốt lõi What to learn next: Impeller, SwiftPM, AI tooling, adaptive UI. Câu hỏi hữu ích không phải API hay pattern có đẹp riêng lẻ hay không, mà là state nằm ở đâu, boundary nào chịu trách nhiệm khi lỗi xảy ra, và người dùng phục hồi thế nào khi happy path biến mất. Mô hình thực tế Budget one skill upgrade per quarter per engineer. Hãy bắt đầu bằng một owner rõ ràng cho behavior. Widget chỉ nên render và phát intent; IO, persistence, permission và retry nên nằm sau một interface nhỏ. Nhờ vậy bạn có seam để fake trong test và một chỗ ghi lại các dữ kiện cần theo dõi ở production. Trong app Flutter, boundary thường có dạng: Widget phát intent như load, submit, refresh hoặc retry. Controller hoặc use case validate intent rồi gọi boundary. Boundary trả về data có kiểu hoặc failure có kiểu, ...

Keyboard shortcuts and pointer support for tablets — hướng dẫn Flutter

Tablets and desktops need keyboard-first paths. Bài này chuyển quan sát đó thành một mô hình nhỏ để bạn có thể kiểm thử, đo lường và giữ cho code dễ bảo trì khi app lớn lên. Vấn đề cốt lõi Shortcuts widget, hover, and trackpad scrolling. Câu hỏi hữu ích không phải API hay pattern có đẹp riêng lẻ hay không, mà là state nằm ở đâu, boundary nào chịu trách nhiệm khi lỗi xảy ra, và người dùng phục hồi thế nào khi happy path biến mất. Mô hình thực tế Add Shortcuts/Actions for primary flows. Hãy bắt đầu bằng một owner rõ ràng cho behavior. Widget chỉ nên render và phát intent; IO, persistence, permission và retry nên nằm sau một interface nhỏ. Nhờ vậy bạn có seam để fake trong test và một chỗ ghi lại các dữ kiện cần theo dõi ở production. Trong app Flutter, boundary thường có dạng: Widget phát intent như load, submit, refresh hoặc retry. Controller hoặc use case validate intent rồi gọi boundary. Boundary trả về data có kiểu hoặc failure có kiểu, không trả string chỉ dành cho log. ...

A mobile technical debt inventory that leaders fund

Debt persists when it is invisible to product leadership. This article turns that observation into a small implementation model you can test, measure, and keep boring when the app grows. The problem in one sentence Quantify risk, user impact, and paydown capacity. The useful question is not whether the API or pattern looks elegant in isolation. It is where state lives, which boundary owns failure, and how a user recovers when the happy path disappears. A practical model Score debt by user impact × failure likelihood. Start with one explicit owner for the behavior. Keep widgets responsible for rendering and user intent; keep IO, persistence, permissions, and retries behind a small interface. That gives you a seam for a fake in tests and a place to record the facts that matter in production. For a Flutter app, the boundary usually looks like this: The widget emits an intent such as load, submit, refresh, or retry. A controller or use case validates the intent and calls the boun...

A 2026 skills roadmap for Flutter teams

Frameworks move. Teams need a deliberate learning backlog. This article turns that observation into a small implementation model you can test, measure, and keep boring when the app grows. The problem in one sentence What to learn next: Impeller, SwiftPM, AI tooling, adaptive UI. The useful question is not whether the API or pattern looks elegant in isolation. It is where state lives, which boundary owns failure, and how a user recovers when the happy path disappears. A practical model Budget one skill upgrade per quarter per engineer. Start with one explicit owner for the behavior. Keep widgets responsible for rendering and user intent; keep IO, persistence, permissions, and retries behind a small interface. That gives you a seam for a fake in tests and a place to record the facts that matter in production. For a Flutter app, the boundary usually looks like this: The widget emits an intent such as load, submit, refresh, or retry. A controller or use case validates the intent ...

Keyboard shortcuts and pointer support for tablets

Tablets and desktops need keyboard-first paths. This article turns that observation into a small implementation model you can test, measure, and keep boring when the app grows. The problem in one sentence Shortcuts widget, hover, and trackpad scrolling. The useful question is not whether the API or pattern looks elegant in isolation. It is where state lives, which boundary owns failure, and how a user recovers when the happy path disappears. A practical model Add Shortcuts/Actions for primary flows. Start with one explicit owner for the behavior. Keep widgets responsible for rendering and user intent; keep IO, persistence, permissions, and retries behind a small interface. That gives you a seam for a fake in tests and a place to record the facts that matter in production. For a Flutter app, the boundary usually looks like this: The widget emits an intent such as load, submit, refresh, or retry. A controller or use case validates the intent and calls the boundary. The boundar...