home / skills / madteacher / mad-agents-skills / flutter-duit-bdui

flutter-duit-bdui skill

/flutter-duit-bdui

This skill helps you integrate Duit into Flutter apps by wiring drivers, transports, and backend-driven UI with reusable components.

npx playbooks add skill madteacher/mad-agents-skills --skill flutter-duit-bdui

Review the files below or copy the command above to add this skill to your agents.

Files (5)
SKILL.md
8.4 KB
---
name: flutter-duit-bdui
description: Integrate Duit framework into Flutter applications including setup, driver configuration, HTTP/WebSocket transports, custom widgets, and themes. Use when integrating backend-driven UI, configuring Duit, or adding Duit to Flutter applications.
---

# Fluttter 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

1. Add dependency to pubspec.yaml
2. Initialize DuitRegistry (optional: with themes/custom widgets)
3. Create XDriver (HTTP, WebSocket, or static)
4. Wrap UI in DuitViewHost
5. Server sends JSON layouts → Duit renders them

## Prerequisites

### SDK Requirements

```yaml
- Dart SDK: >=3.4.4 <4.0.0
- Flutter: >=3.24.0
```

### Add Dependency

```bash
flutter pub add flutter_duit
```

Install:

```bash
flutter pub get
```

## Basic Integration

### Minimal Setup

```dart
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:

```dart
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:

```dart
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:

```dart
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:

```dart
final driver = XDriver.static(
  layoutJson,
);
```

### Custom Decoder/Encoder

```dart
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:

```dart
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

```dart
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.

```dart
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 `data` field
- **objectKey**: Key in the `data` object
- **attributeKey**: Attribute in the widget to bind to
- **defaultValue**: Optional default value if data key is missing

You can register multiple components at once:

```dart
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](./references/capabiliteis.md) — Notes about capability-based design and core framework parts overriding
- [troubleshooting.md](./references/troubleshooting.md) - Notes about common issues in framework integration
- [environvent_vars.md](./references//environment_vars.md) — Notes about avalilable env variables and its usage
- [public_api.md](./references/public_api.md) — Notes about driver public API
- <https://duit.pro/docs/en> — official documentation site

Overview

This skill integrates the Duit backend-driven UI framework into Flutter applications to render layouts and handle server-driven interactions. It provides setup guidance, driver and transport configuration (HTTP, WebSocket, or static), custom widget registration, theme and component registration, and lifecycle management. Use it to let a server control UI layout and updates without shipping new app builds.

How this skill works

The skill configures an XDriver to receive JSON view definitions from a transport (HTTP, WebSocket, or static). DuitRegistry is used to register custom widgets, themes, and reusable components. DuitViewHost wraps your Flutter UI and streams layout updates to Flutter widgets, while drivers handle message decoding, reconnection, and remote actions.

When to use it

  • You need backend-driven UI so layouts update without app releases.
  • Integrating flutter_duit into an existing Flutter app.
  • Building custom widgets that the server can reference by type.
  • Configuring transport: polling REST endpoints or real-time WebSocket feeds.
  • Registering reusable components and templates on app startup.

Best practices

  • Register custom widgets and components during app initialization via DuitRegistry before runApp.
  • Always manage driver lifecycle: create drivers in initState and dispose them in dispose to avoid leaks.
  • Use static drivers or mock transports for local development and tests to isolate server behavior.
  • Provide custom decoders/encoders if your API uses nonstandard payload formats or binary envelopes.
  • Secure transports: attach auth headers and handle token refresh in transport managers.

Example use cases

  • Render marketing or layout changes controlled by the backend without an app update (A/B layouts, promos).
  • Real-time dashboards updated via WebSocket transport for live data feeds.
  • Local testing with XDriver.static to iterate UI templates without server dependencies.
  • Create a CardComponent template and supply different data payloads from the server to populate lists.
  • Implement a custom transport to integrate with an internal messaging system or specialized API.

FAQ

How do I keep drivers from leaking resources?

Create drivers in initState and call driver.dispose() in dispose(); avoid creating drivers repeatedly in build methods.

When should I use WebSocket vs HTTP transport?

Use HTTP for request/response or polling scenarios and WebSocket for bidirectional real-time updates or low-latency push interactions.

Can I pass custom data formats from the server?

Yes. Implement custom Converter/decoder and supply it in transport options to transform incoming payloads into the expected JSON map.