rfw_gen

A code generation package that converts Flutter Widget code into Remote Flutter Widgets (RFW) format. Annotate top-level functions with @RfwWidget, run build_runner, and get .rfwtxt and .rfw files ready for server-driven UI.

Installation

Add rfw_gen to your dependencies and rfw_gen_builder + build_runner to your dev dependencies:

dependencies:
  rfw: ^1.0.17
  rfw_gen: ^0.4.0

dev_dependencies:
  rfw_gen_builder: ^0.4.0
  build_runner: ^2.4.0

Note: The rfw package is required at runtime to render Remote Flutter Widgets. rfw_gen and rfw_gen_builder handle code generation only.

Quick Start

  1. Write a widget function and annotate it with @RfwWidget:
import 'package:flutter/material.dart';
import 'package:rfw_gen/rfw_gen.dart';

@RfwWidget('greeting')
Widget buildGreeting() {
  return Container(
    color: Color(0xFF2196F3),
    child: Text('Hello, RFW!'),
  );
}
  1. Run the code generator:
dart run build_runner build
  1. The generator produces two files:
    • greeting.rfwtxt -- human-readable RFW text format
    • greeting.rfw -- binary format for production use

Supported Widgets

65 widgets are supported across two categories.

Core Widgets (~48)

  • Layout: Align, AspectRatio, Center, Column, Expanded, Flexible, FittedBox, FractionallySizedBox, IntrinsicHeight, IntrinsicWidth, Row, SizedBox, SizedBoxExpand, SizedBoxShrink, Spacer, Stack, Wrap
  • Scrolling: GridView, ListBody, ListView, SingleChildScrollView
  • Styling: ClipRRect, ColoredBox, Container, DefaultTextStyle, Directionality, Icon, IconTheme, Image, Opacity, Padding, Placeholder, Text
  • Transform: Positioned, Rotation, Scale
  • Interaction: GestureDetector
  • Other: AnimationDefaults, SafeArea

Material Widgets (~17)

AppBar, Card, CircularProgressIndicator, Divider, Drawer, ElevatedButton, FloatingActionButton, InkWell, LinearProgressIndicator, ListTile, Material, OutlinedButton, OverflowBar, Scaffold, Slider, TextButton, VerticalDivider

See rules/rfw-widgets.md for the full parameter reference.

Dynamic Features

Data Binding

Reference server-supplied data with DataRef:

Text(DataRef('user.name'))

Generates: Text(text: data.user.name)

Args

Reference widget constructor arguments with ArgsRef:

Text(ArgsRef('product.id'))

Generates: Text(text: args.product.id)

State

Declare local widget state and reference it with StateRef:

@RfwWidget('toggle', state: {'isActive': false})
Widget buildToggle() {
  return Container(
    color: RfwSwitchValue(
      value: StateRef('isActive'),
      cases: {true: Color(0xFF4CAF50), false: Color(0xFFE0E0E0)},
    ),
  );
}

Loops

Iterate over lists with RfwFor:

RfwFor(
  items: DataRef('items'),
  itemName: 'item',
  builder: (item) => ListTile(
    title: Text(item['name']),
  ),
)

Generates: ...for item in data.items: ListTile(title: Text(text: item.name))

Conditionals

Use RfwSwitch for child widgets and RfwSwitchValue for values:

RfwSwitch(
  value: StateRef('status'),
  cases: {
    'loading': CircularProgressIndicator(),
    'done': Text('Complete'),
  },
)

String Concatenation

Combine static text and dynamic references with RfwConcat:

Text(RfwConcat(['Hello, ', DataRef('name'), '!']))

Generates: Text(text: ["Hello, ", data.name, "!"])

Event Handlers

Mutate local state:

GestureDetector(
  onTap: RfwHandler.setState('pressed', true),
)

Generates: onTap: set state.pressed = true

Dispatch events to the Flutter host:

GestureDetector(
  onTap: RfwHandler.event('cart.add', {'itemId': 42}),
)

Generates: onTap: event "cart.add" { itemId: 42 }

Toggle a boolean state value using RfwSwitchValue:

@RfwWidget('toggle', state: {'isActive': false})
Widget buildToggle() {
  return GestureDetector(
    onTap: RfwHandler.setState('isActive', RfwSwitchValue<bool>(
      value: StateRef('isActive'),
      cases: {true: false, false: true},
    )),
    child: Container(
      color: RfwSwitchValue<int>(
        value: StateRef('isActive'),
        cases: {true: 0xFF4CAF50, false: 0xFFE0E0E0},
      ),
    ),
  );
}

