pseudo_ui 0.2.3
pseudo_ui: ^0.2.3 copied to clipboard
Server-Driven UI rendering engine for Flutter. Define UI with JSON Schema + View JSON, render natively with Material 3 widgets. 45+ components, expression engine, conditional visibility, LOV cascading [...]
pseudo_ui #
A Server-Driven UI (SDUI) rendering engine for Flutter. Define your UI once with JSON — render it natively with Material 3 widgets.
The SDK pairs a JSON Schema data contract with a View JSON component tree to produce fully interactive forms, summaries, and multi-step workflows without shipping new client code.
Features #
- 45+ Material Design 3 components — TextField, Dropdown, DatePicker, TabView, Dialog, Slider, Avatar, Chip, and more
- Delegate-driven architecture — the SDK never makes HTTP calls; your app provides data via a simple interface
- Expression engine —
$form,$instance,$param,$ui,$lov,$lookup,$schema,$item,$contextnamespaces for dynamic value resolution - Conditional engine —
showIf/hideIf/enableIf/disableIfwithallOf,anyOf,notcompound rules and 13 operators - Validation engine — JSON Schema validation (
pattern,format,minLength,min/max) plus async custom validation via delegate - LOV & Lookup — List-of-Values dropdowns with cascade filtering and real-time data enrichment
- Nested components — reusable sub-components with isolated contexts, input contracts (
x-binding), and two-way data flow $uistate — transient UI state (dialog visibility, active tab) that never pollutes form data- Overlay surfaces — Dialog, BottomSheet, SideSheet, NavigationDrawer open/close driven by
$uistate - Multi-language — all labels, errors, and enum values support
{ "en": "...", "tr": "...", "ar": "..." }
Installation #
Add to your pubspec.yaml:
dependencies:
pseudo_ui: ^0.1.1
Or from pub.dev:
flutter pub add pseudo_ui
Quick Start #
Two steps: first a minimal form, then we enrich it with a nested component and a dropdown.
Step 1: Minimal form #
Name, surname, birth date, and a Submit button.
Schema:
{
"type": "object",
"required": ["name", "surname"],
"properties": {
"name": { "type": "string", "minLength": 1, "x-labels": { "en": "First Name", "tr": "Ad" } },
"surname": { "type": "string", "minLength": 1, "x-labels": { "en": "Surname", "tr": "Soyad" } },
"birthDate": { "type": "string", "format": "date", "x-labels": { "en": "Date of Birth", "tr": "Doğum Tarihi" } }
}
}
View:
{
"view": {
"type": "Column",
"gap": "md",
"children": [
{ "type": "TextField", "bind": "name" },
{ "type": "TextField", "bind": "surname" },
{ "type": "DatePicker", "bind": "birthDate" },
{ "type": "Button", "label": { "en": "Submit", "tr": "Gönder" }, "variant": "filled", "action": "submit" }
]
}
}
Dart code:
import 'package:flutter/material.dart';
import 'package:pseudo_ui/pseudo_ui.dart';
class MyPage extends StatelessWidget {
final DataSchema schema; // loaded from JSON
final ViewDefinition view; // loaded from JSON
const MyPage({super.key, required this.schema, required this.view});
@override
Widget build(BuildContext context) {
return PseudoView(
schema: schema,
view: view,
lang: 'en',
delegate: MyDelegate(),
);
}
}
class MyDelegate extends PseudoViewDelegate {
@override
RequestDataFn get requestData => (ref, [params]) async {
throw UnimplementedError('No LOV in this example');
};
@override
Future<({DataSchema schema, ViewDefinition view})> loadComponent(String ref) async {
throw UnimplementedError('No nested components');
}
@override
Future<void> onAction(String action, Map<String, dynamic> formData, [String? command]) async {
if (action == 'submit') debugPrint('Form submitted: $formData');
}
}
Step 2: Add a dropdown with LOV #
Add a city field. The dropdown gets options via requestData.
Add to schema:
"city": {
"type": "string",
"x-labels": { "en": "City", "tr": "Şehir" },
"x-lov": {
"source": "get-cities",
"valueField": "$.response.data.code",
"displayField": "$.response.data.name"
}
}
Add to view children (before Button):
{ "type": "Dropdown", "bind": "city" }
Implement requestData:
@override
RequestDataFn get requestData => (ref, [params]) async {
if (ref == 'get-cities') {
return {
'response': {
'data': [
{'code': '06', 'name': 'Ankara'},
{'code': '34', 'name': 'Istanbul'},
]
}
};
}
throw Exception('Unknown source: $ref');
};
Initial data (optional) #
You can pass initial values when the view first renders:
| Parameter | Purpose |
|---|---|
instanceData |
Backend/persisted data (e.g. read-only display, lookup filters). Used by $instance expressions and summary views. |
params |
Parent-bound parameters for nested components. Used by $param expressions. |
PseudoView(
schema: schema,
view: view,
instanceData: {'status': 'active', 'createdAt': '2024-01-01'},
params: {'branchCode': '001'},
lang: 'en',
delegate: delegate,
)
instanceData is for backend state that drives display and lookups. Form data is managed internally by PseudoView based on schema defaults and user input — both instanceData and params are optional.
Lookups (enrichment) #
When a schema property has x-lookup, the SDK fetches enrichment data via requestData. You must activate the lookup by listing it in the view's lookups array — otherwise it won't run.
Schema (defines the lookup):
{
"branchDetail": {
"type": "object",
"x-lookup": {
"source": "get-branch-details",
"resultField": "$.response.data",
"filter": [{ "param": "branchCode", "value": "$param.selectedBranchCode", "required": true }]
}
}
}
View (activates it):
{
"dataSchema": "urn:amorphie:res:schema:shared:branch-info",
"lookups": ["branchDetail"],
"view": { "type": "Column", "children": [
{ "type": "Text", "content": "$lookup.branchDetail.address" }
]}
}
Then use $lookup.branchDetail.address, $lookup.branchDetail.phone, etc. in Text or other components. The SDK calls requestData(source, filterParams) when the view mounts; the delegate returns the enrichment payload.
Architecture #
┌─────────────────────────────────────────────────────┐
│ Your Application │
│ │
│ ┌──────────────┐ implements ┌────────────────┐ │
│ │ MyPage │ ──────────────▶│ Delegate │ │
│ │ │ │ - requestData │ │
│ │ PseudoView( │ │ - loadComponent│ │
│ │ schema, │ │ - onAction │ │
│ │ view, │ │ - onLog │ │
│ │ lang, │ └────────────────┘ │
│ │ delegate) │ ▲ │
│ └──────┬───────┘ │ │
├─────────┼────────────────────────────────┼──────────┤
│ SDK │ │ │
│ ▼ │ │
│ ┌─────────────────┐ ┌──────────────┐ │ │
│ │ DynamicRenderer │ │ Expression │ │ │
│ │ (recursive) │ │ Resolver │ │ │
│ │ │ ├──────────────┤ │ │
│ │ 45+ MD3 widgets │ │ Conditional │ │ │
│ │ built-in Flutter │ │ Engine │ │ │
│ │ │ ├──────────────┤ │ │
│ │ Overlay surfaces │ │ Schema │ │ │
│ │ (Dialog, Sheet) │ │ Resolver │◀──┘ │
│ │ │ │ (validation) │ │
│ └─────────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘
Data Model (MVVM) #
| Layer | File | Purpose |
|---|---|---|
| ViewModel | schema.json |
Data contract — field types, validation, LOV sources, conditionals, multi-lang labels |
| View | view.json |
UI component tree — layout, binding, actions, transient UI state |
| Model | Backend | Persisted data, served via delegate's requestData |
Delegate Interface #
abstract class PseudoViewDelegate {
/// Fetch data from backend (LOV items, lookup enrichment).
RequestDataFn get requestData;
/// Load a nested component's schema + view by reference.
Future<({DataSchema schema, ViewDefinition view})> loadComponent(String ref);
/// Handle user actions (submit, cancel, back, custom commands).
Future<void> onAction(String action, Map<String, dynamic> formData, [String? command]);
/// Optional: custom async validation after built-in checks pass.
Future<String?> onValidationRequest(String field, dynamic value, Map<String, dynamic> formData);
/// Optional: capture SDK logs.
void onLog(String level, String message, [dynamic error, Map<String, dynamic>? context]);
/// Optional: receive nested component lookup data (for debug panels).
void onNestedLookupData(String componentRef, Map<String, Map<String, dynamic>> lookupData);
}
Supported Components #
Layout #
Column · Row · ScrollView · Grid · Expanded · Center · Wrap · Divider
Input #
TextField · TextArea · NumberField · Dropdown · Checkbox · RadioGroup · DatePicker · TimePicker · Switch · Slider · SegmentedButton · SearchField · AutoComplete
Display #
Text · Icon · Image · Avatar · Chip · Badge · ListTile · RichText · ProgressIndicator · LoadingIndicator
Surface & Overlay #
Card · Dialog · BottomSheet · SideSheet · Snackbar · Tooltip
Navigation #
TabView · AppBar · NavigationBar · NavigationDrawer
Container #
ExpansionPanel
Action #
Button · IconButton · FAB · Menu · Toolbar
Control #
ForEach · Component (nested)
Expression Namespaces #
| Namespace | Source | Example |
|---|---|---|
$form.field |
User input data | $form.firstName |
$instance.field |
Backend persisted data | $instance.status |
$param.field |
Parent-bound data (nested components) | $param.cityCode |
$ui.key |
Transient UI state (not submitted) | $ui.showDialog |
$schema.field.label |
Schema label for current language | $schema.city.label |
$lov.field |
LOV items array | $lov.city |
$lov.field.display |
Localized display name for current value | $lov.city.display |
$lookup.prop.field |
Enrichment data | $lookup.branch.address |
$item.field |
ForEach iteration item | $item.name |
$context.lang |
Runtime context | $context.lang |
Conditional Operators #
equals · notEquals · in · notIn · greaterThan · lessThan · greaterThanOrEquals · lessThanOrEquals · contains · startsWith · endsWith · isEmpty · isNotEmpty
Compound rules: allOf (AND), anyOf (OR), not (negate) — recursive nesting supported.
Validation Formats #
Built-in format validators: email · uri / url · date · date-time · time · phone / tel · iban
Vocabularies #
The repository includes shared JSON Schema vocabulary definitions for IDE auto-complete and tooling (in the repo root vocabularies/ folder):
- View Vocabulary (
view-vocabulary.json) — defines all valid component types, properties, and their constraints - ViewModel Vocabulary (
view-model-vocabulary.json) — defines allx-*extensions (x-labels,x-lov,x-conditional, etc.)
Both TS and Dart SDKs share the same vocabulary definitions, ensuring consistent component behavior across platforms.
Testing #
cd core/dart-pseudo-ui
flutter test
101 tests covering expression resolver, schema resolver, conditional engine, bind resolver, data client, widget rendering, overlay surfaces, and LOV cascading.
Theming #
The pseudo-ui Flutter adapter integrates with Material 3 theming. Form fields respect InputDecorationTheme, text uses TextTheme variants, and colors follow ColorScheme roles. For semantic colors (success, warning, info), use the PseudoUiThemeExt theme extension.
See the Theming Guide for comprehensive theming documentation.
Cross-Platform #
This is the Dart/Flutter implementation. A companion TypeScript package (@burgan-tech/pseudo-ui on npm) renders the same JSON schemas and views using PrimeVue, PrimeReact or Angular Material components. Both share the same vocabulary definitions and expression engine, ensuring consistent behavior across platforms.
License #
MIT