flutter_sdui_kit 0.1.0
flutter_sdui_kit: ^0.1.0 copied to clipboard
A server-driven UI framework for Flutter. Render screens from JSON, with built-in components, template data-binding, conditional visibility, form inputs, and a fully extensible component registry.
flutter_sdui_kit #
A server-driven UI framework for Flutter. Ship UI changes instantly — no app-store release required.
Your server sends a JSON schema describing what to render; the SDK turns it into native Flutter widgets on the client.
Table of Contents #
- Why SDUI?
- SDUI JSON Protocol
- Getting Started
- Data Binding & Templates
- Conditional Visibility
- Action Handling
- Custom Components
- Architecture
- Implementing for Other Platforms
- API Reference
Why SDUI? #
| Problem | SDUI Solution |
|---|---|
| A/B tests require new releases | Server sends variant A or B — same binary |
| Layout bug on production | Fix the JSON, users see it instantly |
| Feature flags for UI | visible_if expressions, resolved client-side |
| Consistent UI across platforms | One JSON schema, N client SDKs |
SDUI JSON Protocol #
This protocol is framework-agnostic. Any client SDK (Flutter, SwiftUI, Jetpack Compose, React Native) can implement it by following the same node/action/props contract described here.
Screen Envelope #
Every response from your SDUI API is a screen envelope:
{
"screen": "home",
"version": 1,
"cache_ttl": 300,
"theme": {
"primary": "#6C63FF",
"background": "#FFFFFF",
"text": "#1A1A2E"
},
"body": { … }
}
| Field | Type | Description |
|---|---|---|
screen |
string |
Screen identifier (routing, analytics, caching) |
version |
int |
Schema version for client-side migration gates |
cache_ttl |
int |
Seconds this response may be cached locally |
theme |
object? |
Colour overrides — primary, background, text + any extras |
body |
node |
The root component node |
Node Structure #
Every node follows the same shape:
{
"type": "text",
"props": {
"content": "Hello, {{user.name}}!",
"style": "heading",
"visible_if": "user.is_premium"
},
"action": {
"type": "navigate",
"payload": { "route": "/profile" }
},
"children": []
}
| Field | Type | Description |
|---|---|---|
type |
string |
Component type identifier |
props |
object |
Arbitrary key-value properties for the component |
action |
object? |
Action to fire on user interaction |
children |
node[] |
Ordered child nodes |
Actions #
{
"type": "navigate",
"payload": { "route": "/shop" }
}
The type string is resolved by the client's action handler registry. Common types:
| Action Type | Typical Payload |
|---|---|
navigate |
{ "route": "/path" } |
api_call |
{ "endpoint": "/api/…", "method": "POST", "body": {…} } |
open_sheet |
{ "screen": "filter_sheet" } |
input_changed |
{ "field": "email", "value": "…" } (auto-fired by form components) |
Built-in Component Types #
Layout
| Type | Key Props |
|---|---|
column |
spacing, cross_alignment, main_alignment |
row |
spacing, cross_alignment, main_alignment, alignment |
padding |
all, horizontal, vertical, left, right, top, bottom |
sizedbox |
width, height |
container |
background, corner_radius, width, height, padding |
scroll |
direction ("horizontal" / "vertical") |
Content
| Type | Key Props |
|---|---|
text |
content, style (heading/subheading/body/caption), color, max_lines, text_align |
image |
url, aspect_ratio, corner_radius, fit |
button |
label, variant (primary/outline/text), full_width, background, text_color, corner_radius |
icon |
name, size, color |
Composite
| Type | Key Props |
|---|---|
card |
corner_radius, background, elevation, width |
list |
direction, spacing, padding |
divider |
color, thickness |
Form
| Type | Key Props |
|---|---|
text_input |
placeholder, value, field, max_lines, obscure, border_color, corner_radius |
checkbox |
checked, label, field, size, active_color |
switch |
value, label, field, active_color |
dropdown |
options ([{label, value}]), selected, placeholder, field |
Interaction
| Type | Key Props |
|---|---|
gesture |
behavior ("opaque" / "translucent" / "defer"); requires action on the node |
Getting Started #
Install #
dependencies:
flutter_sdui_kit: ^0.1.0
flutter pub get
Basic Usage #
import 'package:flutter_sdui_kit/flutter_sdui_kit.dart';
class MyScreen extends StatelessWidget {
final String serverJson; // JSON from your API
const MyScreen({super.key, required this.serverJson});
@override
Widget build(BuildContext context) {
return SduiWidget(json: serverJson);
}
}
That's it. The SduiWidget parses the JSON, builds the widget tree using the built-in component registry, and renders it.
Data Binding & Templates #
Use {{path.to.value}} in any string prop. Provide a data map to resolve them:
SduiWidget(
json: serverJson,
data: {
'user': {'name': 'John', 'is_premium': true},
'cart': {'count': 3},
},
)
Server JSON:
{ "type": "text", "props": { "content": "Hello, {{user.name}}! You have {{cart.count}} items." } }
Renders: Hello, John! You have 3 items.
You can also wrap the widget tree with SduiDataProvider to flow data from higher up:
SduiDataProvider(
data: {'user': {'name': 'Alice'}},
child: SduiWidget(json: screenJson),
)
Conditional Visibility #
Add visible_if to any node's props:
{
"type": "text",
"props": {
"content": "Premium exclusive!",
"visible_if": "user.is_premium"
}
}
Supported expressions:
| Expression | Example |
|---|---|
| Truthy | "user.is_premium" |
| Negation | "!cart.is_empty" |
| Equality | "user.role == admin" |
| Inequality | "user.role != guest" |
| Numeric | "cart.count > 0", "cart.count >= 5" |
| AND | "user.is_premium && cart.count > 0" |
| OR | "user.role == admin || user.is_staff" |
Action Handling #
Register handlers for action types your server sends:
final actions = ActionHandler();
actions.register('navigate', (action, payload) {
Navigator.pushNamed(context, payload['route'] as String);
});
actions.register('api_call', (action, payload) async {
await http.post(Uri.parse(payload['endpoint'] as String));
});
// Catch-all for unregistered action types
actions.onUnhandled = (action, payload) {
debugPrint('Unhandled: ${action.type}');
};
SduiWidget(
json: serverJson,
actionHandler: actions,
)
Form components (text_input, checkbox, switch, dropdown) automatically fire input_changed actions with { "field": "…", "value": … }.
Custom Components #
Register your own component builders to extend the kit:
final registry = createDefaultRegistry();
registry.register('rating_stars', (node, context) {
final count = node.props['count'] as int? ?? 5;
final filled = node.props['filled'] as int? ?? 0;
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(count, (i) => Text(
i < filled ? '★' : '☆',
style: TextStyle(
fontSize: 20,
color: context.theme?.primary ?? const Color(0xFFFFD700),
),
)),
);
});
SduiWidget(
json: serverJson,
registry: registry,
)
Your server can now send:
{ "type": "rating_stars", "props": { "count": 5, "filled": 4 } }
Architecture #
┌──────────────────────────────────────────────────────────────┐
│ Server JSON │
│ { screen, version, cache_ttl, theme, body } │
└──────────────────┬───────────────────────────────────────────┘
│ parse
┌──────────────────▼───────────────────────────────────────────┐
│ Models │
│ SduiScreen → SduiNode tree → SduiAction, SduiTheme │
└──────────────────┬───────────────────────────────────────────┘
│ render
┌──────────────────▼───────────────────────────────────────────┐
│ SduiRenderer │
│ ┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ ComponentRegistry│ │TemplateResolv│ │ExpressionEvaluat│ │
│ │ type → builder │ │ {{path}} → v │ │ visible_if │ │
│ └────────┬────────┘ └──────────────┘ └─────────────────┘ │
│ │ build │
│ ┌────────▼────────┐ │
│ │ ComponentBuilder │ ← receives SduiNode + SduiContext │
│ └────────┬────────┘ │
└───────────┼──────────────────────────────────────────────────┘
│
┌───────────▼──────────────────────────────────────────────────┐
│ Flutter Widget Tree │
│ Text, Column, Row, Image, GestureDetector, … │
└──────────────────────────────────────────────────────────────┘
Key abstractions (framework-agnostic):
| Concept | Responsibility |
|---|---|
| Node | Recursive data model with type, props, children, action |
| Component Registry | Maps type strings to platform-native builder functions |
| Action Handler | Dispatches action.type to registered callbacks |
| Template Resolver | Interpolates {{path}} placeholders against a data map |
| Expression Evaluator | Evaluates visible_if boolean expressions |
| Renderer | Walks the node tree, resolves templates/conditions, calls builders |
Any platform SDK (SwiftUI, Compose, React Native) can implement these same six abstractions to render the identical JSON protocol.
Implementing for Other Platforms #
The JSON protocol is designed to be platform-agnostic. To build an SDK for another framework:
- Parse the screen envelope into your platform's model objects (
Screen,Node,Action,Theme). - Build a Component Registry — a dictionary of
type→ native view builder. - Build an Action Handler — a dictionary of
action.type→ callback. - Implement a Template Resolver — regex replace
{{path}}with data map lookups. - Implement an Expression Evaluator — parse
visible_ifstrings into booleans. - Walk the tree — for each node, resolve templates → check visibility → look up builder → render.
The props contract per component type (documented above) stays the same across all platforms.
API Reference #
SduiWidget #
| Property | Type | Default | Description |
|---|---|---|---|
json |
String? |
— | Raw JSON string (mutually exclusive with screen) |
screen |
SduiScreen? |
— | Pre-parsed model (mutually exclusive with json) |
registry |
ComponentRegistry? |
built-in | Component builder registry |
actionHandler |
ActionHandler? |
no-op | Action dispatcher |
data |
Map<String, dynamic> |
{} |
Data context for templates and conditions |
errorWidget |
Widget |
SizedBox.shrink() |
Fallback when JSON fails to parse |
ComponentRegistry #
| Method | Description |
|---|---|
register(type, builder) |
Register a single builder |
registerAll(map) |
Register multiple builders |
unregister(type) |
Remove a builder |
setFallback(builder) |
Override the fallback for unknown types |
has(type) |
Check if a type is registered |
resolve(type) |
Get the builder (or fallback) |
ActionHandler #
| Method | Description |
|---|---|
register(type, handler) |
Register a handler for an action type |
registerAll(map) |
Register multiple handlers |
handle(action) |
Dispatch an action |
onUnhandled |
Catch-all callback for unknown types |
License #
MIT — see LICENSE.