From 7353955d4e95bf5afe101827f8835b7445be905e Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 14 Oct 2026 17:04:05 -0400 Subject: [PATCH] add more guidelines to DEVELOPMENT.md --- DEVELOPMENT.md | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index f14b4ef..02ebb28 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -27,22 +27,26 @@ ref.read(MyController.provider.notifier).helperMethod() We use an object oriented style for controllers, where `provider` is a static member on the controller class. E.g. ```dart -class MyController extends AsyncNotifier { - final SomeInputType input; - MyController(this.input); - +class MyController(final SomeInputType input) + extends AsyncNotifier { @override Future build() async { return input.foo; } static final provider = - AsyncNotifierProvider.family( - AuthorController.new, - ); + AsyncNotifierProvider.family( + MyController.new, + ); } ``` +#### `watch` vs `read` + +Only use `ref.watch` in a `build` method, where you want to rebuild when the value changes. Everywhere else, like callbacks, `useEffect`, and methods on a controller, use `ref.read`. + +Use `.select` to narrow down what you depend on, e.g. `ref.watch(RoomsController.provider.select((rooms) => rooms[roomId]))`. + Providers which are not controllers, e.g. they expose no data, only methods, should instead live in `lib/helpers/`. For an example, see `lib/helpers/launch_helper.dart`. Other, non-provider helpers, like extensions or helper methods can also go in `lib/helpers/`. ### Don't use StatefulWidgets ([Flutter Hooks](https://pub.dev/packages/flutter_hooks)) @@ -63,6 +67,34 @@ When possible, we prefer not to create global variables or methods. You can usua I'm also not a big fan of private methods or variables, but this is contextual. +### Modern Dart syntax + +We try to use newer Dart language features where they make code shorter: + +- Primary constructors for classes, including widgets, e.g. `class const MemberList(final String roomId, {super.key}) extends HookConsumerWidget`. +- Dot shorthands wherever the type can be inferred, e.g. `.new(...)`, `padding: .all(8)`, `relationType == .reply`. +- Patterns and switch expressions over null checks and casts, e.g. `if (event.content case MessageContent(:final body?))` rather than checking `is` then using `!`. + +### Prefer `map` over inline `for` loops + +When building a list (or set, or map) from another collection, use `.map`, `.expand` and friends rather than a `for` inside the collection literal: + +```dart +// Prefer +children: rooms.map((room) => RoomTile(room)).toList(), + +// Over +children: [for (final room in rooms) RoomTile(room)], +``` + +Regular `for` statements are fine for logic that doesn't just build a collection. + +### Async values and errors + +When rendering an `AsyncValue`, use the `betterWhen` extension, which shows our standard loading and error widgets, or a switch expression if you need more control (e.g. `AsyncData(:final value) || AsyncLoading(:final value?)` to keep showing old data while reloading). + +For futures started from the UI that nothing awaits, like button presses, add `.onError(showError)` so failures are shown to the user instead of being lost. + ## LLM/AI Assisted Contributions LLM generated code is NOT allowed. All contributions should be written by humans. Using LLMs for interacting with others, e.g. for Comments, PRs, etc, is also not allowed.