Architecture proposal: A new cross-platform B4X Preferences library
My recommendation is to build a new library rather than fork B4XPreferencesDialog. The goal should be a reusable settings framework for B4A, B4J and B4i, with one shared preference definition that can render as either a dialog or a full settings page.
The most important architectural decision is this:
The preference model should be independent of the UI, language, theme and storage mechanism.
That separation solves your current problems at the architectural level instead of adding individual patches for localization, switch colors, date-picker buttons and file selection.
1. Library identity and scope
I'll use B4XPreferences as the working name. It can be renamed later.
B4XPreferences
A schema-driven, customizable preferences framework for B4X.
B4J
Desktop dialogs and settings panels
B4A
Android dialogs and settings screens
B4i
iOS dialogs and settings screens, subject to native UI constraints
Extensible
Custom controls, validators and platform adapters
Design goals
- One schema for every presentation and supported platform.
- Runtime localization using JSON dictionaries, with no language-specific template duplication.
- Centralized theme configuration and optional per-control overrides.
- Built-in file selection and other common preference types.
- Explicit value storage, validation and change notifications.
- Minimal dependencies and no mandatory third-party UI framework.
- Native platform integrations where needed, with a consistent public API.
A cross-platform library does not require every control to look pixel-identical. It requires consistent behavior and configuration, while respecting platform-specific capabilities.
2. Internal architecture
I recommend implementing the library as a .b4xlib containing shared B4X classes and optional platform-specific modules. This fits the B4X library model, which supports shared code, platform-specific code and declared dependencies. The official cross-platform guidance also recommends using XUI-compatible views and developing the shared project in B4J first.
The modules have clearly defined responsibilities.
| Module | Responsibility |
|---|
| PrefsModel | Parse and validate definitions; manage preference metadata |
| PrefsController | Coordinate values, validation, UI events and lifecycle |
| PrefsRenderer | Create and arrange preference controls |
| PrefsDialog | Present settings in a modal dialog |
| PrefsPage | Present settings in a full page or panel |
| PrefsTheme | Supply colors, fonts, dimensions and control styling |
| PrefsLocalization | Resolve translation keys and locale-dependent formatting |
| PrefsStore | Load, save, reset and migrate stored values |
| PrefsValidators | Built-in and custom validation rules |
| PrefsPlatform | Bridge platform-specific functions such as file selection |
These are proposed module names. We should not create a separate module for every preference type: most controls should be rendered by a shared factory, with dedicated classes only where complexity warrants them.
A critical distinction: model, controller and renderer
- Model: What the setting means, its ID, type, constraints and default.
- Controller: What happens when the user changes it, including validation and persistence.
- Renderer: How the setting appears on screen.
For example, company_logo remains a file preference regardless of whether it is displayed in a dialog or a settings page. Changing its title translation or background color must not change its value or behavior.
3. Public API proposal
The public API should feel natural to B4X developers. It should avoid requiring developers to construct a large collection of internal objects for ordinary use.
The following is a proposed API, not a claim that these methods already exist.
A. Define and load preferences
Support both JSON definitions and programmatic construction. JSON is useful for reusable templates and localization; programmatic construction is useful for dynamic forms.
Private Prefs As B4XPreferences
Private Values As Map
Private Sub InitializePreferences
Prefs.Initialize(Me, "Prefs")
Prefs.LoadDefinition( _
File.ReadString(File.DirAssets, "settings.json"))
Prefs.SetLanguage("en")
Prefs.SetTheme(PrefsTheme.CreateLight)
Values.Initialize
Values.Put("dark_mode", False)
Values.Put("company_logo", "")
Prefs.SetValues(Values)
End Sub
Suggested core methods:
| Method | Purpose |
|---|
| Initialize(Callback, EventName) | Initialize the component and event prefix |
| LoadDefinition(JsonText) | Load a JSON preference definition |
| LoadDefinitionFromFile(Dir, Filename) | Load a definition from a file |
| AddPreference(Definition) | Add a preference programmatically |
| RemovePreference(Id) | Remove a preference |
| GetPreference(Id) | Retrieve a preference definition |
| SetValues(Map) | Initialize current values |
| GetValues | Return a snapshot of current values |
| GetValue(Id) | Get a single value |
| SetValue(Id, Value) | Set a value programmatically |
| ResetToDefaults | Restore declared defaults |
| ValidateAll | Validate the complete set of values |
For dynamic applications, I would also provide methods for adding sections, updating available choice options and enabling or hiding individual preferences at runtime.
B. Show a dialog or render a page
The same model and controller should drive both presentations.
' Dialog presentation
Prefs.ShowDialog("settings.title", "settings")
' Page presentation
Prefs.CreatePage(RootPanel, "settings")
These are illustrative method signatures; exact parameters will be finalized when the B4X implementation is tested.
The API should support:
- Modal dialogs with configurable buttons and sizing.
- Embedded panels that do not create their own window.
- Full-page layouts suitable for B4XPages.
- Section headers, grouped preferences and collapsible sections.
- Save, Cancel and Reset actions.
- Optional immediate-save behavior.
- Validation before accepting changes.
Important: CreatePage should render into a supplied container, not force the library to own navigation. B4XPages can manage navigation, while the preferences library manages only the settings UI. This keeps the library usable in projects that do not use B4XPages.
C. Event model
The event API should make it easy to respond to changes without requiring developers to subclass internal controls.
Sub Prefs_ValueChanged(Id As String, Value As Object)
Log($"${Id} = ${Value}"$)
End Sub
Sub Prefs_ValidationFailed(Id As String, Message As String)
Log($"Invalid ${Id}: ${Message}"$)
End Sub
Sub Prefs_DialogClosed(Confirmed As Boolean, Values As Map)
If Confirmed Then
' Save or apply settings.
End If
End Sub
I recommend these events:
| Event | When it fires |
|---|
| ValueChanged | A value changes |
| BeforeValueChanged | Optional validation or veto before a change |
| ValidationFailed | A field or complete form fails validation |
| BeforeSave | The user attempts to save |
| AfterSave | Values have been accepted and persisted, if configured |
| DialogClosed | The dialog closes with its outcome |
| ActionClicked | An action-type preference is activated |
| FileSelected | A file selection completes |
Events should have documented ordering and should not fire repeatedly for every internal redraw.
For modal editing, the controller should maintain a draft value map. Pressing Cancel discards changes; confirming applies the draft. For an embedded settings page, the developer should be able to choose either explicit Save/Cancel behavior or immediate application of changes.
4. JSON schema proposal
I would use a versioned JSON schema with stable IDs and explicit translation keys.
{
"schemaVersion": 1,
"id": "app.settings",
"sections": [
{
"id": "general",
"titleKey": "section.general",
"preferences": [
{
"id": "dark_mode",
"type": "switch",
"titleKey": "setting.dark_mode",
"descriptionKey": "setting.dark_mode.description",
"defaultValue": false
},
{
"id": "company_logo",
"type": "file",
"titleKey": "setting.company_logo",
"defaultValue": "",
"options": {
"selectionType": "file",
"extensions": ["png", "jpg", "jpeg", "webp"],
"preview": true,
"allowClear": true
}
},
{
"id": "items_per_page",
"type": "number",
"titleKey": "setting.items_per_page",
"defaultValue": 20,
"options": {
"min": 5,
"max": 200,
"step": 5
}
}
]
}
]
}
This schema is a starting point, not a final specification. In particular, I would standardize options by type and reject invalid combinations early with useful error messages.
Schema rules
- id is stable and language-independent.
- type determines the control and value type.
- titleKey, descriptionKey and other text keys are resolved at runtime.
- defaultValue defines the initial value when no saved value exists.
- options defines type-specific behavior.
- Optional visibleWhen and enabledWhen rules allow dependent settings.
- Schema versioning enables future migrations.
A preference's ID should not change just because its displayed title changes or its position in the form changes.
5. Localization architecture
This is one of the most important features for your use case.
I recommend three distinct resources:
- settings.json — preference definitions.
- locales/en.json — English translations.
- locales/ms.json — Malay translations.
Additional dictionaries can provide Simplified Chinese, Traditional Chinese and any future language without duplicating settings.json.
The localization API should provide:
Prefs.SetLanguage("zh-Hant")
Prefs.SetFallbackLanguage("en")
Dim Caption As String = Prefs.Translate("setting.company_logo")
The localization engine should support:
- Runtime language switching.
- A configurable fallback language.
- A missing-key policy, such as returning the key or a readable fallback.
- Locale-aware dates, numbers and formatting where supported.
- Localized built-in buttons and validation messages.
- Application-supplied translations that override library defaults.
The library should ship with a default dictionary for built-in UI labels such as Browse, Clear, Today, OK, Cancel and Reset. Applications should only need to provide their own translations.
For a language switch, the renderer refreshes displayed text without reloading the schema or discarding unsaved values.
6. Theme and styling API
The theme should be a configuration object, not a collection of hard-coded colors hidden in control creation code.
I recommend separating design tokens from control-specific styles.
Design tokens
| Property group | Examples |
|---|
| Colors | Primary, surface, background, text, secondary text, border, error |
| Typography | Font family, title size, body size, label weight |
| Geometry | Corner radius, row height, dialog width, margins, spacing |
| Controls | Switch on/off colors, input borders, focus state, checkbox colors |
| Buttons | Primary, secondary, destructive, disabled states |
| Layout | Label width, control alignment, section spacing |
Illustrative usage:
Dim Theme As PrefsTheme
Theme.Initialize
Theme.PrimaryColor = xui.Color_RGB(40, 120, 210)
Theme.AccentColor = xui.Color_RGB(0, 150, 136)
Theme.BackgroundColor = xui.Color_White
Theme.TextColor = xui.Color_Black
Theme.SwitchOnColor = xui.Color_RGB(40, 180, 100)
Theme.CornerRadius = 10dip
Theme.RowSpacing = 8dip
Prefs.SetTheme(Theme)
I would also provide CreateLight, CreateDark and CreateSystem presets, plus a way to override the style of an individual preference.
Two customization levels
Level 1 — Theme configuration
For most applications. Change the look without writing custom rendering code.
Level 2 — Custom renderers
For developers who need a completely different control, custom layout, or specialized interaction.
A custom renderer should be an extension point, not a requirement for changing ordinary colors and dimensions.
One implementation detail matters: shared B4X code should use XUI-compatible APIs where possible, with small platform-specific adapters for capabilities that differ. The official B4X guidance supports this general strategy, but it does not guarantee that every native control exposes the same styling options on all platforms.
7. File picker design
File selection deserves its own interface because the meaning of a selected file differs between desktop and mobile platforms.
I would define two related preference types:
- file — select a file.
- directory — select a folder where supported.
A file preference could include these options:
| Option | Purpose |
|---|
| selectionType | File or directory |
| extensions | Restrict allowed file extensions |
| multiple | Allow multiple selections where supported |
| preview | Display an image preview |
| allowClear | Allow the current selection to be removed |
| titleKey | Localized picker title |
| startLocation | Optional initial browsing location |
File picker API
' Illustrative API
Prefs.SetValue("company_logo", SelectedPath)
Prefs.GetValue("company_logo")
The picker itself should be opened by the platform adapter, not by the schema parser.
B4J desktop
Use the JavaFX file chooser for file and directory selection. Return a filesystem path.
B4A Android
Use Android's Storage Access Framework or an appropriate system picker. Preserve URI access permissions when necessary.
B4i iOS
Use a document picker or photo picker, depending on the requested media type and required access.
Important distinction: Android and iOS may provide a URI or security-scoped file access rather than a permanent filesystem path. The library must not promise that every selected file can be stored as an absolute path and reopened indefinitely.
For images, I would make the API return a structured selection result internally, containing the URI or path, display name, and access information when applicable. The public API can then expose a simple value for ordinary use and a richer result for advanced use.
For a portable application, an optional copyToAppStorage mode can copy the selected image into application-managed storage. This is often the safest approach for a logo or other image that must remain available later.
8. Value storage and persistence
The library should not force all applications to use one storage system.
I recommend separating current in-memory values from the persistence adapter.
Preferences controller
Current values and draft changes
Map adapter
Caller-managed values and easy testing
JSON adapter
Portable configuration files
Platform adapter
Platform-specific preference storage
Custom adapter
SQLite or application-defined storage
Suggested interface:
' Illustrative API
Prefs.LoadValues(Store)
Prefs.SaveValues(Store)
Prefs.ResetToDefaults
The first release should include Map-based values and a JSON storage implementation. Platform-native storage and SQLite can follow when their use cases are clear.
Additional requirements:
- Do not save values automatically unless the selected mode requires it.
- Keep schema defaults separate from saved values.
- Preserve unknown stored keys when appropriate, so newer or older application versions do not unnecessarily destroy settings.
- Provide a schema migration mechanism for renamed, removed or type-changed preferences.
- Never persist temporary draft changes when the user cancels a dialog.
- Make file-storage behavior explicit rather than silently copying or moving user files.
9. Validation and dependent settings
A settings framework becomes significantly more useful when preferences can depend on one another.
For example, an application may enable a logo-path setting only when show_logo is true.
{
"id": "logo_path",
"type": "file",
"titleKey": "setting.logo_path",
"enabledWhen": {
"id": "show_logo",
"equals": true
}
}
For validation, support basic constraints in the schema and custom validation callbacks in code.
Examples include:
- Required fields.
- Minimum and maximum numbers.
- String length limits.
- Allowed values.
- File extension checks.
- Custom application rules.
The framework should distinguish between a control being hidden, disabled, and read-only. These are different behaviors and should not be represented by one ambiguous flag.
For complex dependencies, I would initially support simple equality and boolean expressions rather than embedding a full expression language in JSON.
10. Cross-platform compatibility contract
We should define what "cross-platform" means before implementation starts.
| Capability | B4A | B4J | B4i |
|---|
| Shared preference schema | Yes | Yes | Yes |
| Localization and themes | Yes | Yes | Yes |
| Basic text, number, switch and choice fields | Yes | Yes | Yes |
| Dialog presentation | Platform-adapted | Platform-adapted | Platform-adapted |
| Embedded settings page | Yes | Yes | Yes |
| File selection | Native adapter | Native adapter | Native adapter |
| Directory selection | Adapter-dependent | Supported through native chooser | Adapter-dependent |
| Image preview | Yes | Yes | Yes |
| Custom preference renderer | Supported API | Supported API | Supported API |
This is the intended compatibility contract, not a statement that these features have already been implemented or tested.
I would establish a minimum supported version for each B4X platform during development, then test the shared classes against those versions. Avoid relying on undocumented platform behavior.
For packaging, a .b4xlib is a good fit because B4X libraries can contain shared classes, supporting files and platform-specific modules.
11. Repository and packaging proposal
I recommend developing the library in a shared source repository, with B4J as the first implementation and test environment.
B4XPreferences/
├── Shared/
│ ├── B4XPreferences.bas
│ ├── PrefsModel.bas
│ ├── PrefsController.bas
│ ├── PrefsRenderer.bas
│ ├── PrefsTheme.bas
│ ├── PrefsLocalization.bas
│ ├── PrefsValidators.bas
│ └── PrefsStore.bas
├── Platform/
│ ├── B4A/
│ │ └── PrefsPlatform.bas
│ ├── B4J/
│ │ └── PrefsPlatform.bas
│ └── B4i/
│ └── PrefsPlatform.bas
├── Examples/
│ ├── B4J/
│ ├── B4A/
│ └── B4i/
├── Tests/
│ ├── Schema/
│ ├── Localization/
│ ├── Validation/
│ └── Storage/
├── Docs/
│ ├── API.md
│ ├── Schema.md
│ └── Migration.md
└── B4XPreferences.b4xlib
This is a proposed repository layout. We should keep platform-specific modules limited to actual platform differences; duplicating the controller or schema logic three times would defeat the purpose.
12. Development roadmap and acceptance criteria
Milestone 1 — Core and B4J prototype
First priority
Implement the schema parser, in-memory values, basic controls, dialog and embedded page.
Acceptance: The same JSON definition renders in both presentations, and changing the presentation does not change the stored values.
Milestone 2 — Localization and themes
Implement language dictionaries, fallback handling, theme presets and per-control overrides.
Acceptance: Switch between ten languages without duplicating the schema; change the switch color and date-picker button styling without modifying library code.
Milestone 3 — File preferences
Implement file selection, filters, image preview and clear-selection behavior.
Acceptance: The selected file can be reopened after restarting the application, subject to the platform's documented file-access rules.
Milestone 4 — B4A and B4i adapters
Implement platform-specific pickers, presentation behavior and any necessary styling adaptations.
Acceptance: The shared schema, value model and localization logic work on all three platforms, with documented platform-specific differences.
Milestone 5 — Release
Finalize the public API, package the .b4xlib, document dependencies and provide runnable examples.
Acceptance: A developer can integrate the library into a fresh project without modifying the library source.
13. Important design decisions to settle
Before writing the implementation, I would make these decisions explicit.
A. Preference definitions
JSON-first, with programmatic additions
Programmatic definitions first, JSON optional
Equal support for JSON and programmatic definitions
B. Saving behavior
Explicit Save / Cancel by default
Save each change immediately by default
Both modes, configurable per presentation
C. Page layout
Adaptive: dialog on smaller screens, page or panel on larger screens
Explicit dialog and page modes, selected by the application
D. Custom preference types
Callback-based custom renderers
Subclass or interface-based renderer extensions
Simple callbacks for ordinary use, renderer extensions for advanced use
Finalize architecture
My defaults would be JSON-first with programmatic additions, both saving modes, explicit presentation selection, and callback-based custom renderers with a more advanced extension mechanism available later.
The immediate next step should be the B4J core prototype, not the complete library. Once the schema, controller, renderer and theme are working in B4J, we can port the shared code to B4A and B4i and implement the platform-specific file pickers. This minimizes the risk of designing an API around behavior that proves difficult to implement consistently across the three platforms.