Skip to main content

Lookup

Lookup columns connect an editable field to application-defined search and resolve logic. The grid provides the invocation context, shows the lookup affordance, and applies the returned field values; your callback decides how data is searched and selected.

Customer lookup
Loading FDC sample…
const customerCatalog = <String, Map<String, Object?>>{
'C-2108': {
'customer_code': 'C-2108',
'company_name': 'Beacon Advisory Group',
'email': 'hello@beaconadvisory.com',
},
'C-3142': {
'customer_code': 'C-3142',
'company_name': 'Cedar Point Logistics',
'email': 'ops@cedarpointlogistics.com',
},
'C-4820': {
'customer_code': 'C-4820',
'company_name': 'Atlas Medical Supply',
'email': 'orders@atlasmedical.com',
},
};

FdcGrid(
dataSet: customers,
options: const FdcGridOptions(autoEdit: true),
statusBar: const FdcGridStatusBar(visible: true),
columns: [
FdcTextColumn<dynamic>(
fieldName: 'customer_code',
label: 'Customer code',
width: 160,
lookupIcon: Icons.manage_search,
onLookup: (context) async {
if (context.lookupMode == FdcLookupMode.search) {
final customer = await showCustomerPicker(
context.buildContext,
initialText: context.lookupText,
);

return customer == null
? null
: FdcLookupResult({
'customer_code': customer.code,
'company_name': customer.companyName,
'email': customer.email,
});
}

final code = context.lookupText?.trim().toUpperCase();
final customer = customerCatalog[code];
return customer == null ? null : FdcLookupResult(customer);
},
),
const FdcTextColumn<dynamic>(
fieldName: 'company_name',
label: 'Company',
width: 260,
readOnly: true,
),
const FdcTextColumn<dynamic>(
fieldName: 'email',
label: 'Email',
width: 300,
readOnly: true,
),
],
);

In the sample, use the lookup button on Customer code or press F4 to trigger interactive search. You can also type a known code such as C-2108, C-3142, or C-4820 and commit the edit to exercise resolve behavior.

Basic configuration​

Add onLookup to a supported editable column:

FdcTextColumn<dynamic>(
fieldName: 'customer_code',
lookupIcon: Icons.manage_search,
onLookup: (context) async {
final customer = await showCustomerPicker(
context.buildContext,
initialText: context.lookupText,
);

if (customer == null) {
return null;
}

return FdcLookupResult({
'customer_code': customer.code,
'company_name': customer.companyName,
'email': customer.email,
});
},
)

The lookup button appears only when onLookup is present. The default shortcut is F4 and can be changed or disabled with lookupShortcut.

Search and resolve modes​

One callback handles two distinct workflows.

FdcLookupMode.search is an explicit user action:

  • clicking the lookup button;
  • pressing the configured lookup shortcut, F4 by default.

Search mode is the right place to open a dialog, searchable list, remote search screen, or any custom selector.

if (context.lookupMode == FdcLookupMode.search) {
return showCustomerPicker(
context.buildContext,
initialText: context.lookupText,
);
}

Resolve​

FdcLookupMode.resolve is used when entered text needs to be resolved during commit. It supports code-entry workflows where experienced users type a known code directly instead of opening search UI.

final code = context.lookupText?.trim().toUpperCase();
final customer = await repository.findByCode(code);

return customer == null
? null
: FdcLookupResult({
'customer_code': customer.code,
'company_name': customer.companyName,
});

Search and resolve belong in the same callback because they are two entry paths into the same business lookup operation.

lookupText semantics​

context.lookupText deliberately preserves the input source:

  • when an editor is active, it contains the current raw editor text;
  • an explicitly cleared editor produces an empty string;
  • when search is invoked without an active editor, it uses the current field contents formatted as text;
  • it is null when the field value itself is null or not textually available.

This distinction is useful when resolve logic must differentiate an empty code from no value.

Returning multiple fields​

FdcLookupResult can return the initiating field and any related fields from the same record:

return FdcLookupResult({
'customer_code': customer.code,
'company_name': customer.companyName,
'email': customer.email,
});

The result is applied as one logical lookup write set. This is the recommended pattern for code/name pairs, foreign-key plus display-name combinations, address lookups, product lookups, and similar RAD workflows.

Lookup context​

FdcLookupContext provides:

MemberPurpose
buildContextPresent dialogs, sheets, or other Flutter UI
dataSetAccess the active dataset
fieldNameIdentify the field that started the lookup
lookupTextRead the current editor/current-field text
lookupModeDistinguish search from resolve
valueOf<T>()Read another field from the active record/edit buffer
tryValueOf<T>()Read another field without failing when unavailable

valueOf() reads through the active edit buffer, so a lookup can depend on sibling values the user has changed but not yet posted.

final warehouseId = context.tryValueOf<int>('warehouse_id');
final productCode = context.lookupText;

Cancellation and unresolved values​

Return null when:

  • the user cancels interactive search;
  • search completes without a selection;
  • resolve cannot find a matching record;
  • the lookup should leave the current record unchanged.

Your application can combine this with dataset validation when an unresolved value must block posting.

Reusing lookup logic​

Lookup context intentionally avoids grid-specific row and column coordinates. The same lookup service can therefore be reused by a grid column and a standalone FDC editor.

Keep selection UI and repository access in application services where practical, and let the callback translate the selected business object into FdcLookupResult values.

Interaction summary​

ActionResult
Click lookup buttonFdcLookupMode.search
Press F4FdcLookupMode.search
Type a code and commitFdcLookupMode.resolve when resolution is required
Return FdcLookupResultApply returned field values
Return nullCancel/no change