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.
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.
Search
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
nullwhen 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:
| Member | Purpose |
|---|---|
buildContext | Present dialogs, sheets, or other Flutter UI |
dataSet | Access the active dataset |
fieldName | Identify the field that started the lookup |
lookupText | Read the current editor/current-field text |
lookupMode | Distinguish 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
| Action | Result |
|---|---|
| Click lookup button | FdcLookupMode.search |
| Press F4 | FdcLookupMode.search |
| Type a code and commit | FdcLookupMode.resolve when resolution is required |
Return FdcLookupResult | Apply returned field values |
Return null | Cancel/no change |