flutter-duit-bdui
Scannednpx machina-cli add skill MADTeacher/mad-agents-skills/flutter-duit-bdui --openclawFluttter Duit Backend-driven UI
Overview
Duit enables backend-driven UI in Flutter applications. The server controls both data and layout via JSON, allowing UI updates without app releases.
Quick Start
- Add dependency to pubspec.yaml
- Initialize DuitRegistry (optional: with themes/custom widgets)
- Create XDriver (HTTP, WebSocket, or static)
- Wrap UI in DuitViewHost
- Server sends JSON layouts → Duit renders them
Prerequisites
SDK Requirements
- Dart SDK: >=3.4.4 <4.0.0
- Flutter: >=3.24.0
Add Dependency
flutter pub add flutter_duit
Install:
flutter pub get
Basic Integration
Minimal Setup
import 'package:flutter/material.dart';
import 'package:flutter_duit/flutter_duit.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: DuitViewHost.withDriver(
driver: XDriver.static({
"type": "Text",
"id": "1",
"attributes": {"data": "Hello, World!"},
}),
),
),
);
}
}
Driver Lifecycle Management
Always dispose drivers to prevent memory leaks:
class MyWidgetState extends State<MyWidget> {
late final XDriver driver;
@override
void initState() {
super.initState();
driver = XDriver.static(/* ... */);
}
@override
void dispose() {
driver.dispose();
super.dispose();
}
}
Transport Configuration
HTTP Transport
Fetch layouts from REST API endpoints:
final driver = XDriver(
transportManager: HttpTransportManager(
options: HttpTransportOptions(
baseUrl: 'https://api.example.com/view',
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
),
),
);
WebSocket Transport
Real-time bidirectional communication:
final driver = XDriver(
transportManager: WSTransportManager(
options: WSTransportOptions(
url: 'wss://api.example.com/ws',
headers: {
'Authorization': 'Bearer $token',
},
reconnectInterval: Duration(seconds: 5),
heartbeatInterval: Duration(seconds: 30),
),
),
);
Static/Stub Transport
For testing or local layouts:
final driver = XDriver.static(
layoutJson,
);
Custom Decoder/Encoder
import 'dart:convert';
import 'dart:typed_data';
class CustomDecoder extends Converter<Uint8List, Map<String, dynamic>> {
@override
Map<String, dynamic> convert(Uint8List input) {
// Custom decode logic
return jsonDecode(utf8.decode(input));
}
}
final driver = XDriver(
transportManager: HttpTransportManager(
options: HttpTransportOptions(
baseUrl: 'https://api.example.com',
decoder: CustomDecoder(),
),
),
);
Custom Transport
Create your own transport implementation if needed:
class MyCustomTransportManager with TransportCapabilityDelegate {
@override
void linkDriver(UIDriver driver) {
// Implement linkDriver method
}
@override
Stream<Map<String, dynamic>> connect({
Map<String, dynamic>? initialRequestData,
Map<String, dynamic>? staticContent,
}) async* {
// Implement connect method
}
@override
Future<Map<String, dynamic>?> executeRemoteAction(
ServerAction action,
Map<String, dynamic> payload,
) async {
//Implement executeRemoteAction method
}
@override
Future<Map<String, dynamic>?> request(
String url,
Map<String, dynamic> meta,
Map<String, dynamic> body,
) async {
//Implement request method
}
@override
void releaseResources() {
// Implement linkDriver method
}
}
Custom Widgets
Create and register Custom Widget
import 'package:flutter_duit/flutter_duit.dart';
// 1. Define custom widget
class MyCustomWidget extends StatelessWidget {
final ViewAttribute attributes;
const MyCustomWidget({
required this.attributes,
super.key,
});
@override
Widget build(BuildContext context) {
final attrs = attributes.payload;
return Container(
child: Text(attrs.getString(key: "message")),
);
}
}
// 2. Create build factory fn for widget
Widget myCustomBuildFactory(ElementPropertyView model) {
if (model.isControlled) {
return MyCustomWidget(
attributes: model.attributes,
);
} else {
return const SizedBox.shrink();
}
}
// 3. Register build-fn
void main() async {
WidgetsFlutterBinding.ensureInitialized();
DuitRegistry.register(
"MyCustomWidget",
buildFactory: myCustomBuildFactory,
);
runApp(const MyApp());
}
Components
Components registration
Components allow you to create reusable UI templates that can be referenced by a tag and populated with dynamic data.
import 'package:flutter_duit/flutter_duit.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Define component template
final cardComponent = {
"tag": "CardComponent",
"layoutRoot": {
"type": "Container",
"id": "cardContainer",
"controlled": false,
"attributes": {
"padding": {"all": 16},
"margin": {"all": 8},
"decoration": {
"borderRadius": 12,
"color": "#FFFFFF",
"boxShadow": [
{
"color": "#00000033",
"blurRadius": 6,
"offset": {"dx": 0, "dy": 2},
},
],
},
},
"children": [
{
"type": "Text",
"id": "cardTitle",
"controlled": false,
"attributes": {
"data": {
"style": {
"fontSize": 18,
"fontWeight": "w600",
"color": "#333333",
},
},
"refs": [
{
"objectKey": "title",
"attributeKey": "data",
},
],
},
},
{
"type": "Text",
"id": "cardDescription",
"controlled": false,
"attributes": {
"data": {
"style": {
"fontSize": 14,
"color": "#666666",
},
},
"refs": [
{
"objectKey": "description",
"attributeKey": "data",
},
],
},
},
],
},
};
// Register the component
await DuitRegistry.registerComponents([cardComponent]);
runApp(const MyApp());
}
// Usage in JSON layout from server:
// {
// "type": "Component",
// "id": "card1",
// "tag": "CardComponent",
// "data": {
// "title": "Hello World",
// "description": "This is a card component"
// }
// }
Key concepts:
- tag: Unique identifier for the component
- layoutRoot: Root element of the component template
- refs: References to dynamic data passed via the
datafield - objectKey: Key in the
dataobject - attributeKey: Attribute in the widget to bind to
- defaultValue: Optional default value if data key is missing
You can register multiple components at once:
await DuitRegistry.registerComponents([
cardComponent,
buttonComponent,
listItemComponent,
]);
When to Use This Skill
Use this skill when:
- Integration flutter_duit library into project
- Custom widet creation
- Components registration
- Basic framework behavior overriding via capabilities implementation
- Need help with the framework API
Resources
Reference Documentation
- capabilities.md — Notes about capability-based design and core framework parts overriding
- troubleshooting.md - Notes about common issues in framework integration
- environvent_vars.md — Notes about avalilable env variables and its usage
- public_api.md — Notes about driver public API
- https://duit.pro/docs/en — official documentation site
Source
git clone https://github.com/MADTeacher/mad-agents-skills/blob/main/flutter-duit-bdui/SKILL.mdView on GitHub Overview
Duit enables backend-driven UI in Flutter apps by letting a server control both data and layout through JSON. This skill covers the end-to-end setup—from pubspec dependency to DuitRegistry themes and widgets—driver configuration, transport options (HTTP, WebSocket, or static), and rendering UI without frequent app updates.
How This Skill Works
DuitViewHost wraps the Flutter UI and renders layouts provided by a configured XDriver. The server sends JSON layouts and data, which Duit translates into Flutter widgets. Optional theming and custom widgets can be registered via DuitRegistry to tailor the UI.
When to Use It
- Integrate a backend-driven UI where layout and data come from a server via JSON
- Configure Duit with HTTP or WebSocket transports
- Test layouts locally using static/Stub transport
- Customize themes or widgets through DuitRegistry
- Implement custom transport, decoder, or encoder if needed
Quick Start
- Step 1: Add the flutter_duit dependency to pubspec.yaml and (optionally) initialize DuitRegistry with themes or custom widgets
- Step 2: Create an XDriver using a transport (HTTP, WebSocket, or static) for layouts
- Step 3: Wrap your UI with DuitViewHost and let the server-sent JSON layouts render, disposing the driver when done
Best Practices
- Always dispose drivers to prevent memory leaks
- Wrap your UI with DuitViewHost when rendering server-driven layouts
- Initialize DuitRegistry with optional themes or custom widgets
- Choose the appropriate transport (HTTP, WebSocket, or static) for your use case
- Use a custom decoder/encoder or a custom transport if you need special formats
Example Use Cases
- Minimal setup with a static driver rendering a simple JSON layout
- HTTP transport fetching layouts from a REST endpoint
- WebSocket transport enabling real-time UI updates
- Static/Stub transport for offline testing and UI experimentation
- Custom decoder or encoder to handle a non-standard payload