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

Bài đăng

Đang hiển thị bài đăng từ Tháng 10, 2026

skeletonizer: loading UI từ chính widget tree thật

skeletonizer: loading UI từ chính widget tree thật skeletonizer đáng chú ý vì nó giải quyết fake data, annotation skeleton, layout ổn định và accessibility. 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 Skeletonizer ( enabled : state.isLoading, child : ListView . builder ( itemCount : state.isLoading ? 6 : state.items.length, itemBuilder : (_, index) => ProductTile ( product : state.isLoading ? Product . fake () : state.items[index], ), ), ); 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...

shadcn_flutter: UI primitive có thể ghép cho Flutter

shadcn_flutter: UI primitive có thể ghép cho Flutter shadcn_flutter đáng chú ý vì nó giải quyết primitive ít áp đặt style, phong cách New York, Material interop và theme. 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 runApp ( ShadcnApp ( theme : ThemeData (colorScheme : ColorSchemes . lightZinc ()), home : Scaffold ( body : Center ( child : PrimaryButton (child : const Text ( 'Continue' )), ), ), ), ); 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...

shadcn-flutter: port design system, không chỉ theme

Copy design system web vào Flutter thất bại nếu chỉ copy màu. shadcn-flutter hữu ích vì ép câu chuyện phân lớp: token → primitive → composite → app của bạn. Học theo Sở hữu source (mô hình copy-in) để sửa không đau fork. Giữ accessibility và focus ring là primitive hạng nhất. Tài liệu hóa variant; đừng giấu trong một mega-widget. Cạm bẫy Giả định CSS web (cascade, :hover) không map 1:1. Ngân sách polish pointer desktop tách khỏi touch mobile. Dùng khi default Material đấu với brand của bạn. 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.

skeletonizer: loading UI from the real widget tree

skeletonizer: loading UI from the real widget tree skeletonizer is useful because it focuses on fake data, skeleton annotations, layout stability, and accessibility. 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 Skeletonizer ( enabled : state.isLoading, child : ListView . builder ( itemCount : state.isLoading ? 6 : state.items.length, itemBuilder : (_, index) => ProductTile ( product : state.isLoading ? Product . fake () : state.items[index], ), ), ); Production checklist Read the README and changelog for the exact version you pin. Add on...

shadcn_flutter: composable UI primitives for Flutter

shadcn_flutter: composable UI primitives for Flutter shadcn_flutter is useful because it focuses on unstyled primitives, New York style, Material interop, and theming. 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 runApp ( ShadcnApp ( theme : ThemeData (colorScheme : ColorSchemes . lightZinc ()), home : Scaffold ( body : Center ( child : PrimaryButton (child : const Text ( 'Continue' )), ), ), ), ); Production checklist Read the README and changelog for the exact version you pin. Add one test for lifecycle, failure, and app ...

shadcn-flutter: porting a design system, not a theme

Copying a web design system into Flutter fails when you only copy colors. shadcn-flutter is useful because it forces a layered story: tokens → primitives → composites → your app. Steal this Own the source (copy-in model) so you can edit without fork pain. Keep accessibility and focus rings as first-class primitives. Document variants; do not hide them in one mega-widget. Pitfalls Web CSS assumptions (cascading, :hover) do not map 1:1. Budget for desktop pointer polish separately from mobile touch. Use this when Material defaults fight your brand. Originally published on FlutterCook . Read the latest version there — that copy is the one kept up to date.

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.

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

Mocktail: mock Dart dễ đọc mà không cần file generated

Mocktail: mock Dart dễ đọc mà không cần file generated mocktail đáng chú ý vì nó giải quyết fake, stub, verify, fallback value và ranh giới test. 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 class MockUserApi extends Mock implements UserApi {} final api = MockUserApi (); when (() => api. fetchUser ( '42' )). thenAnswer ((_) async => user); final result = await repository. load ( '42' ); verify (() => api. fetchUser ( '42' )). called ( 1 ); 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à...

Melos: biến Flutter monorepo thành ranh giới sản phẩm

