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

Bài đăng

Escaping setState spaghetti with clear ownership — hướng dẫn Flutter

setState is fine for ephemeral UI. App state needs a real owner. 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 Local UI state vs app state and where Bloc/Riverpod fit. 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ế Name the state owner before adding a package. 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 ...

CarPlay and Android Auto with mobile companion apps — hướng dẫn Flutter

Car UIs have strict distraction guidelines. 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 belongs on the car screen and what does not. 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ế Glanceable actions only; no text entry while driving. 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 ...

Certificate pinning and network security on mobile — hướng dẫn Flutter

Pinning reduces MITM risk but causes outages if rotated wrong. 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 When pinning helps and when it breaks you. 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ế Prefer backup pins and short-lived leaves. 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...

Escaping setState spaghetti with clear ownership

setState is fine for ephemeral UI. App state needs a real owner. 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 Local UI state vs app state and where Bloc/Riverpod fit. 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 Name the state owner before adding a package. 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 ca...

CarPlay and Android Auto with mobile companion apps

Car UIs have strict distraction guidelines. 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 belongs on the car screen and what does not. 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 Glanceable actions only; no text entry while driving. 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. T...

Certificate pinning and network security on mobile

Pinning reduces MITM risk but causes outages if rotated wrong. 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 When pinning helps and when it breaks you. 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 Prefer backup pins and short-lived leaves. 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. ...

Maps, location, and permission UX in Flutter apps — hướng dẫn Flutter

Permission copy and timing decide grant rates more than the API does. 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 Foreground vs background location and privacy nutrition labels. 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ế Request at the moment of need, with a clear benefit sentence. 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 ...

Logistics tracking UX under poor connectivity — hướng dẫn Flutter

Logistics is an offline problem wearing a map costume. 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 Location batching, offline maps, and ETA honesty. 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ế Batch GPS and show sync state clearly. 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 ...

Pseudolocales and expansion testing before translators — hướng dẫn Flutter

German and Vietnamese expand. Arabic flips. Test before paying translators. 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 Finding truncation bugs without a full translation set. 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ế Pseudolocales catch overflow for free. 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ô...

Đa ngôn ngữ trong Flutter với file ARB: số nhiều, giống, và những chỗ cắn người

Commit đa ngôn ngữ đầu tiên trong đa số ứng dụng Flutter là một Map<String, String> đánh khoá theo mã ngôn ngữ, tra qua một biến toàn cục. Nó chạy được với hai ngôn ngữ và một trăm chuỗi. Nó vỡ ngay lần đầu ai đó cần “1 item” so với “2 items”, và vỡ nặng ngay lần đầu một người dịch hỏi home_screen_label_2 là để làm gì. Câu trả lời chính thức của Flutter là file ARB được gen_l10n biên dịch thành một class Dart sinh tự động. Phần cài đặt rất ngắn. Thứ đáng hiểu là những phần mà bài quickstart không nhắc: plural theo ICU, kiểu của placeholder, cách phân giải locale, và chuyện gì xảy ra vào ngày có người thêm tiếng Ả Rập. Cài đặt, một lần # pubspec.yaml dependencies : flutter_localizations : sdk : flutter intl : any flutter : generate : true # l10n.yaml ở thư mục gốc dự án arb-dir : lib/l10n template-arb-file : app_en.arb output-localization-file : app_localizations.dart nullable-getter : false nullable-getter: false đáng để đặt một cách có chủ ý. Với nó...

Local notifications that do not spam or break on Android 13+ — hướng dẫn Flutter

Notifications are a retention channel and a deletion risk. 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 Channels, permissions, and scheduled reliability. 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ế Create channels with clear purpose; request permission at value moment. 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ó...

Marketing on-device AI without lying — hướng dẫn Flutter

Privacy marketing is regulated and scrutinized. 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 “on-device” must mean for privacy claims. 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ế Be precise about what leaves the device. 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. UI...

Vì sao ListView của bạn chậm, và bốn cách sửa thật sự có tác dụng

Đội Flutter nào cũng gặp chuyện này. Danh sách chạy ngon với hai mươi mục lúc phát triển, phát hành, rồi một người dùng có tám trăm mục đã lưu báo rằng cuộn bị khựng và ứng dụng cảm giác nặng nề. Phản xạ đầu tiên là đổ lỗi cho ListView , cho Flutter, hoặc cho cái máy. ListView không có lỗi. Trong mọi danh sách chậm tôi từng phân tích, nguyên nhân là một trong bốn thứ, và phân biệt chúng mất khoảng mười phút. Trước tiên: xem luồng nào đang trễ Trước khi đổi bất cứ thứ gì, hãy chạy ở chế độ profile trên thiết bị thật và mở performance view của DevTools. Mỗi frame được vẽ thành hai thanh: Luồng UI dài → build và layout đang đắt. itemBuilder của bạn làm quá nhiều việc. Luồng raster dài → vẽ đang đắt. Bóng đổ, làm mờ, lớp opacity, saveLayer, ảnh lớn. Phân biệt đó loại bỏ ngay một nửa số cách sửa khả dĩ. Thêm RepaintBoundary vào một danh sách mà nút thắt nằm ở luồng UI chẳng có tác dụng gì; đơn giản hoá cấu trúc widget trong một danh sách mà luồng raster đang bão hoà vì Backdro...

Designing for Liquid Glass and Material without dual UIs — hướng dẫn Flutter

Platform design languages keep shifting. Adaptive wrappers absorb the shock. 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 Adaptive chrome, materials, and platform-adaptive widgets. 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ế Share business UI; swap chrome by platform. 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ó ...

Key trong Flutter: một quy tắc duy nhất giải thích mọi trường hợp

Có một con bug rất cụ thể dạy cho tất cả mọi người về key. Bạn có một danh sách các dòng có trạng thái — mỗi dòng một checkbox, hoặc một expansion tile, hoặc một ô nhập liệu. Bạn xóa dòng thứ hai. Dòng đó biến mất đúng như mong đợi, nhưng dấu tích vốn thuộc về nó giờ lại nằm trên một dòng khác. Dữ liệu chẳng có gì sai. Tải lại trang thì mọi thứ bình thường. Đó không phải lỗi của Flutter. Đó là framework làm đúng những gì được bảo, và cách sửa chỉ dài một từ. Nhưng cách sửa chỉ thật sự dính lại nếu bạn hiểu quy tắc bên dưới, vì cùng quy tắc đó giải thích vì sao đôi khi key chẳng làm gì cả, vì sao GlobalKey đắt, và vì sao PageStorageKey không thật sự là key theo cùng nghĩa. Ba cây, và cái cây giữ state của bạn Flutter duy trì ba cấu trúc song song. Cây widget là kết quả build của bạn: các đối tượng cấu hình bất biến, bị vứt đi và tạo lại liên tục. Cây render lo layout và vẽ. Nằm giữa hai cái đó là cây element , và đây mới là cái quan trọng ở bài này, vì một Element sống lâu và ...