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

Bài đăng

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

Clean architecture in Flutter without ceremony — hướng dẫn Flutter

Architecture exists to change code safely, not to impress interviewers. 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 layers help and when they are cargo cult. 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ế Feature-first folders beat layer-only mega folders at scale. 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ó ...

Clean architecture in Flutter without ceremony

Architecture exists to change code safely, not to impress interviewers. 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 layers help and when they are cargo cult. 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 Feature-first folders beat layer-only mega folders at scale. 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 i...

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 ...

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...

ADRs for mobile architecture decisions — hướng dẫn Flutter

ADRs explain why the code looks like this six months later. 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 One page per irreversible choice, with context. 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 them next to the code, not in a wiki graveyard. 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ả str...

ADRs for mobile architecture decisions

ADRs explain why the code looks like this six months later. 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 One page per irreversible choice, with context. 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 them next to the code, not in a wiki graveyard. 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...

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à...

Clean architecture in Flutter without ceremony — hướng dẫn Flutter

Architecture exists to change code safely, not to impress interviewers. 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 layers help and when they are cargo cult. 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ế Feature-first folders beat layer-only mega folders at scale. 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ó ...

Chạy nền trong Flutter: hệ điều hành thật sự cho phép bạn chạy gì

“Đồng bộ dữ liệu người dùng mười lăm phút một lần” nghe như bài toán lập lịch. Thực ra đó là cuộc thương lượng với hai hệ điều hành đã dành cả thập kỷ để giỏi hơn trong việc nói không. Trước khi chọn gói, đáng để nói chính xác từng nền tảng thật sự cho gì, vì khoảng cách giữa chúng quyết định tính năng của bạn được phép hứa điều gì. Bạn thật sự được phép làm gì Khả năng Android iOS Chạy nền định kỳ Có, tối thiểu ~15 phút, còn tuỳ Doze BGAppRefreshTask — hệ thống quyết định khi nào, có thể là không bao giờ Việc hoãn lại chạy một lần Có, kèm ràng buộc BGProcessingTask , thường vào ban đêm khi đang sạc Bảo đảm chạy khi ứng dụng đã đóng Chỉ qua foreground service kèm thông báo nhìn thấy được Không Việc chạy dài (nhiều phút) Foreground service Không — tác vụ nền có vài giây rồi bị đình chỉ Kích hoạt từ máy chủ Có, FCM mức ưu tiên cao Hạn chế; push ngầm bị bóp Dòng làm thay đổi thiết kế là dòng thứ ba. Trên iOS không có cách nào bảo đảm mã của bạn chạy...

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 ...

ADRs for mobile architecture decisions — hướng dẫn Flutter

ADRs explain why the code looks like this six months later. 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 One page per irreversible choice, with context. 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 them next to the code, not in a wiki graveyard. 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ả str...

Clean architecture in Flutter without ceremony

Architecture exists to change code safely, not to impress interviewers. 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 layers help and when they are cargo cult. 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 Feature-first folders beat layer-only mega folders at scale. 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 i...

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...

ADRs for mobile architecture decisions

ADRs explain why the code looks like this six months later. 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 One page per irreversible choice, with context. 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 them next to the code, not in a wiki graveyard. 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...

Định tuyến mô hình và thác bậc: chỉ trả tiền cho mức thông minh bạn cần

Hoá đơn về, và 90% trong đó là những yêu cầu kiểu “tóm tắt cái này trong một câu” hay “tin nhắn này có phải spam không?” — được gửi tới đúng cái model tiên tiến bạn dùng cho suy luận nhiều bước, chỉ vì đó là model bạn nối dây đầu tiên. Cách sửa nghe hiển nhiên: dùng model nhỏ ở chỗ model nhỏ là đủ . Cái khó nằm trọn trong chữ đủ , và trong việc biết yêu cầu nào là như vậy trước khi bạn có câu trả lời. Ba kiến trúc, không phải một Người ta nói “định tuyến” cho ba thứ khác nhau, và chúng có hồ sơ rủi ro khác nhau. Cách tiếp cận Quyết định thế nào Chi phí Rủi ro Định tuyến tĩnh Nhánh code quyết định model Ít phức tạp nhất Gán sai không lộ ra cho tới khi có phàn nàn chất lượng Định tuyến bằng phân loại Một model nhỏ hoặc heuristic chọn theo từng yêu cầu Thêm một lời gọi Phân loại sai đẩy việc khó cho model yếu Thác bậc Thử nhỏ trước, leo thang khi hỏng Đôi khi hai lời gọi đầy đủ Tiêu chí leo thang là toàn bộ bài toán Hãy bắt đầu bằng định tuyến tĩnh. Nó g...

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à...