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

Bài đăng

Hiển thị các bài đăng có nhãn Dart

Equatable: value semantics cho state object Dart

Equatable: value semantics cho state object Dart equatable đáng chú ý vì nó giải quyết equality, props, sealed state và kiểm tra rebuild dễ đoán. Bài này trình bày cách đặt package vào đúng boundary của một ứng dụng Flutter production thay vì chỉ copy một snippet vào màn hình. Package này sở hữu phần nào? Giữ API của package ở một adapter hoặc feature boundary có tên rõ ràng. Đưa lifecycle, lỗi và trạng thái loading vào contract mà UI có thể kiểm thử. Chỉ expose capability cần thiết; đừng để toàn app phụ thuộc vào chi tiết triển khai. Snippet bắt đầu class UserLoaded extends Equatable { const UserLoaded ( this .user); final User user; @override List < Object ?> get props => [user.id, user.updatedAt]; } Checklist trước khi ship Đọc README và changelog của package đúng version đang pin. Viết một test cho lifecycle, lỗi và hành vi khi app về background. Kiểm tra Android, iOS, web hoặc desktop đúng platform mà app hỗ trợ. Ghi lại owner, upgra...

Equatable: value semantics for Dart state objects

Equatable: value semantics for Dart state objects equatable is useful because it focuses on equality, props, sealed states, and predictable rebuild checks. The important engineering move is to place the package behind a clear boundary, so your Flutter UI depends on a stable capability rather than a vendor-shaped API. What the library should own Keep package calls inside a named adapter or feature boundary. Make lifecycle, errors, and loading state part of a testable contract. Expose only the capability the app needs; hide implementation details from the whole tree. A focused starting point class UserLoaded extends Equatable { const UserLoaded ( this .user); final User user; @override List < Object ?> get props => [user.id, user.updatedAt]; } Production checklist Read the README and changelog for the exact version you pin. Add one test for lifecycle, failure, and app background/foreground behavior. Verify every platform your product support...

Switch expressions for cleaner Flutter mapping — hướng dẫn Flutter

Switch expressions make state-to-UI mapping readable and exhaustive. 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 Mapping enums and states to widgets without arrow soup. 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ế Pair with sealed classes for compile-time exhaustiveness. 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 ...

Switch expressions for cleaner Flutter mapping

Switch expressions make state-to-UI mapping readable and exhaustive. 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 Mapping enums and states to widgets without arrow soup. 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 Pair with sealed classes for compile-time exhaustiveness. 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 th...

Serverpod: one language from SQL to Flutter widgets — hướng dẫn Flutter

Serverpod removes JSON drift by generating typed Flutter clients from server methods. 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 Generated clients, auth, and why Flutter-first teams pick Dart backends. 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ế Hiring and CI stay in one toolchain. 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 ...

Serverpod: one language from SQL to Flutter widgets

Serverpod removes JSON drift by generating typed Flutter clients from server methods. 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 Generated clients, auth, and why Flutter-first teams pick Dart backends. 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 Hiring and CI stay in one toolchain. 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...

Dot shorthands in Flutter: cleaner enum and const syntax — hướng dẫn Flutter

Dot shorthands cut repetition when writing enum values and consts in UI trees. 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 Why the new Dart syntax makes Flutter UI code less noisy. 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ế Less noise in builders means easier review diffs. 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 fail...

Dot shorthands in Flutter: cleaner enum and const syntax

Dot shorthands cut repetition when writing enum values and consts in UI trees. 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 Why the new Dart syntax makes Flutter UI code less noisy. 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 Less noise in builders means easier review diffs. 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 validate...

Dart primary constructors in Flutter model code — hướng dẫn Flutter

Primary constructors shrink DTO and state class boilerplate. 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 Less boilerplate for immutable UI models and DTOs. 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 validation logic in factories, not the constructor soup. 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, ...

Dart primary constructors in Flutter model code

Primary constructors shrink DTO and state class boilerplate. 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 Less boilerplate for immutable UI models and DTOs. 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 validation logic in factories, not the constructor soup. 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 ...

Codegen that pays for itself: freezed and json_serializable — hướng dẫn Flutter

Hand-written JSON parsers rot. Codegen keeps models honest with the API. 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 Immutable models, unions, and less hand-written parse code. 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ế Unions model sealed UI and network states cleanly. 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...

Codegen that pays for itself: freezed and json_serializable

Hand-written JSON parsers rot. Codegen keeps models honest with the API. 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 Immutable models, unions, and less hand-written parse code. 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 Unions model sealed UI and network states cleanly. 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 t...

