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