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

Bài đăng

Mobile OKRs that engineering can influence — hướng dẫn Flutter

OKRs fail when eng cannot move the number. 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 Session quality, task success, and crash-free as shared goals. 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 product metrics with reliability metrics. 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ỉ ...
Các bài đăng gần đây

Sync conflict resolution patterns for mobile apps — hướng dẫn Flutter

Conflicts are inevitable offline. Decide the rule before users find it. 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 Last-write-wins, CRDTs, and when to ask the user. 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 the user only when data loss is real. 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ả ...

Graceful degradation when AI is unavailable — hướng dẫn Flutter

AI providers fail. Your app should not. 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 Feature flags, local heuristics, and honest empty states. 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 non-AI path for critical 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. U...

Mobile OKRs that engineering can influence

OKRs fail when eng cannot move the number. 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 Session quality, task success, and crash-free as shared goals. 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 product metrics with reliability metrics. 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 boundar...

Sync conflict resolution patterns for mobile apps

Conflicts are inevitable offline. Decide the rule before users find it. 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 Last-write-wins, CRDTs, and when to ask the user. 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 the user only when data loss is real. 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 ...

Graceful degradation when AI is unavailable

AI providers fail. Your app should not. 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 Feature flags, local heuristics, and honest empty states. 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 non-AI path for critical 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 boundary...

Mobile observability: Sentry, Crashlytics, and custom traces — hướng dẫn Flutter

Crashes without release context are archaeology. Observability is product hygiene. 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 Correlate crashes with performance and releases. 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ế Tag traces with user flows and feature flags. 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...

Mobile observability cost control — hướng dẫn Flutter

Telemetry can cost more than hosting if left unchecked. 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 Sampling, cardinality, and what to keep forever. 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ế Sample traces; keep 100% of errors. 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...

Obfuscation, symbols, and secrets in Flutter release builds — hướng dẫn Flutter

Release builds need size, symbols, and secret hygiene in one checklist. 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 Split-debug-info, tree-shake icons, and never ship API keys in Dart. 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ế Server-side keys belong on a backend, not in the bundle. 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ể...

Mobile observability: Sentry, Crashlytics, and custom traces

Crashes without release context are archaeology. Observability is product hygiene. 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 Correlate crashes with performance and releases. 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 Tag traces with user flows and feature flags. 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...

Mobile observability cost control

Telemetry can cost more than hosting if left unchecked. 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 Sampling, cardinality, and what to keep forever. 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 Sample traces; keep 100% of errors. 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 boun...

Obfuscation, symbols, and secrets in Flutter release builds

Release builds need size, symbols, and secret hygiene in one checklist. 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 Split-debug-info, tree-shake icons, and never ship API keys in Dart. 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 Server-side keys belong on a backend, not in the bundle. 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 ca...

State restoration across process death — hướng dẫn Flutter

Android kills processes constantly. Restoration keeps users in context. 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 RestorationMixin, restorationIds, and why scroll position matters. 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ế Mark every important widget with restoration IDs. 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 fa...

go_router và deep link: những phần mà bài quickstart bỏ qua

Mọi bài hướng dẫn điều hướng đều kết thúc ở cùng một chỗ: một GoRouter với ba route, một context.go('/details/42') , và ảnh chụp màn hình cho thấy nó chạy. Phần đó mất mười phút. Những phần ngốn hết phần còn lại của tuần thì chẳng ai demo — một redirect kiểm tra đăng nhập không đánh nhau với màn hình login, một bottom navigation bar mà mỗi tab giữ lịch sử riêng, và phần cấu hình nền tảng quyết định xem https://yourapp.com/order/7 sẽ mở app của bạn hay mở Safari. Bảng route, và state thật sự nằm ở đâu Bắt đầu bằng hình dạng có thể lớn lên được, tức một đối tượng router ở cấp cao nhất và không bị dựng lại: final _rootKey = GlobalKey < NavigatorState >(); final _shellKey = GlobalKey < NavigatorState >(); final router = GoRouter ( navigatorKey : _rootKey, initialLocation : '/feed' , debugLogDiagnostics : true , routes : [ /* ... */ ], errorBuilder : (context, state) => NotFoundScreen (uri : state.uri), ); class App e...

Multiple Flutter instances in one host app — hướng dẫn Flutter

Some hosts need two Flutter surfaces. Engines are not free. 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 Memory cost, engines, and when a single engine is enough. 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 FlutterEngineGroup to share resources. 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ả ...

State restoration across process death

Android kills processes constantly. Restoration keeps users in context. 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 RestorationMixin, restorationIds, and why scroll position matters. 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 Mark every important widget with restoration IDs. 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 valida...

go_router and deep links: the parts the quickstart leaves out

Every routing tutorial ends at the same place: a GoRouter with three routes, a context.go('/details/42') , and a screenshot of it working. That part takes ten minutes. The parts that take the rest of the week are the ones nobody demos — an auth redirect that does not fight the login screen, a bottom navigation bar where each tab keeps its own history, and the platform configuration that decides whether https://yourapp.com/order/7 opens your app or Safari. The routing table, and where state actually lives Start with the shape that scales, which is a top-level router object that is not rebuilt: final _rootKey = GlobalKey < NavigatorState >(); final _shellKey = GlobalKey < NavigatorState >(); final router = GoRouter ( navigatorKey : _rootKey, initialLocation : '/feed' , debugLogDiagnostics : true , routes : [ /* ... */ ], errorBuilder : (context, state) => NotFoundScreen (uri : state.uri), ); class App extends Stat...