rfw_gen 0.5.2 copy "rfw_gen: ^0.5.2" to clipboard
rfw_gen: ^0.5.2 copied to clipboard

Annotations and runtime helpers for converting Flutter Widget code to RFW (Remote Flutter Widgets) format.

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))

Loop Variables in Event Handlers

Use the builder parameter's subscript syntax to reference loop variables in event handler data:

RfwFor(
  items: DataRef('items'),
  itemName: 'item',
  builder: (item) => ListTile(
    title: Text(item['name']),
    onTap: RfwHandler.event('item.select', {
      'itemId': item['id'],
    }),
  ),
)

Generates:

...for item in data.items:
  ListTile(
    title: Text(text: item.name),
    onTap: event "item.select" { itemId: item.id }
  )

Note: Do not use DataRef('item.id') inside a loop — DataRef always generates a data. prefix (e.g., data.item.id), which is a different reference than the loop variable item.id.

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.

  • double.infinity is not supported in RFW. Use SizedBoxExpand to fill available space, or set a fixed size.

  • 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.

0
likes
160
points
384
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Annotations and runtime helpers for converting Flutter Widget code to RFW (Remote Flutter Widgets) format.

Homepage
Repository (GitHub)
View/report issues

Topics

#rfw #remote-flutter-widgets #code-generation

License

BSD-3-Clause (license)

Dependencies

rfw

More

Packages that depend on rfw_gen