Generates:

onTap: set state.isActive = switch state.isActive {
  true: false,
  false: true,
},

Custom Widgets

Custom widgets are automatically detected and supported. When you use a non-built-in widget inside an @RfwWidget function, the generator:

  1. Discovers the custom widget via AST analysis
  2. Resolves its constructor using the Dart analyzer (WidgetResolver)
  3. Generates a LocalWidgetBuilder bridge (.rfw_library.dart)
  4. Emits widget metadata (.rfw_meta.json) for MCP/tooling
// 1. Define your custom widget
class CustomText extends StatelessWidget {
  const CustomText({super.key, required this.text, this.fontSize = 14.0});
  final String text;
  final double fontSize;

  @override
  Widget build(BuildContext context) => Text(text, style: TextStyle(fontSize: fontSize));
}

// 2. Use it in an @RfwWidget function — no config file needed
@RfwWidget('customDemo')
Widget buildCustomDemo() {
  return CustomText(text: 'Hello', fontSize: 24.0);
}

Running build_runner generates:

  • custom_demo.rfwtxt / .rfw — RFW output with CustomText references
  • .rfw_library.dartMap<String, LocalWidgetBuilder> that wires RFW's DataSource to your widget's constructor
  • .rfw_meta.json — machine-readable parameter/child/handler metadata

No rfw_gen.yaml configuration is required (removed in 0.4.0).

Generated Output

For each .dart file containing @RfwWidget functions, the generator produces:

File Purpose
.rfwtxt Human-readable RFW text format
.rfw Binary format for production use
.rfw_library.dart LocalWidgetBuilder map for custom widgets
.rfw_meta.json Widget metadata (params, child type, handlers)

Rendering Generated Widgets

After running build_runner, use the generated .rfw file with the rfw package to render widgets:

import 'package:flutter/material.dart';
import 'package:rfw/rfw.dart';

class RfwScreen extends StatefulWidget {
  const RfwScreen({super.key});

  @override
  State<RfwScreen> createState() => _RfwScreenState();
}

class _RfwScreenState extends State<RfwScreen> {
  final Runtime _runtime = Runtime();
  final DynamicContent _data = DynamicContent();

  @override
  void initState() {
    super.initState();
    // Register built-in widget libraries
    _runtime.update(const LibraryName(<String>['core', 'widgets']), createCoreWidgets);
    _runtime.update(const LibraryName(<String>['material']), createMaterialWidgets);

    // Load the generated .rfw binary
    rootBundle.load('assets/greeting.rfw').then((bytes) {
      _runtime.update(
        const LibraryName(<String>['main']),
        decodeLibraryBlob(bytes.buffer.asUint8List()),
      );
      setState(() {});
    });

    // Supply dynamic data
    _data.update('user', <String, Object>{'name': 'World'});
  }

  @override
  Widget build(BuildContext context) {
    return RemoteWidget(
      runtime: _runtime,
      data: _data,
      widget: const FullyQualifiedWidgetName(
        LibraryName(<String>['main']),
        'greeting',
      ),
    );
  }
}

For more details, see the rfw package documentation.

Limitations

  • @RfwWidget must be applied to top-level functions only.

  • Only the 65 built-in widgets (core + material) are supported out of the box. Other widgets are auto-detected and require their source to be importable.

  • RfwSwitchValue / RfwSwitch cannot be used in handler positions (e.g., onTap, onPressed). This is an RFW runtime limitation — handlers only support setState, setStateFromArg, and event. To conditionally switch behavior, use RfwSwitch to swap entire widgets:

    // ❌ This does NOT work:
    onPressed: RfwSwitchValue(value: StateRef('mode'), cases: {
      true: RfwHandler.event('pause', {}),
      false: RfwHandler.event('start', {}),
    })
    
    // ✅ Use RfwSwitch to swap widgets instead:
    RfwSwitch(
      value: StateRef('mode'),
      cases: {
        true: ElevatedButton(
          onPressed: RfwHandler.event('pause', {}),
          child: Text('Pause'),
        ),
        false: ElevatedButton(
          onPressed: RfwHandler.event('start', {}),
          child: Text('Start'),
        ),
      },
    )
    

Pre-1.0 Note

This package is pre-1.0. Minor version bumps may include breaking changes. Pin to a specific version if stability is critical for your project.

Libraries

rfw_gen