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

Bài đăng

Serverpod: full-stack Dart phía sau Flutter

Serverpod là backend “không, bạn không cần học ba ngôn ngữ”: server Dart, client Flutter sinh tự động, tooling database kèm theo. Vì sao team chọn Type dùng chung hết lệch JSON. Auth và scaffolding database giảm lo trang trắng. Một toolchain cho hiring và CI. Cạm bẫy Lock-in full-stack là thật — giữ domain logic di động nếu có thể tách sau. Không để secret trong path code client sinh. Cân nhắc Serverpod khi team Flutter-first và muốn backend mạch lạc không đổi ngữ cảnh Node/Go. Bài viết gốc đăng tại FlutterCook . Bản trên đó là bản được cập nhật mới nhất.
Các bài đăng gần đây

Sentry Flutter: lỗi đi kèm context của release

Sentry Flutter: lỗi đi kèm context của release sentry_flutter đáng chú ý vì nó giải quyết crash report, trace hiệu năng, breadcrumb và source map. Bài này trình bày cách đặt package vào đúng boundary của một ứng dụng Flutter production thay vì chỉ copy một snippet vào màn hình. Package này sở hữu phần nào? Giữ API của package ở một adapter hoặc feature boundary có tên rõ ràng. Đưa lifecycle, lỗi và trạng thái loading vào contract mà UI có thể kiểm thử. Chỉ expose capability cần thiết; đừng để toàn app phụ thuộc vào chi tiết triển khai. Snippet bắt đầu await SentryFlutter . init ( (options) { options.dsn = const String . fromEnvironment ( 'SENTRY_DSN' ); options.tracesSampleRate = 0.1 ; options.environment = const String . fromEnvironment ( 'APP_ENV' ); }, appRunner : () => runApp ( const App ()), ); Checklist trước khi ship Đọc README và changelog của package đúng version đang pin. Viết một test cho lifecycle, lỗi và h...

Saber: ghi chú viết tay và file local-first

App viết tay là vấn đề sâu tới RenderObject: input nét, blend highlighter, export và định dạng file đều quan trọng. Bài học File local trước; sync sau. Note phải mở được offline. Model nét là data (điểm/áp lực), không phải screenshot. Composite highlighter hai lớp là bài học graphics cổ điển trong sản phẩm. Cạm bẫy Latency stylus cảm nhận được ở ~1 frame. Lock-in định dạng file làm user giận hơn thiếu cloud. Học Saber khi xây công cụ Flutter lõi canvas . Bài viết gốc đăng tại FlutterCook . Bản trên đó là bản được cập nhật mới nhất.

Serverpod: full-stack Dart behind Flutter

Serverpod is the “no, you may not learn three languages” backend: Dart server, generated Flutter clients, database tooling included. Why teams pick it Shared types end JSON drift. Auth and database scaffolding reduce blank-page anxiety. One toolchain for hiring and CI. Pitfalls Full-stack lock-in is real — keep domain logic portable if you might split later. Do not put secrets in client-generated code paths. Consider Serverpod when the team is Flutter-first and wants a coherent backend without Node/Go context switching. Originally published on FlutterCook . Read the latest version there — that copy is the one kept up to date.

Sentry Flutter: errors with release context

Sentry Flutter: errors with release context sentry_flutter is useful because it focuses on crash reporting, performance traces, breadcrumbs, and source maps. The important engineering move is to place the package behind a clear boundary, so your Flutter UI depends on a stable capability rather than a vendor-shaped API. What the library should own Keep package calls inside a named adapter or feature boundary. Make lifecycle, errors, and loading state part of a testable contract. Expose only the capability the app needs; hide implementation details from the whole tree. A focused starting point await SentryFlutter . init ( (options) { options.dsn = const String . fromEnvironment ( 'SENTRY_DSN' ); options.tracesSampleRate = 0.1 ; options.environment = const String . fromEnvironment ( 'APP_ENV' ); }, appRunner : () => runApp ( const App ()), ); Production checklist Read the README and changelog for the exact version you pin....

