Skip to content

Commit 23a35e6

Browse files
committed
Improve CommandBuilder documentation clarity and accuracy
- Fix basic example to pass manager via constructor instead of field - Remove verbose type parameters from all CommandBuilder examples (Dart infers them) - Add TParam/TResult explanation for builder parameters - Consolidate builder precedence rules and toWidget() docs in command_builders.md - Fix emoji formatting to use proper variation selector (❌️) - Clarify BuildContext availability: passed as parameter in CommandBuilder, accessible from enclosing build in toWidget() - Add required configuration warnings for lastValue/lastResult patterns - Update get_it_mixin reference in watch_it docs to clarify it as predecessor
1 parent 008eb18 commit 23a35e6

5 files changed

Lines changed: 195 additions & 90 deletions

File tree

‎code_samples/lib/command_it/command_builder_example.dart‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ class CounterManager {
1616

1717
// #region example
1818
class CounterWidgetWithBuilder extends StatelessWidget {
19-
CounterWidgetWithBuilder({super.key});
19+
const CounterWidgetWithBuilder({
20+
super.key,
21+
required this.manager,
22+
});
2023

21-
final manager = CounterManager();
24+
final CounterManager manager;
2225

2326
@override
2427
Widget build(BuildContext context) {
2528
return Column(
2629
children: [
27-
CommandBuilder<void, String>(
30+
CommandBuilder(
2831
command: manager.incrementCommand,
2932
whileRunning: (context, _, __) => CircularProgressIndicator(),
3033
onData: (context, value, _) => Text('Count: $value'),
@@ -41,6 +44,8 @@ class CounterWidgetWithBuilder extends StatelessWidget {
4144
// #endregion example
4245

4346
void main() {
47+
final manager = CounterManager();
4448
runApp(MaterialApp(
45-
home: Scaffold(body: Center(child: CounterWidgetWithBuilder()))));
49+
home: Scaffold(
50+
body: Center(child: CounterWidgetWithBuilder(manager: manager)))));
4651
}

‎docs/documentation/command_it/command_builders.md‎

Lines changed: 178 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ ValueListenableBuilder<CommandResult<void, String>>(
1818
)
1919
2020
// Use this:
21-
CommandBuilder<void, String>(
21+
CommandBuilder(
2222
command: command,
2323
whileRunning: (context, _, __) => CircularProgressIndicator(),
2424
onError: (context, error, _, __) => Text('Error: $error'),
@@ -40,6 +40,10 @@ CommandBuilder<void, String>(
4040

4141
All parameters are optional except `command`:
4242

43+
**Generic Types:**
44+
- `TParam` - The parameter that was passed when the command was called (e.g., the search query)
45+
- `TResult` - The return value from the command's execution
46+
4347
| Parameter | Type | Description |
4448
|-----------|------|-------------|
4549
| **command** | `Command<TParam, TResult>` | Required. The command to observe |
@@ -48,28 +52,30 @@ All parameters are optional except `command`:
4852
| **onNullData** | `Widget Function(BuildContext, TParam?)` | Builder when command returns null |
4953
| **whileRunning** | `Widget Function(BuildContext, TResult?, TParam?)` | Builder while command is executing |
5054
| **onError** | `Widget Function(BuildContext, Object, TResult?, TParam?)` | Builder when command throws error |
55+
| **runCommandOnFirstBuild** | `bool` | If true, executes command in initState (default: false) |
56+
| **initialParam** | `TParam?` | Parameter to pass when runCommandOnFirstBuild is true |
5157

5258
### When to Use Each Builder
5359

5460
**onData** - Commands with return values:
5561
```dart
56-
CommandBuilder<String, List<Item>>(
62+
CommandBuilder(
5763
command: searchCommand,
5864
onData: (context, items, query) => ItemList(items), // ✅ Use items
5965
)
6066
```
6167

6268
**onSuccess** - Void commands or when you don't need the result:
6369
```dart
64-
CommandBuilder<Item, void>(
70+
CommandBuilder(
6571
command: deleteCommand,
6672
onSuccess: (context, deletedItem) => Text('Deleted: ${deletedItem?.name}'),
6773
)
6874
```
6975

7076
**onNullData** - Handle null results explicitly:
7177
```dart
72-
CommandBuilder<void, Data?>(
78+
CommandBuilder(
7379
command: fetchCommand,
7480
onData: (context, data, _) => DataWidget(data),
7581
onNullData: (context, _) => Text('No data available'),
@@ -95,12 +101,16 @@ onError: (context, error, lastValue, param) => ErrorWidget(
95101
)
96102
```
97103

104+
::: tip
105+
The `lastValue` parameter in `whileRunning` and `onError` will only contain data if the command was created with `includeLastResultInCommandResults: true`. Otherwise, it will always be `null`. See [includeLastResultInCommandResults](/documentation/command_it/command_results#includelastresultincommandresults).
106+
:::
107+
98108
## Showing Parameter in UI
99109

100110
Access the command parameter in any builder:
101111

102112
```dart
103-
CommandBuilder<String, List<Item>>(
113+
CommandBuilder(
104114
command: searchCommand,
105115
whileRunning: (context, _, query) => Text('Searching for: $query'),
106116
onData: (context, items, query) => Column(
@@ -113,45 +123,176 @@ CommandBuilder<String, List<Item>>(
113123
)
114124
```
115125

116-
## toWidget() Extension Method
126+
## Auto-Running Commands on Mount
127+
128+
CommandBuilder can automatically execute a command when the widget is first built using the `runCommandOnFirstBuild` parameter. This is particularly useful when not using watch_it (which provides `callOnce` for this purpose).
129+
130+
### Basic Usage (No Parameter)
131+
132+
```dart
133+
CommandBuilder(
134+
command: loadTodosCommand,
135+
runCommandOnFirstBuild: true, // Executes command in initState
136+
whileRunning: (context, _, __) => CircularProgressIndicator(),
137+
onData: (context, todos, _) => TodoList(todos),
138+
onError: (context, error, _, __) => ErrorWidget(error),
139+
)
140+
```
141+
142+
**What happens:**
143+
1. Widget builds
144+
2. Command executes automatically in `initState`
145+
3. UI shows loading state → data/error state
146+
4. Command only runs **once** - not on rebuilds
147+
148+
### With Parameters
149+
150+
Use `initialParam` to pass a parameter to the command:
117151

118-
For use with `get_it_mixin`, `provider`, or `flutter_hooks` where you already have access to `CommandResult`:
152+
```dart
153+
CommandBuilder(
154+
command: searchCommand,
155+
runCommandOnFirstBuild: true,
156+
initialParam: 'flutter', // Parameter to pass
157+
whileRunning: (context, _, query) => Text('Searching for: $query'),
158+
onData: (context, items, query) => ItemList(items),
159+
onError: (context, error, _, query) => Text('Search failed: $error'),
160+
)
161+
```
162+
163+
### When to Use
164+
165+
**✅ Use runCommandOnFirstBuild when:**
119166

167+
<ul style="list-style: none; padding-left: 0;">
168+
<li style="padding-left: 1.5em; text-indent: -1.5em;">✅ Not using watch_it (no access to `callOnce`)</li>
169+
<li style="padding-left: 1.5em; text-indent: -1.5em;">✅ Widget should load its own data on mount</li>
170+
<li style="padding-left: 1.5em; text-indent: -1.5em;">✅ Want self-contained data-loading widgets</li>
171+
<li style="padding-left: 1.5em; text-indent: -1.5em;">✅ Simple data fetching scenarios</li>
172+
</ul>
173+
174+
**❌️ Don't use when:**
175+
176+
<ul style="list-style: none; padding-left: 0;">
177+
<li style="padding-left: 1.5em; text-indent: -1.5em;">❌️ Using watch_it - prefer `callOnce` instead (clearer separation)</li>
178+
<li style="padding-left: 1.5em; text-indent: -1.5em;">❌️ Command is already running elsewhere</li>
179+
<li style="padding-left: 1.5em; text-indent: -1.5em;">❌️ Need conditional logic before running</li>
180+
</ul>
181+
182+
### Comparison with watch_it's callOnce
183+
184+
**With watch_it (recommended if using watch_it):**
120185
```dart
121-
class MyWidget extends StatelessWidget with GetItStatefulWidgetMixin {
186+
class TodoWidget extends WatchingWidget {
122187
@override
123188
Widget build(BuildContext context) {
124-
final result = watchX((Manager m) => m.command.results);
189+
callOnce((Manager m) => m.loadTodos()); // Explicit trigger
190+
191+
return CommandBuilder(
192+
command: getIt<Manager>().loadTodos,
193+
onData: (context, todos, _) => TodoList(todos),
194+
);
195+
}
196+
}
197+
```
125198

126-
return result.toWidget(
127-
whileRunning: (lastValue, param) => CircularProgressIndicator(),
128-
onResult: (data, param) => DataWidget(data),
129-
onError: (error, lastValue, param) => ErrorWidget(error),
199+
**Without watch_it (use runCommandOnFirstBuild):**
200+
```dart
201+
class TodoWidget extends StatelessWidget {
202+
@override
203+
Widget build(BuildContext context) {
204+
return CommandBuilder(
205+
command: getIt<Manager>().loadTodos,
206+
runCommandOnFirstBuild: true, // Built-in trigger
207+
onData: (context, todos, _) => TodoList(todos),
130208
);
131209
}
132210
}
133211
```
134212

213+
## Builder Precedence Rules
214+
215+
Both `CommandBuilder` and `CommandResult.toWidget()` use the same precedence rules when determining which builder to call:
216+
217+
**Full precedence order:**
218+
1. **`if (error != null)`** → call `onError`
219+
2. **`if (isRunning)`** → call `whileRunning`
220+
3. **`if (onSuccess != null)`** → call `onSuccess` ⚠️ **Takes priority over onData!**
221+
4. **`if (data != null)`** → call `onData`
222+
5. **`else`** → call `onNullData`
223+
224+
::: tip onData vs onSuccess
225+
**When command completes successfully:**
226+
1. If `onSuccess` provided → call it (doesn't check if data is null)
227+
2. Else if data != null → call `onData`
228+
3. Else → call `onNullData`
229+
230+
**Choose `onSuccess` when:**
231+
- Command returns void (e.g., `Command.createAsyncNoResult`)
232+
- You only need to show confirmation/success message
233+
- Result data is irrelevant to the UI
234+
235+
**Choose `onData` when:**
236+
- Command returns data you need to display/use
237+
- You want to handle non-null data differently from null data
238+
:::
239+
240+
## toWidget() Extension Method
241+
242+
The `.toWidget()` extension method on `CommandResult` provides the same declarative builder pattern as `CommandBuilder`, but for use when you already have access to a `CommandResult` (e.g., via `watch_it`, `provider`, or `flutter_hooks`).
243+
244+
<<< @/../code_samples/lib/command_it/command_result_towidget_example.dart#example
245+
246+
**Parameters:**
247+
248+
You must provide **at least one** of these two:
249+
250+
- **`onData`** - `Widget Function(TResult result, TParam? param)?`
251+
- Called when command has **non-null data** (only if `onSuccess` not provided)
252+
- Receives both the result data and parameter
253+
- Use for commands that return data you need to display
254+
255+
- **`onSuccess`** - `Widget Function(TParam? param)?`
256+
- Called on successful completion (no error, not running)
257+
- Does **NOT** receive result data, only the parameter
258+
- **Takes priority** over `onData` if both provided
259+
- Use for void-returning commands or when you don't need the result value
260+
261+
Optional builders:
262+
263+
- **`whileRunning`** - `Widget Function(TResult? lastResult, TParam? param)?`
264+
- Called while command executes
265+
- Receives last result (if `includeLastResultInCommandResults: true`) and parameter
266+
267+
- **`onError`** - `Widget Function(Object error, TResult? lastResult, TParam? param)?`
268+
- Called when error occurs
269+
- Receives error, last result, and parameter
270+
271+
- **`onNullData`** - `Widget Function(TParam? param)?`
272+
- Called when data is null (only if neither `onSuccess` nor `onData` handle it)
273+
- Receives only the parameter
274+
135275
**Key differences from CommandBuilder:**
136276

137277
| Feature | CommandBuilder | toWidget() |
138278
|---------|---------------|-----------|
139-
| Access to BuildContext | ✅ Yes | ❌ No |
140-
| Requires CommandResult | ❌ No (takes Command) | ✅ Yes |
279+
| BuildContext in builders | ✅ Yes (as parameter) | ❌️ No (access from enclosing build) |
280+
| Requires CommandResult | ❌️ No (takes Command) | ✅ Yes |
141281
| Use case | Direct Command usage | Already watching results |
282+
| Builder precedence | Same as toWidget() | Same as CommandBuilder |
142283

143284
## When to Use What
144285

145286
**Use CommandBuilder when:**
146287
- Building UI directly from a Command
147-
- Need access to BuildContext in builders
148288
- Prefer declarative widget composition
149289
- Don't use state management that exposes results
290+
- Want BuildContext passed to builder functions
150291

151292
**Use toWidget() when:**
152-
- Already watching `command.results` via get_it_mixin/provider/hooks
153-
- Don't need BuildContext in builders
154-
- Want slightly less boilerplate
293+
- Already watching `command.results` via watch_it/provider/hooks
294+
- Want simpler builder signatures (no BuildContext parameter)
295+
- Prefer less boilerplate when already subscribed to results
155296

156297
**Use ValueListenableBuilder when:**
157298
- Need complete control over rendering logic
@@ -164,8 +305,19 @@ class MyWidget extends StatelessWidget with GetItStatefulWidgetMixin {
164305

165306
Show stale data while loading fresh data:
166307

308+
::: warning Required Configuration
309+
This pattern requires the command to be created with `includeLastResultInCommandResults: true`. Without this option, `lastItems` will always be `null` during execution. See [Command Results - includeLastResultInCommandResults](/documentation/command_it/command_results#includelastresultincommandresults) for details.
310+
:::
311+
167312
```dart
168-
CommandBuilder<String, List<Item>>(
313+
// Command must be created with this option:
314+
final searchCommand = Command.createAsync<String, List<Item>>(
315+
searchApi,
316+
[],
317+
includeLastResultInCommandResults: true, // Required for pattern below
318+
);
319+
320+
CommandBuilder(
169321
command: searchCommand,
170322
whileRunning: (context, lastItems, query) => Column(
171323
children: [
@@ -180,6 +332,10 @@ CommandBuilder<String, List<Item>>(
180332

181333
### Error with Retry
182334

335+
::: warning Required Configuration
336+
To display the last successful value (line 7), the command must be created with `includeLastResultInCommandResults: true`. See [Command Results - includeLastResultInCommandResults](/documentation/command_it/command_results#includelastresultincommandresults).
337+
:::
338+
183339
```dart
184340
onError: (context, error, lastValue, param) => Column(
185341
children: [
@@ -199,13 +355,13 @@ Not all builders are required - only provide what you need:
199355

200356
```dart
201357
// Minimal: only show data
202-
CommandBuilder<void, String>(
358+
CommandBuilder(
203359
command: command,
204360
onData: (context, data, _) => Text(data),
205361
)
206362
207363
// No loading indicator needed
208-
CommandBuilder<void, String>(
364+
CommandBuilder(
209365
command: command,
210366
onData: (context, data, _) => Text(data),
211367
onError: (context, error, _, __) => Text('Error: $error'),

0 commit comments

Comments
 (0)