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

Bài đăng

Running a remote mobile team across timezones — hướng dẫn Flutter

Mobile release windows are unforgiving for async teams. 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 Release ownership, design pairing, and incident follow-the-sun. 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ế Rotate release captains; document runbooks. 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ả ...
Các bài đăng gần đây

Public release windows: when your Flutter PR ships — hướng dẫn Flutter

Published cutoffs replace fuzzy open-source planning. 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 Branch cutoffs for 3.41/3.44/3.47/3.50 so teams can plan upgrades. 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ế Plugin authors should align breaking changes with cutoffs, not sprint ends. 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...

Mobile rollback playbooks when stores are slow — hướng dẫn Flutter

You cannot instant-rollback a store binary. Design for server disable. 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 Remote kill, staged rollout, and server-side disable. 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ế Staged rollout + kill switch is the real rollback plan. 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ó...

Running a remote mobile team across timezones

Mobile release windows are unforgiving for async teams. 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 Release ownership, design pairing, and incident follow-the-sun. 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 Rotate release captains; document runbooks. 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 ...

Public release windows: when your Flutter PR ships

Published cutoffs replace fuzzy open-source planning. 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 Branch cutoffs for 3.41/3.44/3.47/3.50 so teams can plan upgrades. 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 Plugin authors should align breaking changes with cutoffs, not sprint ends. 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 cas...

Mobile rollback playbooks when stores are slow

You cannot instant-rollback a store binary. Design for server disable. 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 Remote kill, staged rollout, and server-side disable. 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 Staged rollout + kill switch is the real rollback plan. 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 ...

A mobile release checklist that prevents Friday fires — hướng dẫn Flutter

Releases are projects. Checklists beat heroics. 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 Store assets, crash-free gates, and rollback plans. 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ế Gate on crash-free sessions, review screenshots, and analytics events. 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ô...

Refund handling that protects ratings — hướng dẫn Flutter

Refund experiences decide future reviews. 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 Proactive credits, store flows, and support macros. 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ế Make support one tap from the paywall error state. 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...

prefers-reduced-motion and Flutter animations — hướng dẫn Flutter

Some users get sick from motion. Honor the platform setting. 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 MediaQuery disableAnimations and designing calm alternatives. 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ế Swap parallax and large transitions for opacity fades. 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...

A mobile release checklist that prevents Friday fires

Releases are projects. Checklists beat heroics. 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 Store assets, crash-free gates, and rollback plans. 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 Gate on crash-free sessions, review screenshots, and analytics events. 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...

Refund handling that protects ratings

Refund experiences decide future reviews. 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 Proactive credits, store flows, and support macros. 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 Make support one tap from the paywall error state. 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 ...

prefers-reduced-motion and Flutter animations

Some users get sick from motion. Honor the platform setting. 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 MediaQuery disableAnimations and designing calm alternatives. 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 Swap parallax and large transitions for opacity fades. 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 int...

RAG on mobile: what belongs on-device vs server — hướng dẫn Flutter

RAG is not only a server pattern. Small corpora can live on-device. 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 Embeddings, sync, and battery-aware retrieval. 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ế Sync embeddings incrementally; do not rebuild indexes every launch. 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 ...

Notification permission primers that earn the grant — hướng dẫn Flutter

Cold-requesting notifications on first launch loses the grant forever. 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 Timing, copy, and pre-permission education screens. 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ế Ask after a clear 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ó kiểu, không trả string ch...

Push notification trong Flutter: bốn trạng thái ứng dụng có thể đang ở

Thông báo tới nơi. Lúc thì ứng dụng hiển thị nó, lúc thì hệ điều hành, lúc chạm vào thì mở đúng màn hình còn lúc lại quăng người dùng ra trang chủ. Hành vi trông như ngẫu nhiên cho tới khi bạn nhìn ra cấu trúc bên dưới: cùng một tin nhắn đi theo bốn đường khác nhau tuỳ theo lúc đó ứng dụng đang làm gì. Trạng thái ứng dụng Ai hiển thị Handler nào chạy Chạy nổi Mặc định là không ai onMessage Chạy nền Hệ điều hành onBackgroundMessage (isolate riêng) Đã tắt hẳn Hệ điều hành onBackgroundMessage (isolate riêng) Được mở bằng cách chạm — onMessageOpenedApp , hoặc getInitialMessage nếu trước đó đã tắt hẳn Mọi lỗi thông báo tôi từng gỡ đều là một trong các dòng đó chưa được xử lý. Nối đủ bốn dòng thì phần lớn sự bí ẩn biến mất. Notification message so với data message Trước khi vào mã, đây là phân biệt quyết định mọi thứ: một payload FCM có thể chứa khối notification , khối data , hoặc cả hai. Có notification — hệ điều hành tự hiển thị khi ứng dụng chạy nề...

RAG on mobile: what belongs on-device vs server

RAG is not only a server pattern. Small corpora can live on-device. 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 Embeddings, sync, and battery-aware retrieval. 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 Sync embeddings incrementally; do not rebuild indexes every launch. 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...

Notification permission primers that earn the grant

Cold-requesting notifications on first launch loses the grant forever. 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 Timing, copy, and pre-permission education screens. 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 Ask after a clear value moment. 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 bound...

Push notifications in Flutter: the four states your app can be in

The notification arrives. Sometimes the app shows it, sometimes the system does, sometimes tapping it opens the right screen and sometimes it dumps the user on the home page. The behaviour feels random until you see the structure underneath: the same message takes four different paths depending on what the app was doing. App state Who displays it Which handler runs Foreground Nobody, by default onMessage Background The OS onBackgroundMessage (separate isolate) Terminated The OS onBackgroundMessage (separate isolate) Opened by tapping — onMessageOpenedApp , or getInitialMessage if it was terminated Every notification bug I have debugged was one of those rows being unhandled. Wire all four and most of the mystery disappears. Notification messages versus data messages Before the code, the distinction that determines everything: an FCM payload can contain a notification block, a data block, or both. notification present — the OS displays it automa...