Equatable: value semantics for Dart state objects

Equatable: value semantics for Dart state objects equatable is useful because it focuses on equality, props, sealed states, and predictable rebuild checks. The important engineering move is to place the package behind a clear boundary, so your Flutter UI depends on a stable capability rather than a vendor-shaped API. What the library should own Keep package calls inside a named adapter or feature boundary. Make lifecycle, errors, and loading state part of a testable contract. Expose only the capability the app needs; hide implementation details from the whole tree. A focused starting point class UserLoaded extends Equatable { const UserLoaded ( this .user); final User user; @override List < Object ?> get props => [user.id, user.updatedAt]; } Production checklist Read the README and changelog for the exact version you pin. Add one test for lifecycle, failure, and app background/foreground behavior. Verify every platform your product support...

Switch expressions for cleaner Flutter mapping

Switch expressions make state-to-UI mapping readable and exhaustive. 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 Mapping enums and states to widgets without arrow soup. 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 Pair with sealed classes for compile-time exhaustiveness. 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 th...

Serverpod: one language from SQL to Flutter widgets

Serverpod removes JSON drift by generating typed Flutter clients from server methods. 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 Generated clients, auth, and why Flutter-first teams pick Dart backends. 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 Hiring and CI stay in one toolchain. 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...

Tiêm phụ thuộc trong Flutter mà không cần nghi thức rườm rà

Tiêm phụ thuộc có một cái tên đáng sợ cho một ý tưởng hết sức bình thường: một lớp nên được trao thứ nó cần thay vì tự dựng hoặc tự đi tìm. Toàn bộ khái niệm chỉ có vậy. Mọi thứ còn lại — container, locator, provider, mã sinh tự động — chỉ là bộ máy để giao hàng. Lý do đáng quan tâm là kiểm thử. Viết ApiClient() bên trong một repository nghĩa là mọi test của repository đó đều gọi mạng thật. Truyền ApiClient vào nghĩa là mọi test đều truyền được bản giả. Đó là toàn bộ phần thưởng, và thế là đủ. Tiêm qua constructor, không cần gói nào final class UserRepository { const UserRepository ( this ._api, this ._cache); final ApiClient _api; final UserCache _cache; Future < User > fetch ( String id) async { final cached = _cache. get (id); if (cached != null ) return cached; final user = await _api. getUser (id); _cache. put (user); return user; } } Kiểm thử nó không cần framework nào: test ( 'trả về user từ cache mà...

Dot shorthands in Flutter: cleaner enum and const syntax — hướng dẫn Flutter

Dot shorthands cut repetition when writing enum values and consts in UI trees. 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 Why the new Dart syntax makes Flutter UI code less noisy. 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ế Less noise in builders means easier review diffs. 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 fail...

Dart primary constructors in Flutter model code — hướng dẫn Flutter

Primary constructors shrink DTO and state class boilerplate. 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 Less boilerplate for immutable UI models and DTOs. 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 validation logic in factories, not the constructor soup. 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, ...

Codegen that pays for itself: freezed and json_serializable — hướng dẫn Flutter

Hand-written JSON parsers rot. Codegen keeps models honest with the API. 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 Immutable models, unions, and less hand-written parse code. 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ế Unions model sealed UI and network states cleanly. 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...

Stream trong Dart chuyên sâu: backpressure, broadcast và những rò rỉ ở giữa

Bad state: Stream has already been listened to. là lỗi đẩy phần lớn mọi người lên Stack Overflow, và câu trả lời họ tìm thấy — “dùng .asBroadcastStream() ” — thường là cách sửa sai. Nó làm lỗi biến mất và âm thầm thay đổi ngữ nghĩa phân phối dữ liệu của bạn. Hai loại stream, và vì sao mặc định là loại nghiêm ngặt Stream đơn đăng ký chỉ được nghe đúng một lần. Nó đệm sự kiện cho tới khi có listener, và giao mọi sự kiện cho listener đó. Đọc file, thân phản hồi HTTP và các hàm async* đều sinh ra loại này. Stream broadcast có bao nhiêu listener cũng được, chỉ giao sự kiện cho những listener đang đăng ký, và không đệm gì cả. Listener tới muộn thì đơn giản là lỡ mất những gì đã qua. final single = StreamController < int >(); // đơn đăng ký final bus = StreamController < int >. broadcast (); Cụm “không đệm gì cả” là mấu chốt. Chuyển một stream đơn đăng ký sang broadcast để làm im lỗi nghĩa là các sự kiện phát ra trước khi listener thứ hai gắn vào sẽ mất — kh...