LVGL Dropdowns: Separate Display Text from Stable Configuration Values
Store a stable configuration value in the application model and derive the dropdown’s selected index from the current option table. Replacing or translating option text must not silently change that value. If the stored value disappears from the available choices, enter an explicit reconciliation state instead of accepting whichever option becomes index zero.
Applicability: this article pins LVGL v9.3.0, an application-owned configuration model and a dropdown whose options are rebuilt under serialized UI ownership. The proposed fixture concerns display-language and capability changes, not virtualized lists. It includes no target execution, persistent-storage write or validated product setting.

1. Define values independently of captions
A hypothetical device offers MODE_AUTO, MODE_QUIET and MODE_BOOST. These identifiers belong to the model and any persisted configuration schema. “Automatic,” “Quiet” and “Boost” are display captions. The numeric LVGL indexes belong only to the current option list.
| Stable value | Initial caption/index | Reordered caption/index |
|---|---|---|
| MODE_AUTO | Automatic / 0 | Auto / 1 |
| MODE_QUIET | Quiet / 1 | Quiet / 2 |
| MODE_BOOST | Boost / 2 | Boost / 0 |
If the model contains MODE_QUIET, the selected index must move from one to two after this reorder. Preserving index one would select MODE_AUTO. The example changes one caption as well, demonstrating that matching old visible text is not the right restoration mechanism.
Generate the newline-separated option string and index-to-value array from the same ordered records. Require unique stable values. Validate captions against the chosen option encoding, including rejecting embedded newlines that would accidentally create extra options.
2. Account for replacement and clamping
The dropdown reference presents selection as an option index. In the v9.3.0 implementation, both option-list replacement setters reset the current and original selection indexes to zero. They do not search the new strings for the old semantic value.
After a successful replacement, search the new application table for the model’s stable value and set that resulting index. Validate the index first. The setter clamps an out-of-range index to the last option in a nonempty list, so a mistaken “not found” integer can silently become a valid-looking choice.
An empty string passed as the options text is counted as one blank option. Clearing options is a different operation that produces a zero-option state. Do not call the selection setter with a fabricated no-selection index or rely on it to validate an empty list.
Build a separate empty-state presentation: disable interaction, show an explanatory label and keep the model’s validity status explicit. The selected-index getter is not a substitute for checking the option count and the application’s mapping length.
3. Keep display reconstruction separate from configuration commits
Use a rebuild guard around presentation updates. Save the authoritative stable value, finish or cancel any open interaction according to the UI policy, prepare the new option records and validate them before installation. Publish the new options and mapping together while command processing is blocked.
If the stable value is found, restore its new index and verify the displayed choice. Then clear the guard. This is application coordination, not a transactional guarantee from several LVGL setter calls. Allocation failure and callback reentrancy need the product’s usual reviewed handling.
The tagged programmatic selection setter does not emit a value-changed event. Do not synthesize a user-change event merely to reuse a persistence callback during reconstruction. Instead, have model-to-view refresh and user-to-model commit call clearly separated application operations.
For user-generated LV_EVENT_VALUE_CHANGED, read a valid index from the current generation and resolve it to a stable value immediately. Recheck current availability, then submit a configuration request. Compare with the existing model value so a repeated selection does not automatically produce another persistent write.
4. Decide what a removed option means
Suppose a capability update removes MODE_QUIET while it remains the stored value. Choose a policy before implementing the rebuild. Possible outcomes include retaining the stored value as unavailable pending user choice, applying an explicitly approved migration, or rejecting the capability/configuration combination.
For the proposed fixture, preserve MODE_QUIET in the model, mark the selection unresolved and require an explicit new choice. Do not save MODE_AUTO simply because replacing the list reset the widget to zero. A visual default is not an authorization to change device behavior.
If the UI must display an unavailable current value, provide a distinct noncommitting presentation. A custom dropdown caption can describe the condition, but lv_dropdown_set_text changes the displayed button text; it is not a replacement for the model value or an extra option record. Keep its storage lifetime valid and prevent accidental commit while unresolved.
When the missing option later returns, resolve by the preserved stable value again. Record whether the product restores the display automatically or requires confirmation. This makes temporary capability loss distinguishable from an intentional user configuration change.
5. Test duplicate captions and open-list state
Two options can legitimately acquire identical translated captions. The tagged text-search helper returns the first matching option. Therefore, caption-to-index lookup cannot uniquely reconstruct configuration identity. Keep the semantic table authoritative and use disambiguated captions when users otherwise cannot tell choices apart.
Also avoid parsing the selected caption into a configuration enum. It can be truncated in a diagnostic buffer, localized or overridden by custom display text. During an open interaction, current and original selection state can differ; commit through the defined interaction event rather than sampling arbitrary intermediate text.
Request a locale change while the dropdown is open. The proposed policy defers rebuilding until the interaction is completed or deliberately cancelled, then performs the guarded refresh. Verify that a late release cannot commit an index using the replacement mapping.
6. Build a reconciliation regression
- Start with MODE_QUIET at index one and save the model value independently of the widget.
- Translate all captions without reordering; assert the model is unchanged and the matching value remains selected.
- Apply the illustrated reorder; expect MODE_QUIET at index two and no configuration-write request.
- Remove MODE_QUIET; expect unresolved presentation and the declared preserved-value policy.
- Test zero available options, one option, duplicate captions and an invalid mapping index.
- Make an explicit eligible user selection; expect one stable-value request through the normal commit path.
Keep option-table generations, model values, selected indexes and commit counts in the test record. Acceptance requires correct reconciliation and no unintended setting change during presentation updates. The persisted value should describe device intent, not the position of a translated line in yesterday’s dropdown.