Melos: biến Flutter monorepo thành ranh giới sản phẩm melos đáng chú ý vì nó giải quyết script workspace, package graph, test chọn lọc và release. 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 name : flutter_workspace packages : - apps /** - packages/** scripts: analyze: melos exec -- flutter analyze test: melos exec --fail-fast -- flutter test changed: melos exec --since=main -- flutter test 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 platfor...

Lotti: journal với audio, habit và local DB nghiêm túc

Tutorial Flutter hay dừng ở CRUD. Lotti thú vị vì journaling buộc entry đa modal (text, audio, habit, đo lường) và lưu trữ local dài hạn. Bài học Kiểu entry phân cấp thắng một row nullable khổng lồ. Capture + playback audio là surface platform, không phải widget. Insight cần index — thiết kế query trước chart. Cạm bẫy Migration trên dữ liệu cá nhân là thiêng; viết cẩn thận. Quyền ghi âm nền khác nhau theo OS. Nhìn Lotti khi “app note đơn giản” của bạn hết đơn giản. 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.

Mocktail: readable Dart mocks without generated files

Mocktail: readable Dart mocks without generated files mocktail is useful because it focuses on fakes, stubbing, verification, fallback values, and test boundaries. 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 class MockUserApi extends Mock implements UserApi {} final api = MockUserApi (); when (() => api. fetchUser ( '42' )). thenAnswer ((_) async => user); final result = await repository. load ( '42' ); verify (() => api. fetchUser ( '42' )). called ( 1 ); Production checklist Read the README and changelog for the exact version yo...

Melos: make a Flutter monorepo a product boundary

Melos: make a Flutter monorepo a product boundary melos is useful because it focuses on workspace scripts, package graph, selective tests, and releases. 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 name : flutter_workspace packages : - apps /** - packages/** scripts: analyze: melos exec -- flutter analyze test: melos exec --fail-fast -- flutter test changed: melos exec --since=main -- flutter test 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 e...

Lotti: journaling with audio, habits, and a serious local DB

Most Flutter tutorials stop at CRUD. Lotti is interesting because journaling forces multi-modal entries (text, audio, habits, measurements) and long-term local storage. Lessons Entry type hierarchies beat one giant nullable row. Audio capture + playback is a platform surface, not a widget. Insights need indexes — design queries before charts. Pitfalls Migrations on personal data are sacred; write them carefully. Background recording permissions vary by OS. Look at Lotti when your “simple notes app” is no longer simple. Originally published on FlutterCook . Read the latest version there — that copy is the one kept up to date.

LocalSend: truyền file kiểu AirDrop không cần cloud

LocalSend hỏi một câu sản phẩm sắc: hai thiết bị đổi file không cần tài khoản server được không? Có — nếu bạn thiết kế discovery LAN, HTTPS và consent rõ ràng. Điểm thiết kế Peer protocol trên HTTPS cục bộ (thường gắn với cổng 53317 ). UI Flutter với phần Rust/native cho đường nhạy hiệu năng. Không có control plane cloud — privacy là feature. Học theo Discovery + consent UX là nửa sản phẩm. Transfer phải resume được và hiện rõ; lỗi im lặng phá niềm tin. Giữ CLI headless cho power user và automation. Cạm bẫy Giới hạn background mobile giết transfer dài ngây thơ. Mạng hỗn hợp (AP isolation, VPN, captive portal) sinh support load — hãy tài liệu hóa. Học LocalSend khi cần pattern local-first , không chỉ một package để copy. 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.

local_auth: biometric là step-up, không phải nơi lưu password

local_auth: biometric là step-up, không phải nơi lưu password local_auth đáng chú ý vì nó giải quyết hỗ trợ thiết bị, prompt biometric, credential fallback và UX lỗi. 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 auth = LocalAuthentication (); final supported = await auth. isDeviceSupported (); if (supported) { final ok = await auth. authenticate ( localizedReason : 'Unlock your saved account' , options : const AuthenticationOptions (biometricOnly : false ), ); if (ok) unlock (); } Checklist trước khi ship Đọc README và changelog của package đúng version đang pin. ...

just_audio: audio có thể điều khiển bằng stream

just_audio: audio có thể điều khiển bằng stream just_audio đáng chú ý vì nó giải quyết queue, stream position, playlist, tốc độ và tích hợp background. 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 player = AudioPlayer (); await player. setAudioSource ( ConcatenatingAudioSource (children : [ AudioSource . uri ( Uri . parse (trackUrl)), ]), ); player. play (); player.positionStream. listen (updateProgress); 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 d...

LocalSend: AirDrop-style transfer with zero cloud

LocalSend answers a sharp product question: can two devices exchange files without an account or a server? Yes — if you design for LAN discovery, HTTPS, and explicit consent. Design highlights Peer protocol over local HTTPS (commonly associated with port 53317 ). Flutter UI with Rust/native pieces for performance-critical paths. No cloud control plane — privacy is the feature. Steal this Discovery + consent UX is half the product. Make transfers resumable and visible; silent failures destroy trust. Keep a CLI headless mode for power users and automation. Pitfalls Mobile OS background limits kill naive long transfers. Mixed networks (AP isolation, VPN, captive portals) will generate support load — document them. Study LocalSend when you need local-first product patterns, not just a package to copy. Originally published on FlutterCook . Read the latest version there — that copy is the one kept up to date.

local_auth: biometrics as a step-up, not a password store

local_auth: biometrics as a step-up, not a password store local_auth is useful because it focuses on device support, biometric prompts, fallback credentials, and failure UX. 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 auth = LocalAuthentication (); final supported = await auth. isDeviceSupported (); if (supported) { final ok = await auth. authenticate ( localizedReason : 'Unlock your saved account' , options : const AuthenticationOptions (biometricOnly : false ), ); if (ok) unlock (); } Production checklist Read the README and change...

just_audio: controllable audio with streams

just_audio: controllable audio with streams just_audio is useful because it focuses on queues, position streams, playlists, speed, and background integration. 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 player = AudioPlayer (); await player. setAudioSource ( ConcatenatingAudioSource (children : [ AudioSource . uri ( Uri . parse (trackUrl)), ]), ); player. play (); player.positionStream. listen (updateProgress); Production checklist Read the README and changelog for the exact version you pin. Add one test for lifecycle, failure, and app background/foreground...

json_serializable: JSON nhàm chán nhưng bền qua refactor

json_serializable: JSON nhàm chán nhưng bền qua refactor json_serializable đáng chú ý vì nó giải quyết codec generated, converter, checked mode và boundary API model. 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 @JsonSerializable () class User { const User ({ required this .id, required this .displayName}); final String id; final String displayName; factory User . fromJson ( Map < String , dynamic > json) => _$UserFromJson (json); Map < String , dynamic > toJson () => _$UserToJson ( this ); } Checklist trước khi ship Đọc README và changelog của package đúng version đ...