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

Bài đăng

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 ...
Các bài đăng gần đây

Bí mật trong ứng dụng Flutter: cái gì lưu được, cái gì thì không

Hãy bắt đầu từ phần khó chịu, vì mọi thứ còn lại đều suy ra từ đó. Bất kỳ chuỗi nào được biên dịch vào ứng dụng đều là công khai. Không phải “khó tìm” — mà là công khai. Một file APK là file zip; chạy strings trên binary đã giải nén chỉ mất vài giây. Giá trị --dart-define , hằng số, tên đã làm rối, khối base64: tất cả đều lấy lại được bởi bất kỳ ai đủ quyết tâm tải ứng dụng của bạn về một lần. Đây không phải điểm yếu của Flutter. Điều đó đúng với mọi ứng dụng phía client trên mọi nền tảng. Thứ nó thay đổi là chỗ bạn kẻ ranh giới giữa “ứng dụng biết điều này” và “ứng dụng có thể hỏi xin điều này”. Ranh giới Loại bí mật Thuộc về đâu Khoá API bên thứ ba gắn với hoá đơn Chỉ ở máy chủ. Ứng dụng gọi backend của bạn, backend gọi họ Khoá công khai (Stripe publishable, cấu hình Firebase, khoá Maps) Nằm trong ứng dụng — chúng được thiết kế cho việc đó và được giới hạn phía máy chủ Token phiên của người dùng Kho an toàn trên thiết bị, vòng đời ngắn, làm mới ...

Secure storage and biometrics for Flutter tokens — hướng dẫn Flutter

Refresh tokens in SharedPreferences are a security incident waiting to happen. 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 Keychain/Keystore, biometric gates, and token rotation. 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ế Use platform keystores and optional biometric unlock for sensitive vaults. 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ề...

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

Secrets in a Flutter app: what you can store, and what you cannot

Start with the uncomfortable part, because everything else follows from it. Any string compiled into your app is public. Not “hard to find” — public. An APK is a zip file; strings on the extracted binary takes seconds. --dart-define values, constants, obfuscated names, base64-encoded blobs: all of it is recoverable by anyone motivated enough to download your app once. This is not a Flutter weakness. It is true of every client application on every platform. What it changes is where you draw the line between “the app knows this” and “the app can ask for this”. The line Kind of secret Where it belongs Third-party API key with billing attached Server only. The app calls your backend, your backend calls them Public/publishable keys (Stripe publishable, Firebase config, Maps key) In the app — they are designed for it, and restricted server-side User session token Device secure storage, short-lived, refreshable Refresh token Device secure storage, revoc...

Secure storage and biometrics for Flutter tokens

Refresh tokens in SharedPreferences are a security incident waiting to happen. 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 Keychain/Keystore, biometric gates, and token rotation. 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 Use platform keystores and optional biometric unlock for sensitive vaults. 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 controll...

Storing tokens with Secure Enclave / StrongBox — hướng dẫn Flutter

Hardware-backed keys raise the bar for malware theft. 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 Key generation, access control, and fallbacks. 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ế Use biometric-gated keys for high-value secrets. 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àn...

Redacting secrets from mobile logs and crash reports — hướng dẫn Flutter

Logs leave the device. Treat them as a data export path. 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 to scrub before the device leaves the pocket. 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ế Centralize redaction in a logging wrapper. 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à...

SEA market expansion checklist for mobile products — hướng dẫn Flutter

SEA is not one market. Device and payment profiles differ. 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 Languages, payments, and device profiles by country. 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ế Pilot one country before regional rollout. 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...

Storing tokens with Secure Enclave / StrongBox

Hardware-backed keys raise the bar for malware theft. 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 Key generation, access control, and fallbacks. 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 Use biometric-gated keys for high-value secrets. 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. ...

Redacting secrets from mobile logs and crash reports

Logs leave the device. Treat them as a data export path. 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 to scrub before the device leaves the pocket. 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 Centralize redaction in a logging wrapper. 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....

SEA market expansion checklist for mobile products

SEA is not one market. Device and payment profiles differ. 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 Languages, payments, and device profiles by country. 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 Pilot one country before regional rollout. 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...

Building store screenshots that convert — hướng dẫn Flutter

Screenshot 1 is your billboard. Lead with the benefit, not the logo. 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 Narrative order, captions, and device frames for 2026 stores. 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 captions under seven words at thumbnail size. 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...

flutter_rust_bridge for heavy compute in Flutter — hướng dẫn Flutter

Some loops belong outside Dart isolates. Rust + FFI is a proven path. 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 Porting hot paths to Rust without rewriting the UI. 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 the bridge thin and the data model shared. 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ôn...

RTL layouts that do not look broken in Flutter — hướng dẫn Flutter

Half the world reads right-to-left. Mirroring is not optional. 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 Directionality, icons, and mirrored gestures. 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ế Use start/end, not left/right, in padding and alignment. 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...

Building store screenshots that convert

Screenshot 1 is your billboard. Lead with the benefit, not the logo. 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 Narrative order, captions, and device frames for 2026 stores. 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 captions under seven words at thumbnail size. 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...