Skip to main content

Custom adapters

Create a custom adapter when an application has a specialized source that does not fit the built-in memory adapter or Pro SQLite, JSON, and REST adapters.

The simplest approach is to extend FdcDataAdapter and implement the operations your source supports.

class CustomerServiceAdapter extends FdcDataAdapter {
CustomerServiceAdapter(this.service)
: super(
readOnly: true,
capabilities: const FdcDataAdapterCapabilities(
filtering: true,
sorting: true,
paging: true,
totalCount: true,
search: true,
),
);

final CustomerService service;

@override
Future<FdcDataLoadResult> load(FdcDataLoadRequest request) async {
final result = await service.loadCustomers(request);

return FdcDataLoadResult(
rows: result.rows,
totalCount: result.totalCount,
);
}
}

Declare capabilities accurately

FdcDataAdapterCapabilities is part of the adapter contract. The dataset validates requested operations before calling the adapter.

Do not advertise an operation and silently ignore it. For example, an adapter that reports paging support must honor page request values and return rows for that requested slice.

Writable adapters

A writable adapter sets readOnly: false and implements applyUpdates().

The method receives an FdcChangeSet containing pending dataset changes and returns FdcDataApplyResult. Backend-confirmed values can be returned to synchronize generated keys or normalized fields back into the dataset.

Storage-specific validation can be added through validateStorageValue(). Backend exceptions can be translated into structured row-level errors through mapApplyException().

Apply acknowledgment and reconciliation

A writable adapter participates in this flow:

DataSet tracked changes

FdcChangeSet

adapter.applyUpdates()

FdcDataApplyResult

server-confirmed values or structured errors

DataSet reconciliation

A successful result may include authoritative values returned by storage, such as:

  • generated primary keys,
  • server timestamps,
  • normalized names or codes,
  • trigger-computed values.

The dataset correlates those results to local tracked records by record id. A custom adapter should therefore preserve stable correlation across the apply call rather than relying on response row order.

For failures, return structured FdcDataApplyError values with record and field context where available. This lets the dataset surface useful validation/storage feedback instead of collapsing the operation into one opaque exception.

Adapters should choose and document a clear apply contract. If the storage operation is atomic, return overall success or failure consistently. If a specialized adapter intentionally supports partial persistence, its reconciliation and retry semantics must be explicit and tested.

FdcDataAdapterCapabilities is not a performance hint. It is a semantic promise: the dataset may route filters, sorts, search, paging, aggregates, or selected-key queries to the adapter because the corresponding capability is advertised.

Aggregates

When an adapter advertises aggregate support, override aggregate() and calculate values over the complete effective query result. Aggregate requests contain filters and search state but no page limit.

Synchronous local adapters

Local adapters that can load immediately may also implement IFdcSynchronousDataAdapter. This is intended for genuinely synchronous sources; async-only adapters should use the normal asynchronous load() contract.