Saber: handwriting notes and local-first files

Handwriting apps are RenderObject-deep problems: stroke input, highlighter blend, export, and file format choices all matter. Lessons Local files first; sync second. Notes must open offline. Model strokes as data (points/pressure), not screenshots. Dual-layer highlighter compositing is a classic graphics lesson in a product. Pitfalls Stylus latency is perceptible at ~1 frame. File format lock-in will anger users more than missing cloud features. Study Saber when building canvas-centric Flutter tools. Originally published on FlutterCook . Read the latest version there — that copy is the one kept up to date.

RustDesk: lõi Rust, vỏ Flutter

Không phải mọi vấn đề pixel là vấn đề Dart. RustDesk giữ capture/encode/networking trong Rust và dùng Flutter làm shell tương tác — dạng hybrid bạn nên hiểu trước khi rewrite module native bằng Dart thuần. Phân chia trách nhiệm Mục Nơi Capture màn, codec, relay Lõi Rust UX kết nối, settings, session UI Flutter Tích hợp OS Platform channel / FFI Học theo Vẽ biên cứng: vòng UI nóng vs vòng data nóng. Ưu tiên FFI cho throughput; channel cho event thưa. Giữ session state ngoài cây widget. Cạm bẫy Build hybrid nhân đôi ma trận CI. ABI và packaging installer desktop không đơn giản. App nặng UI có thể chỉ cần Flutter. App nặng pipeline hãy copy biên giới của RustDesk, chưa chắc đã copy Rust. Bài viết gốc đăng tại FlutterCook . Bản trên đó là bản được cập nhật mới nhất.

Riverpod: state bất đồng bộ có thể test mà không dính widget

