QueryMixin<T extends StatefulWidget> mixin Reading queries Mutations Infinite queries

Adds watchQuery, watchSelectQuery, watchInfiniteQuery and watchMutation to a State, to read queries flat in its build.

One of the four equal ways to read a query; the others are QueryContext, the builder widgets such as QueryBuilder, and the controllers such as QueryController. This one reads like a hook, and everything it creates belongs to the State: disposed with it, and recreated when the client above it changes.

class TaskScreen extends StatefulWidget {
  const TaskScreen(this.id, {super.key});

  final String id;

  @override
  State<TaskScreen> createState() => _TaskScreenState();
}

class _TaskScreenState extends State<TaskScreen> with QueryMixin {
  @override
  Widget build(BuildContext context) {
    final task = watchQuery(taskQuery(widget.id));
    final rename = watchMutation(renameTask(widget.id));
    return Column(
      children: [
        Text(task.dataOrNull?.name ?? '…'),
        FilledButton(
          onPressed: rename.value.isPending
              ? null
              : () => rename.mutate('Renamed'),
          child: const Text('Rename'),
        ),
      ],
    );
  }
}

Identity

Reads are identified by their QueryKey and types, not by call order, so there is nothing like the rules of hooks: calling watchQuery inside an if is fine. What that identity does not tell apart — two reads of one key with different selectors of the same output type, or two mutations of the same shape, the same mutationKey included — takes an id:, and reading two that differ without one is caught in debug builds (see watchMutation for what a mutation is compared by). An id then takes the key's place in the read's identity, the types staying part of it: the read keeps its observer when its key changes, which is what PlaceholderData.compute((previous, _) => previous) needs to keep showing the previous key's data while the next loads.

When a read is released

A key read in the previous build but not in this one is released after the frame, and so is a mutation. Everything goes when the State is disposed. A State that stops calling watchQuery altogether gives no signal, so its last observers stay until it is disposed; keep a conditional read in a widget of its own, and the condition becomes that widget's presence in the tree.

"Build" means this State's own build. A watchQuery inside a nested builder callback — a ValueListenableBuilder, LayoutBuilder or AnimatedBuilder in build, or a ListView.builder's itemBuilder — also reads for this State, but that callback re-runs on its own, so its reads are additive: they release nothing build read, and a key the callback stops reading (the layout that was left, the rows scrolled away) stays subscribed until an own build of this State that reads, or its disposal. A build that reads nothing itself, leaving every read to a nested builder, never starts over: those keys stay until the parent rebuilds this State's widget or it is disposed. Nothing shown in this State's subtree ever loses its subscription this way. Where the cost matters — a long list above all, or a key that depends on constraints — give the nested part, or each row, a widget of its own.

A read belongs to this State and rebuilds it, whoever made it: a dialog or sheet builder calling watchQuery is not rebuilt by a change, and its keys go at this State's next build that reads. Give a dialog a reader of its own.

The client

Reads run on the nearest QueryClientProvider's client. Override queryClient to read from a client of your own instead — a widget.client, say; a change of client releases everything held and recreates it on the new one.

Superclass constraints

Properties

context → BuildContext
The location in the tree where this widget builds.
no setterinherited
hashCode → int
The hash code for this object.
no setterinherited
mounted → bool
Whether this State object is currently in a tree.
no setterinherited
queryClient → QueryClient
The client every read of this State runs on: the nearest QueryClientProvider's by default.
no setter
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited
widget → T
The current configuration.
no setterinherited

Methods

activate() → void
Called when this object is reinserted into the tree after having been removed via deactivate.
inherited
build(BuildContext context) → Widget
Describes the part of the user interface represented by this widget.
inherited
deactivate() → void
Called when this object is removed from the tree.
inherited
debugFillProperties(DiagnosticPropertiesBuilder properties) → void
Add additional properties associated with the node.
inherited
didChangeDependencies() → void
Called when a dependency of this State object changes.
inherited
didUpdateWidget(covariant T oldWidget) → void
Called whenever the widget configuration changes.
inherited
dispose() → void
Called when this object is removed from the tree permanently.
override
initState() → void
Called when this object is inserted into the tree.
inherited
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
reassemble() → void
Called whenever the application is reassembled during debugging, for example during hot reload.
inherited
setState(VoidCallback fn) → void
Notify the framework that the internal state of this object has changed.
inherited
toDiagnosticsNode({String? name, DiagnosticsTreeStyle? style}) → DiagnosticsNode
Returns a debug representation of the object that is used by debugging tools and by DiagnosticsNode.toStringDeep.
inherited
toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) → String
A string representation of this object.
inherited
toStringShort() → String
A brief description of this object, usually just the runtimeType and the hashCode.
inherited
watchInfiniteQuery<TPageData, TPageParam, TData>(InfiniteQueryObserverOptionsBase<TPageData, TPageParam, TData> options, {Object? id, BuildWhen<QueryResult<TData>>? buildWhen}) → InfiniteQueryController<TPageData, TPageParam, TData>
watchQuery for an infinite query. Returns the controller rather than the result, because paging lives on it.
watchMutation<TData, TVariables, TOnMutateResult>(MutationOptions<TData, TVariables, TOnMutateResult> options, {Object? id, BuildWhen<MutationResult<TData, TVariables>>? buildWhen}) → MutationController<TData, TVariables, TOnMutateResult>
A mutation owned by this State, as a MutationController.
watchQuery<TData>(QueryObserverOptions<TData> options, {Object? id, BuildWhen<QueryResult<TData>>? buildWhen}) → QueryResult<TData>
Subscribes this State to options's query and returns its current result.
watchSelectQuery<TQueryData, TData>(QuerySelectOptions<TQueryData, TData> options, {Object? id, BuildWhen<QueryResult<TData>>? buildWhen}) → QueryResult<TData>
watchQuery for a query with a select: the cache holds TQueryData, this State sees TData.

Operators

operator ==(Object other) → bool
The equality operator.
inherited