sdui_kit 0.0.1
sdui_kit: ^0.0.1 copied to clipboard
A Server-Driven UI (SDUI) Flutter plugin that renders dynamic widgets from JSON. Supports text, title, description, table, grid, array, section, action, docs, html, and details components — each with [...]
sdui_kit #
Server-Driven UI for Flutter — render dynamic widget layouts from JSON without shipping a new app update.
┌─────────────────────────────────────────────────────────────┐
│ Server sends JSON → sduiParser() → SduiView renders │
└─────────────────────────────────────────────────────────────┘
Table of Contents #
- Installation
- Quick Start
- How It Works
- Components Reference
- Full JSON Payload Example
- API Reference
- Theming
- Custom PDF Viewer
- Extending with Custom Types
Installation #
Add to your pubspec.yaml:
dependencies:
sdui_kit: ^0.0.1
Then:
flutter pub get
Quick Start #
import 'package:sdui_kit/sdui_kit.dart';
// 1. Parse the server JSON
final items = sduiParser(json['viewData']);
// 2. Render
SduiView(
items: items,
primaryColor: Colors.indigo,
onAction: (action) {
Navigator.pushNamed(context, action.route);
},
)
That's it. The server controls what components appear, in what order, and with which style.
How It Works #
Server JSON
│
▼
sduiParser(raw) parses List → List<SduiItem>
│
▼
SduiView(items: items) wraps in SduiScope (theme propagation)
│
▼
SduiItemView(item: item) routes each item by type + style
│
├─ type: "text" → SduiDefaultText / SduiTextStyle1 / ...
├─ type: "grid" → SduiDefaultGrid / SduiGridStyle1 / ...
├─ type: "section" → SduiDefaultSection (recurses SduiView)
└─ ...
Each SduiItem has:
| Field | Type | Description |
|---|---|---|
type |
String | Component type: text, grid, table, etc. |
label |
String | Label / heading shown in the component |
style |
String | Visual variant: default, style1, style2 |
column |
int | Grid column count (default: 2) |
color |
String | Color key for colorized components |
data |
dynamic | Component-specific payload (String / List / Map) |
Components Reference #
1. text #
Displays a key–value pair. Four visual styles.
Styles
| Style | Visual Description |
|---|---|
default |
Label left · Value right, with optional copy button |
style1 |
Card with gradient icon, label above, large value below |
style2 |
Pill label badge on left · Bold value on right |
style3 |
Colored badge box (uses color field) |
JSON Schema
{
"type": "text",
"label": "Employee Name",
"data": "John Smith",
"style": "default"
}
{
"type": "text",
"label": "Status",
"data": "Active",
"style": "style3",
"color": "green"
}
Available color values: primary (default), red, green, orange, blue
Visual (default)
┌─────────────────────────────────────────┐
│ Employee Name John Smith │
└─────────────────────────────────────────┘
Visual (style1)
┌─────────────────────────────────────────┐
│ [📊] Employee Name │
│ John Smith │
└─────────────────────────────────────────┘
Visual (style2)
┌─────────────────────────────────────────┐
│ [LABEL] John Smith │
└─────────────────────────────────────────┘
Visual (style3)
┌─────────────────────────────────────────┐
│ Active ← green colored badge box │
└─────────────────────────────────────────┘
2. title #
Section heading. Two styles.
Styles
| Style | Visual Description |
|---|---|
default |
Bold primary-colored text |
style1 |
Left black accent bar + bold text |
JSON Schema
{"type": "title", "data": "Dashboard Overview", "style": "default"}
{"type": "title", "data": "Performance Metrics", "style": "style1"}
Visual (default)
Dashboard Overview
─────────────────
Visual (style1)
▌ Performance Metrics
3. description #
Long-form text. Two styles.
Styles
| Style | Visual Description |
|---|---|
default |
Optional label above, wrapped text below |
style1 |
White card with left accent bar + "Description" header |
JSON Schema
{
"type": "description",
"data": "This is a long explanation...",
"style": "default"
}
{
"type": "description",
"data": "This is a description with style1.",
"style": "style1"
}
Visual (default)
┌─────────────────────────────────────────┐
│ This is a long explanation of the │
│ feature you are reading about... │
└─────────────────────────────────────────┘
Visual (style1)
┌──────────────────────────────────────────┐
│ ▌ Description │
│ │
│ This is a description with style1. │
└──────────────────────────────────────────┘
4. details #
Title + description pair. One style.
JSON Schema
{
"type": "details",
"label": "Project Info",
"style": "default",
"data": {
"title": "Q4 Goals",
"description": "Complete the migration and launch the SDK.",
"style": "default"
}
}
Visual
Q4 Goals
────────
Complete the migration and launch the SDK.
5. section #
Container that groups nested components. Supports recursion.
Styles
| Style | Visual Description |
|---|---|
default |
White card with label header, children inside |
style1 |
Collapsible ExpansionTile card |
JSON Schema
{
"type": "section",
"label": "Employee Details",
"style": "default",
"data": [
{"type": "text", "label": "ID", "data": "EMP-00142", "style": "default"},
{"type": "text", "label": "Role", "data": "Developer", "style": "style1"}
]
}
{
"type": "section",
"label": "Collapsible Section",
"style": "style1",
"data": [
{"type": "title", "data": "Phase 1", "style": "style1"}
]
}
Visual (default)
┌──────────────────────────────────────────┐
│ Employee Details │
│ ────────────────────────────────────── │
│ ID EMP-00142 │
│ [📊] Role │
│ Developer │
└──────────────────────────────────────────┘
Visual (style1 — expandable)
┌──────────────────────────────────────────┐
│ Collapsible Section ▼ │
│ ────────────────────────────────────── │
│ ▌ Phase 1 │
└──────────────────────────────────────────┘
6. table #
Data table with dynamic columns. Three styles.
Styles
| Style | Visual Description |
|---|---|
default |
Simple DataTable with border, horizontal scroll |
style1 |
Card-shadowed table, primary-tinted header row |
style2 |
Full-width table, solid primary header, alternating row colors |
JSON Schema
{
"type": "table",
"label": "Sales Report",
"style": "default",
"data": [
{"Product": "Widget A", "Qty": "120", "Revenue": "$2,400"},
{"Product": "Widget B", "Qty": "85", "Revenue": "$1,700"}
]
}
data is List<Map<String, String>>. All values must be strings.
Visual (default)
Table (Default)
┌─────────────┬──────┬──────────┐
│ Product │ Qty │ Revenue │
├─────────────┼──────┼──────────┤
│ Widget A │ 120 │ $2,400 │
│ Widget B │ 85 │ $1,700 │
└─────────────┴──────┴──────────┘
Visual (style1)
Sales Report
┌──────────────────────────────────────┐
│ ░ Product │ ░ Qty │ ░ Revenue │ ← primary tint header
├─────────────┼────────┼───────────────┤
│ Widget A │ 120 │ $2,400 │
└─────────────┴────────┴───────────────┘
Visual (style2)
████████████ Product ████████████ ← solid primary header (white text)
│ Widget A │ 120 │ $2,400 │ ← row
│ Widget B │ 85 │ $1,700 │ ← alternating
7. grid #
Grid cards showing metrics. Items have type: "main" or type: "sub". Three styles.
Styles
| Style | Visual Description |
|---|---|
default |
Simple white cards in a grid, value large |
style1 |
Main = gradient card with icon + trend; Sub = white card + badge |
style2 |
Same layout as style1 but sub-cards use color for theming |
JSON Schema
{
"type": "grid",
"style": "style1",
"column": 2,
"data": [
{
"label": "Total Sales",
"type": "main",
"value": "5,280",
"status": "up",
"colorOne": "0xFF1565C0",
"colorTwo": "0xFF0D47A1"
},
{
"label": "Returns",
"type": "sub",
"value": "34",
"badge": "Today"
}
]
}
| Field | Applies to | Description |
|---|---|---|
type |
all | "main" or "sub" |
label |
all | Card label text |
value |
all | Large value displayed |
status |
main/sub | "up" → trending up icon, "down" → trending down |
colorOne |
main | Gradient start (hex: "0xFF1565C0") |
colorTwo |
main | Gradient end (hex: "0xFF0D47A1") |
badge |
sub | Small badge label (e.g. "Today") |
color |
sub | Color key for style2: red,green,etc. |
Visual (style1)
┌──────────────────┐ ┌──────────────────┐
│ [📊] │ │ [📊] │
│ │ │ │
│ Total Sales │ │ Profit │ ← gradient cards (main)
│ 5,280 ↑ │ │ $41K ↑ │
└──────────────────┘ └──────────────────┘
┌──────────────────┐ ┌──────────────────┐
│ [🕐] [Today] │ │ [🕐] [Q4] │
│ Returns │ │ Expenses │ ← white cards (sub)
│ 34 │ │ $12K │
└──────────────────┘ └──────────────────┘
8. array #
List of string items. Three display styles.
Styles
| Style | Visual Description |
|---|---|
default |
Chip tags in a Wrap layout |
style1 |
Bordered container with label icon each |
style2 |
Bullet point list |
JSON Schema
{
"type": "array",
"label": "Tech Stack",
"style": "default",
"data": ["Flutter", "Dart", "Firebase", "Node.js"]
}
Numbers in data are automatically cast to String.
Visual (default)
Tech Stack
[Flutter] [Dart] [Firebase] [Node.js]
Visual (style1)
Features
┌──────────────────────┐ ┌──────────────────────┐
│ 🏷 Real-time sync │ │ 🏷 Offline support │
└──────────────────────┘ └──────────────────────┘
Visual (style2)
Requirements
• Flutter SDK 3.10+
• Dart 3.0+
• Internet connection
9. docs #
File/document viewer (PDF, images). Two styles.
Styles
| Style | Visual Description |
|---|---|
default |
2-column grid of tappable document cards with gradient |
style2 |
Large preview area + horizontal tab list below |
JSON Schema
{
"type": "docs",
"label": "Attachments",
"style": "default",
"data": [
{
"key": "contract",
"title": "Contract Document",
"url": "https://example.com/doc.pdf",
"mime": "application/pdf"
},
{
"key": "photo",
"title": "Profile Photo",
"url": "https://example.com/photo.jpg",
"mime": "image/jpeg"
}
]
}
| Field | Description |
|---|---|
key |
Unique identifier for the document |
title |
Display name (falls back to key) |
url |
Remote URL |
mime |
MIME type: application/pdf, image/png, etc |
Tapping any document opens a modal dialog. For PDF, provide a pdfViewerBuilder — see Custom PDF Viewer.
Visual (default)
┌──────────────────────┐ ┌──────────────────────┐
│ [📄] Contract Doc › │ │ [🖼] Profile Photo › │
└──────────────────────┘ └──────────────────────┘
Visual (style2)
┌──────────────────────────────────────────────────┐
│ │
│ [Large preview area] │
│ │
└──────────────────────────────────────────────────┘
┌──────────────────────┐ ┌──────────────────────┐
│ [📄] Contract Doc │ │ [🖼] Profile Photo │ ← tabs
└──────────────────────┘ └──────────────────────┘
10. action #
Button that fires the onAction callback. Three styles.
Styles
| Style | Visual Description |
|---|---|
default |
Full-width gradient button, square corners |
action1 |
Full-width gradient button, rounded (pill) corners |
action2 |
Full-width outlined gradient-text button |
JSON Schema
{
"type": "action",
"label": "Submit Application",
"style": "default",
"data": {
"route": "/apply",
"id": "form-001",
"code": "APPLY",
"type": "primary",
"baseApi": "/api/v1"
}
}
data.route, data.id, data.code, data.type, data.baseApi are all passed to onAction.
Visual (default)
┌──────────────────────────────────────────────────┐
│ ▓▓▓ Submit Application ▓▓▓ │ ← gradient fill
└──────────────────────────────────────────────────┘
Visual (action1)
( ▓▓▓ View Details ▓▓▓ ) ← rounded pill
Visual (action2)
( - - - - - Cancel Request - - - - - - - - - ) ← outlined + gradient text
11. html #
Renders an HTML string using flutter_widget_from_html.
JSON Schema
{
"type": "html",
"label": "Terms & Conditions",
"style": "default",
"data": "<h2>Terms</h2><p>By using this app...</p><ul><li>Item 1</li></ul>"
}
HTML tags supported: h1–h6, p, ul, ol, li, strong, em, a, img, table, and more.
Visual
Terms & Conditions
Terms
═════
By using this app...
• Item 1
Full JSON Payload Example #
This is the complete test payload demonstrating every component type:
{
"status": true,
"data": {
"id": "0111",
"name": "View data documentation",
"viewData": [
{ "type": "text", "label": "Text (Default)", "data": "This is a default text item.", "style": "default" },
{ "type": "text", "label": "Text (Style 1)", "data": "This is text with style1.", "style": "style1" },
{ "type": "text", "label": "Text (Style 2)", "data": "This is text with style2.", "style": "style2" },
{ "type": "text", "label": "Text (Style 3)", "data": "This is text with style3.", "style": "style3" },
{ "type": "title", "data": "Title (Default)", "style": "default" },
{ "type": "title", "data": "Title (Style 1)", "style": "style1" },
{ "type": "description", "data": "This is a default description.", "style": "default" },
{ "type": "description", "data": "This is a description with style1.", "style": "style1" },
{
"type": "details",
"label": "Details Section",
"style": "default",
"data": { "title": "Detail Title", "description": "Detail Description content goes here." }
},
{
"type": "section",
"label": "Section (Default)",
"style": "default",
"data": [{ "type": "text", "label": "Inside Section", "data": "Nested text item", "style": "default" }]
},
{
"type": "section",
"label": "Section (Style 1 — Collapsible)",
"style": "style1",
"data": [{ "type": "title", "data": "Section Title", "style": "default" }]
},
{
"type": "table",
"label": "Table (Default)",
"style": "default",
"data": [{ "Col1": "Val1", "Col2": "Val2" }, { "Col1": "Val3", "Col2": "Val4" }]
},
{ "type": "table", "label": "Table (Style 1)", "style": "style1", "data": [{ "Header1": "Data1", "Header2": "Data2" }] },
{ "type": "table", "label": "Table (Style 2)", "style": "style2", "data": [{ "Key": "Value", "Status": "Active" }] },
{
"type": "grid",
"style": "default",
"column": 2,
"data": [
{ "label": "Main Item", "type": "main", "value": "100", "colorOne": "0xFFE0E0E0", "colorTwo": "0xFFFFFFFF" },
{ "label": "Sub Item", "type": "sub", "value": "50", "badge": "New" }
]
},
{
"type": "grid",
"style": "style1",
"column": 2,
"data": [
{ "label": "Total Sales", "type": "main", "value": "5,280", "status": "up", "colorOne": "0xFF1565C0", "colorTwo": "0xFF0D47A1" },
{ "label": "Returns", "type": "sub", "value": "34", "badge": "Today" }
]
},
{
"type": "grid",
"style": "style2",
"column": 2,
"data": [
{ "label": "Conversions", "type": "main", "value": "68%" },
{ "label": "Bounce Rate", "type": "sub", "value": "24%", "badge": "Low", "color": "green" }
]
},
{
"type": "docs",
"style": "default",
"data": [
{ "key": "doc1", "title": "Document 1", "url": "https://...", "mime": "application/pdf" },
{ "key": "img1", "title": "Image 1", "url": "https://...", "mime": "image/png" }
]
},
{
"type": "docs",
"style": "style2",
"data": [{ "key": "doc2", "title": "Document 2", "url": "https://...", "mime": "application/pdf" }]
},
{ "type": "action", "label": "Action Button (Default)", "style": "default", "data": { "route": "/some/route", "id": "123", "code": "ACT001", "type": "primary", "baseApi": "/api/v1" } },
{ "type": "action", "label": "Action Button (Style 1)", "style": "action1", "data": { "route": "/style1/route", "id": "456" } },
{ "type": "action", "label": "Action Button (Style 2)", "style": "action2", "data": { "route": "/style2/route", "id": "789" } },
{ "type": "array", "label": "Array (Default)", "style": "default", "data": ["Item 1", "Item 2", "Item 3"] },
{ "type": "array", "label": "Array (Style 1)", "style": "style1", "data": ["Item A", "Item B"] },
{ "type": "array", "label": "Array (Style 2)", "style": "style2", "data": ["First item", "Second item"] },
{ "type": "html", "label": "Html data", "style": "default", "data": "<h1>Hello</h1><p>World</p>" }
]
},
"title": "Test View Data",
"message": "Data fetched successfully.",
"error": ""
}
API Reference #
sduiParser(dynamic source) → List<SduiItem> #
Converts a raw JSON list into typed SduiItem objects.
final items = sduiParser(json['viewData']);
SduiView #
The main rendering widget.
SduiView({
required List<SduiItem> items,
Color? primaryColor, // brand color (defaults to Theme.primaryColor)
SduiActionCallback? onAction, // called when action button tapped
SduiPdfViewerBuilder? pdfViewerBuilder, // optional PDF widget builder
})
SduiItem #
class SduiItem {
final String type; // "text" | "title" | "description" | "details" |
// "section" | "table" | "grid" | "array" |
// "docs" | "action" | "html"
final String label;
final String style; // "default" | "style1" | "style2" | "action1" | "action2"
final int column; // grid column count (default: 2)
final String? color; // color key for colorized components
final dynamic data; // component-specific payload
}
ActionViewData #
Payload delivered to onAction:
class ActionViewData {
final String route;
final String? id;
final String? code;
final String? type;
final String? baseApi;
}
Usage:
SduiView(
items: items,
onAction: (ActionViewData action) {
switch (action.route) {
case '/apply':
Navigator.pushNamed(context, '/apply', arguments: action.id);
break;
case '/cancel':
showCancelDialog(context, id: action.id);
break;
}
},
)
Theming #
primaryColor drives all component theming. Pass it once to SduiView:
SduiView(
items: items,
primaryColor: const Color(0xFF1565C0), // deep blue
)
For nested section components, the theme propagates automatically via SduiScope.
To use the theme outside SduiView:
final primary = SduiScope.of(context).primaryColor;
Custom PDF Viewer #
By default, PDF files show a placeholder. To enable actual PDF rendering, install your preferred viewer (e.g. syncfusion_flutter_pdfviewer) and pass it via pdfViewerBuilder:
# pubspec.yaml
dependencies:
syncfusion_flutter_pdfviewer: ^latest
SduiView(
items: items,
pdfViewerBuilder: (String url) {
return SfPdfViewer.network(url);
},
)
Extending with Custom Types #
To handle component types not built into the plugin (e.g. ytVideo), use SduiItemView directly and wrap it:
class MyAppItemView extends StatelessWidget {
final SduiItem item;
const MyAppItemView({required this.item});
@override
Widget build(BuildContext context) {
if (item.type == 'ytVideo') {
return YoutubePlayer(url: item.data as String);
}
// Fall back to plugin renderer for everything else
return SduiItemView(item: item);
}
}
Then use _SduiViewBody-like logic to build your custom SduiView override.
Component Quick Reference #
| Type | Styles | data payload type |
|---|---|---|
text |
default, style1, style2, style3 | String |
title |
default, style1 | String |
description |
default, style1 | String |
details |
default | { title, description } |
section |
default, style1 | List<SduiItem json> |
table |
default, style1, style2 | List<Map<String, String>> |
grid |
default, style1, style2 | List<GridViewData json> |
array |
default, style1, style2 | List<String> |
docs |
default, style2 | List<{ key, title, url, mime }> |
action |
default, action1, action2 | { route, id, code, type, baseApi } |
html |
default | String (raw HTML) |
License #
MIT