Riverpod: state bất đồng bộ có thể test mà không dính widget riverpod đáng chú ý vì nó giải quyết provider, trạng thái async, cache và override dependency. Bài này trình bày cách đặt package vào đúng boundary của một ứng dụng Flutter production thay vì chỉ copy một snippet vào màn hình. Package này sở hữu phần nào? Giữ API của package ở một adapter hoặc feature boundary có tên rõ ràng. Đưa lifecycle, lỗi và trạng thái loading vào contract mà UI có thể kiểm thử. Chỉ expose capability cần thiết; đừng để toàn app phụ thuộc vào chi tiết triển khai. Snippet bắt đầu final usersProvider = FutureProvider < List < User >>((ref) async { final api = ref. watch (apiProvider); return api. fetchUsers (); }); class UsersView extends ConsumerWidget { const UsersView ({ super .key}); @override Widget build ( BuildContext context, WidgetRef ref) => ref. watch (usersProvider). when ( data : (users) => UserList (users), ...

Rive: animation vector tương tác bằng state machine

Rive: animation vector tương tác bằng state machine rive đáng chú ý vì nó giải quyết artboard, state machine, input và hiệu năng runtime. Bài này trình bày cách đặt package vào đúng boundary của một ứng dụng Flutter production thay vì chỉ copy một snippet vào màn hình. Package này sở hữu phần nào? Giữ API của package ở một adapter hoặc feature boundary có tên rõ ràng. Đưa lifecycle, lỗi và trạng thái loading vào contract mà UI có thể kiểm thử. Chỉ expose capability cần thiết; đừng để toàn app phụ thuộc vào chi tiết triển khai. Snippet bắt đầu final controller = StateMachineController . fromArtboard ( artboard, 'LoginMachine' , ); if (controller != null ) { artboard. addController (controller); final success = controller. findInput < bool >( 'success' ); success ? .value = true ; } Checklist trước khi ship Đọc README và changelog của package đúng version đang pin. Viết một test cho lifecycle, lỗi và hành vi khi app về backgro...

RustDesk: Rust core, Flutter shell

Not every pixel problem is a Dart problem. RustDesk keeps capture/encode/networking in Rust and uses Flutter as the interactive shell — a hybrid you should understand before you rewrite a native module in pure Dart. Split of responsibilities Concern Home Screen capture, codecs, relay Rust core Connection UX, settings, session UI Flutter OS integration Platform channels / FFI Steal this Draw a hard boundary: hot UI loop vs hot data loop. Prefer FFI for throughput; channels for infrequent events. Keep session state outside widget trees. Pitfalls Hybrid builds double your CI matrix. ABI and packaging for desktop installers are non-trivial. If your app is UI-heavy, Flutter alone may suffice. If it is pipeline-heavy, copy RustDesk’s boundary , not necessarily Rust itself. Originally published on FlutterCook . Read the latest version there — that copy is the one kept up to date.

Riverpod: testable async state without widget coupling

Riverpod: testable async state without widget coupling riverpod is useful because it focuses on providers, async states, caching, and dependency overrides. The important engineering move is to place the package behind a clear boundary, so your Flutter UI depends on a stable capability rather than a vendor-shaped API. What the library should own Keep package calls inside a named adapter or feature boundary. Make lifecycle, errors, and loading state part of a testable contract. Expose only the capability the app needs; hide implementation details from the whole tree. A focused starting point final usersProvider = FutureProvider < List < User >>((ref) async { final api = ref. watch (apiProvider); return api. fetchUsers (); }); class UsersView extends ConsumerWidget { const UsersView ({ super .key}); @override Widget build ( BuildContext context, WidgetRef ref) => ref. watch (usersProvider). when ( data : (users) =...

Rive: interactive vector animation as a state machine

Rive: interactive vector animation as a state machine rive is useful because it focuses on artboards, state machines, inputs, and runtime performance. The important engineering move is to place the package behind a clear boundary, so your Flutter UI depends on a stable capability rather than a vendor-shaped API. What the library should own Keep package calls inside a named adapter or feature boundary. Make lifecycle, errors, and loading state part of a testable contract. Expose only the capability the app needs; hide implementation details from the whole tree. A focused starting point final controller = StateMachineController . fromArtboard ( artboard, 'LoginMachine' , ); if (controller != null ) { artboard. addController (controller); final success = controller. findInput < bool >( 'success' ); success ? .value = true ; } Production checklist Read the README and changelog for the exact version you pin. Add one test for life...

Pigeon: platform channel có kiểu sinh từ một schema

Pigeon: platform channel có kiểu sinh từ một schema pigeon đáng chú ý vì nó giải quyết codegen Dart/native, contract API, task queue và versioning. Bài này trình bày cách đặt package vào đúng boundary của một ứng dụng Flutter production thay vì chỉ copy một snippet vào màn hình. Package này sở hữu phần nào? Giữ API của package ở một adapter hoặc feature boundary có tên rõ ràng. Đưa lifecycle, lỗi và trạng thái loading vào contract mà UI có thể kiểm thử. Chỉ expose capability cần thiết; đừng để toàn app phụ thuộc vào chi tiết triển khai. Snippet bắt đầu @HostApi () abstract class SecureBiometrics { bool isAvailable (); @async bool authenticate ( String reason); } // dart run pigeon --input pigeons/biometrics.dart Checklist trước khi ship Đọc README và changelog của package đúng version đang pin. Viết một test cho lifecycle, lỗi và hành vi khi app về background. Kiểm tra Android, iOS, web hoặc desktop đúng platform mà app hỗ trợ. Ghi lại owner, upgrade...

permission_handler: luồng xin quyền runtime có thể giải thích

permission_handler: luồng xin quyền runtime có thể giải thích permission_handler đáng chú ý vì nó giải quyết status, rationale, mở settings fallback và khai báo platform. Bài này trình bày cách đặt package vào đúng boundary của một ứng dụng Flutter production thay vì chỉ copy một snippet vào màn hình. Package này sở hữu phần nào? Giữ API của package ở một adapter hoặc feature boundary có tên rõ ràng. Đưa lifecycle, lỗi và trạng thái loading vào contract mà UI có thể kiểm thử. Chỉ expose capability cần thiết; đừng để toàn app phụ thuộc vào chi tiết triển khai. Snippet bắt đầu final status = await Permission .camera. request (); if (status.isGranted) { openCamera (); } else if (status.isPermanentlyDenied) { await openAppSettings (); } Checklist trước khi ship Đọc README và changelog của package đúng version đang pin. Viết một test cho lifecycle, lỗi và hành vi khi app về background. Kiểm tra Android, iOS, web hoặc desktop đúng platform mà app hỗ trợ. Ghi ...

Patrol: integration test có thể đi ra ngoài sandbox Flutter

Patrol: integration test có thể đi ra ngoài sandbox Flutter patrol đáng chú ý vì nó giải quyết permission native, notification, deep link và tự động hóa thiết bị. Bài này trình bày cách đặt package vào đúng boundary của một ứng dụng Flutter production thay vì chỉ copy một snippet vào màn hình. Package này sở hữu phần nào? Giữ API của package ở một adapter hoặc feature boundary có tên rõ ràng. Đưa lifecycle, lỗi và trạng thái loading vào contract mà UI có thể kiểm thử. Chỉ expose capability cần thiết; đừng để toàn app phụ thuộc vào chi tiết triển khai. Snippet bắt đầu patrolTest ( 'camera permission flow' , ($) async { await $. native . grantPermissionWhenInUse (); await $. pumpWidgetAndSettle ( const CameraApp ()); await $(#takePhoto). tap (); expect (find. byType ( PhotoPreview ), findsOneWidget); }); Checklist trước khi ship Đọc README và changelog của package đúng version đang pin. Viết một test cho lifecycle, lỗi và hành vi khi app về backgr...

Pigeon: typed platform channels generated from one schema

Pigeon: typed platform channels generated from one schema pigeon is useful because it focuses on Dart/native codegen, API contracts, task queues, and versioning. The important engineering move is to place the package behind a clear boundary, so your Flutter UI depends on a stable capability rather than a vendor-shaped API. What the library should own Keep package calls inside a named adapter or feature boundary. Make lifecycle, errors, and loading state part of a testable contract. Expose only the capability the app needs; hide implementation details from the whole tree. A focused starting point @HostApi () abstract class SecureBiometrics { bool isAvailable (); @async bool authenticate ( String reason); } // dart run pigeon --input pigeons/biometrics.dart Production checklist Read the README and changelog for the exact version you pin. Add one test for lifecycle, failure, and app background/foreground behavior. Verify every platform your product suppo...

permission_handler: runtime permission flows you can explain

permission_handler: runtime permission flows you can explain permission_handler is useful because it focuses on status, rationale, settings fallback, and platform declarations. The important engineering move is to place the package behind a clear boundary, so your Flutter UI depends on a stable capability rather than a vendor-shaped API. What the library should own Keep package calls inside a named adapter or feature boundary. Make lifecycle, errors, and loading state part of a testable contract. Expose only the capability the app needs; hide implementation details from the whole tree. A focused starting point final status = await Permission .camera. request (); if (status.isGranted) { openCamera (); } else if (status.isPermanentlyDenied) { await openAppSettings (); } Production checklist Read the README and changelog for the exact version you pin. Add one test for lifecycle, failure, and app background/foreground behavior. Verify every platform your pro...

Patrol: integration tests that can leave the Flutter sandbox

Patrol: integration tests that can leave the Flutter sandbox patrol is useful because it focuses on native permissions, notifications, deep links, and device automation. The important engineering move is to place the package behind a clear boundary, so your Flutter UI depends on a stable capability rather than a vendor-shaped API. What the library should own Keep package calls inside a named adapter or feature boundary. Make lifecycle, errors, and loading state part of a testable contract. Expose only the capability the app needs; hide implementation details from the whole tree. A focused starting point patrolTest ( 'camera permission flow' , ($) async { await $. native . grantPermissionWhenInUse (); await $. pumpWidgetAndSettle ( const CameraApp ()); await $(#takePhoto). tap (); expect (find. byType ( PhotoPreview ), findsOneWidget); }); Production checklist Read the README and changelog for the exact version you pin. Add one test for lifecycle...