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

Bài đăng

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

When Flutter is wrong: choosing native iOS or Android — hướng dẫn Flutter

Flutter is not always right. Platform-only APIs, team skills, and UX purity matter. 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 A decision framework for teams that feel FOMO from both ecosystems. 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ế Hybrid (native shell + Flutter modules) is a valid middle path. 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 ...

When Flutter is wrong: choosing native iOS or Android

Flutter is not always right. Platform-only APIs, team skills, and UX purity matter. 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 A decision framework for teams that feel FOMO from both ecosystems. 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 Hybrid (native shell + Flutter modules) is a valid middle path. 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 co...

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

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

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

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

True cost of cross-platform in 2026 — hướng dẫn Flutter

Headcount and time-to-market dominate framework ROI. 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 Flutter wins, when native wins, and the hybrid middle. 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ế Measure feature parity lag, not lines of code. 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ả stri...

True cost of cross-platform in 2026

Headcount and time-to-market dominate framework ROI. 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 Flutter wins, when native wins, and the hybrid middle. 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 Measure feature parity lag, not lines of code. 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 ...

Passkeys and passwordless login in mobile apps — hướng dẫn Flutter

Passwords leak. Passkeys raise security and conversion. 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 ASAuthorization, Credential Manager, and account recovery UX. 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ế Always keep a recovery path when going passwordless. 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ô...

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

Mua hàng trong ứng dụng Flutter: những phần không nằm ở cái nút

Phiên bản trong bài hướng dẫn của việc mua hàng trong ứng dụng là một cái nút, một lời gọi buyNonConsumable , và một callback thành công đặt isPremium = true . Phát hành thứ đó rồi bạn sẽ lần lượt phát hiện: người dùng cài lại thì mất sạch, một giao dịch bị cuộc gọi cắt ngang thì không bao giờ hoàn tất, người đã hoàn tiền vẫn giữ quyền truy cập mãi mãi, và bất kỳ ai có bản build đã sửa đều mở khoá ứng dụng miễn phí. Không cái nào trong số đó là trường hợp biên. Chúng là điều kiện vận hành bình thường của một hệ thống thanh toán. Stream mới là API in_app_purchase không hoạt động theo kiểu hỏi-đáp. Giao dịch đến trên một stream, bao gồm cả giao dịch bạn không khởi tạo trong phiên này — các lần khôi phục, giao dịch hoàn tất khi ứng dụng đã đóng, và giao dịch từ thiết bị khác cùng tài khoản. final class PurchaseService { PurchaseService ( this ._iap, this ._backend); final InAppPurchase _iap; final BackendApi _backend; StreamSubscription < List < Purchase...