pseudo_ui 0.1.2 copy "pseudo_ui: ^0.1.2" to clipboard
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, $context namespaces for dynamic value resolution
  • Conditional engineshowIf / hideIf / enableIf / disableIf with allOf, anyOf, not compound 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
  • $ui state — transient UI state (dialog visibility, active tab) that never pollutes form data
  • Overlay surfaces — Dialog, BottomSheet, SideSheet, NavigationDrawer open/close driven by $ui state
  • 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

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 all x-* 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

0
likes
0
points
11
downloads

Publisher

unverified uploader

Weekly Downloads

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, nested components.

Homepage
Repository (GitHub)
View/report issues

Topics

#sdui #server-driven-ui #json-schema #material-design #dynamic-forms

License

unknown (license)

Dependencies

flutter

More

Packages that depend on pseudo_ui