pseudo_ui 0.1.2
pseudo_ui: ^0.1.2 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.
Cross-Platform #
This is the Dart/Flutter implementation. A TypeScript/Vue package (@burgantech/pseudo-ui) renders the same JSON schemas and views using PrimeVue 4 components. Both share the same vocabulary definitions and expression engine, ensuring consistent behavior across platforms.
License #
MIT