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

Bài đăng

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

Firebase App Distribution for Android/iOS betas — hướng dẫn Flutter

Distributing betas without store review needs a real pipeline. 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 Tester groups, CI upload, and release notes automation. 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ế Wire App Distribution into CI on every main merge. 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...

Firebase App Distribution for Android/iOS betas

Distributing betas without store review needs a real pipeline. 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 Tester groups, CI upload, and release notes automation. 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 Wire App Distribution into CI on every main merge. 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 ...

Product flavors on Windows and Linux for CI matrices — hướng dẫn Flutter

Dev/staging/prod builds are no longer mobile-only. 3.47 brings flavors to Windows and Linux. 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 Per-flavor assets and —flavor builds join Android/iOS on desktop. 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ế CI can produce side-by-side installers with different IDs and icons. 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...

Product flavors on Windows and Linux for CI matrices

Dev/staging/prod builds are no longer mobile-only. 3.47 brings flavors to Windows and Linux. 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 Per-flavor assets and —flavor builds join Android/iOS on desktop. 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 CI can produce side-by-side installers with different IDs and icons. 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 ...

Symbolication done right for iOS and Android crashes — hướng dẫn Flutter

Unsymbolicated crashes are unreadable and waste engineering time. 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 dSYM, mapping.txt, and CI upload steps that do not skip. 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ế Upload symbols in the same CI job that produces the archive. 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...

Symbolication done right for iOS and Android crashes

Unsymbolicated crashes are unreadable and waste engineering time. 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 dSYM, mapping.txt, and CI upload steps that do not skip. 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 Upload symbols in the same CI job that produces the archive. 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 t...

GitHub Actions for Flutter with sane caches — hướng dẫn Flutter

CI minutes are money. Cache pub, gradle, and pods/swiftpm correctly. 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 Faster builds via pub, gradle, and derived data caches. 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ế Split analyze, test, and build jobs so failures are obvious. 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 failu...

GitHub Actions for Flutter with sane caches

CI minutes are money. Cache pub, gradle, and pods/swiftpm correctly. 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 Faster builds via pub, gradle, and derived data caches. 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 Split analyze, test, and build jobs so failures are obvious. 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...

Firebase App Distribution for Android/iOS betas — hướng dẫn Flutter

Distributing betas without store review needs a real pipeline. 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 Tester groups, CI upload, and release notes automation. 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ế Wire App Distribution into CI on every main merge. 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...

Product flavors on Windows and Linux for CI matrices — hướng dẫn Flutter

Dev/staging/prod builds are no longer mobile-only. 3.47 brings flavors to Windows and Linux. 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 Per-flavor assets and —flavor builds join Android/iOS on desktop. 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ế CI can produce side-by-side installers with different IDs and icons. 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...

Symbolication done right for iOS and Android crashes — hướng dẫn Flutter

Unsymbolicated crashes are unreadable and waste engineering time. 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 dSYM, mapping.txt, and CI upload steps that do not skip. 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ế Upload symbols in the same CI job that produces the archive. 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...

GitHub Actions for Flutter with sane caches — hướng dẫn Flutter

CI minutes are money. Cache pub, gradle, and pods/swiftpm correctly. 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 Faster builds via pub, gradle, and derived data caches. 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ế Split analyze, test, and build jobs so failures are obvious. 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 failu...

Firebase App Distribution for Android/iOS betas

Distributing betas without store review needs a real pipeline. 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 Tester groups, CI upload, and release notes automation. 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 Wire App Distribution into CI on every main merge. 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 ...

Viết một lint tuỳ chỉnh cho codebase Dart của bạn

Nhóm nào cũng có một quy tắc cứ bị phá vỡ hoài. “Đừng dùng context sau một await.” “Repository trả về Result , không bao giờ ném lỗi.” “Không print trong lib/ .” Chúng sống trong một bình luận review, được giải thích lại cho từng người mới, rồi tuần sau lại bị vi phạm. Một luật của analyzer thì không bao giờ mệt khi phải nhắc đi nhắc lại. Trước hết, dùng cạn các luật có sẵn Trước khi viết bất cứ thứ gì, hãy kiểm tra xem luật đó đã tồn tại chưa. Linter đi kèm hơn hai trăm luật, và phần lớn quy ước của nhóm nằm trong số đó. Một cấu hình đáng để bắt đầu: # analysis_options.yaml include : package:flutter_lints/flutter.yaml analyzer : language : strict-casts : true strict-raw-types : true errors : invalid_annotation_target : ignore unused_import : error dead_code : error exclude : - "**/*.g.dart" - "**/*.freezed.dart" linter : rules : - always_declare_return_types - avoid_print - prefer_final_locals...

Sinh mã trong Dart: build_runner mà không bực mình

Bạn thêm json_serializable , chạy dart run build_runner build , và nhận được: Conflicting outputs were detected and the build will be terminated. Bạn chạy lại với --delete-conflicting-outputs , nó chạy được, và từ đó bạn gõ cờ này mãi mãi mà không biết nó đã xoá gì. Đó là mối quan hệ điển hình giữa lập trình viên và build_runner, và nó đáng được sửa, vì công cụ này dễ đoán hơn vẻ ngoài của nó. Mô hình: một asset vào, một asset ra build_runner không phải bộ chạy script. Nó là một hệ thống build trên asset — những file được định danh theo dạng package:name|path . Mỗi builder khai báo nó tiêu thụ phần mở rộng nào và sinh ra phần mở rộng nào, và build_runner dựng một đồ thị từ đó. json_serializable nói: với mỗi .dart , có thể sinh ra một .g.dart . freezed nói: với mỗi .dart , có thể sinh ra một .freezed.dart . Vì đầu ra được đánh khoá theo đường dẫn, hai builder cùng nhận một đường dẫn đầu ra sẽ xung đột — và một builder cũng xung đột với chính đầu ra cũ của nó còn nằm trên đĩa từ...

Product flavors on Windows and Linux for CI matrices

Dev/staging/prod builds are no longer mobile-only. 3.47 brings flavors to Windows and Linux. 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 Per-flavor assets and —flavor builds join Android/iOS on desktop. 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 CI can produce side-by-side installers with different IDs and icons. 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 ...

Symbolication done right for iOS and Android crashes

Unsymbolicated crashes are unreadable and waste engineering time. 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 dSYM, mapping.txt, and CI upload steps that do not skip. 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 Upload symbols in the same CI job that produces the archive. 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 t...

GitHub Actions for Flutter with sane caches

CI minutes are money. Cache pub, gradle, and pods/swiftpm correctly. 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 Faster builds via pub, gradle, and derived data caches. 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 Split analyze, test, and build jobs so failures are obvious. 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...