Skip to content

What is XWidget?

XWidget is server-driven UI for Flutter apps.

Ship UI changes as versioned resource bundles instead of waiting on app-store builds and user updates. Layout, copy, styling, navigation, feature surfaces, test variants — it all updates live from XWidget Cloud, while your compiled Flutter app keeps control of the things that should never leave the binary: services, credentials, permissions, business rules, and native integrations.

The short version

XML is the markup. Bundles are the transport. Flutter remains the runtime.

Server-Driven Flutter

Flutter gives developers a fast feedback loop during development. Production is usually slower: even small UI changes can require a new build, app store review, gradual user adoption, and coordination across platforms.

XWidget moves presentation resources out of the app binary and into deployable UI bundles. Your app starts with a registered set of Flutter widgets, controllers, icons, functions, and model types. At runtime, XWidget loads XML fragments and value resources, evaluates expressions, and inflates native Flutter widgets.

That gives you a controlled SDUI surface:

  • update UI without an app store release
  • deploy to staging before production
  • publish known-good revisions to any channel
  • run UI experiments, rollouts, and A/B tests from resources
  • keep remote UI inside the capabilities compiled into the app
  • continue using Flutter widgets, custom widgets, and third-party package widgets

Flutter First

XWidget is designed to work with the Flutter code you already write.

You choose which Flutter classes should be available in XML, and XWidget Builder generates the inflaters and XML schema for those classes. That means your markup can use Flutter SDK widgets, your own app widgets, and widgets from third-party packages.

lib/xwidget/inflater_spec.dart
import 'package:flutter/material.dart';
import 'package:my_app/widgets/revenue_chart.dart';

const inflaters = [
  Button,
  Column,
  Container,
  Padding,
  RevenueChart,
  Row,
  Text,
  TextStyle,
];

Run the generator, and those classes become XML elements with schema-backed attribute completion:

dart run xwidget_builder:generate

The generated binding is based on the Flutter and package versions in your app. If your app can compile it, XWidget can expose it.

Markup That Looks Like UI

XWidget fragments are XML documents. XML is used as authored markup because UI is tree-shaped, nested, and readable when the document mirrors the widget tree.

resources/fragments/pages/overview.xml
<Controller name="OverviewController" xmlns="https://xwidget.dev/fragments">
    <ValueListener varName="overview">
        <Column crossAxisAlignment="start">
            <Text data="${overview.title}">
                <TextStyle for="style" fontSize="28" fontWeight="bold" />
            </Text>

            <Text data="${overview.subtitle}" />

            <Button onPressed="${refreshOverview}">
                <Text>Refresh</Text>
            </Button>
        </Column>
    </ValueListener>
</Controller>

With the generated XML schema registered in your IDE, fragments get completion, validation, and inline documentation while you author them.

Fragments can include widgets, resources, expressions, controllers, conditions, loops, callbacks, and nested fragments. They are small enough to review, easy to diff, and natural to deploy as versioned UI resources.

State With Boundaries

Server-driven UI should not mean server-driven everything.

XWidget keeps behavior in Dart and presentation in resources. Controllers load data, call services, enforce permissions, mutate state, and publish values or functions into Dependencies. XML listens and reacts.

Use Dependencies for glue values and callbacks. Use Model when data has shape: API records, page state, forms, validation, type conversion, or nested objects.

lib/xwidget/controllers/overview_controller.dart
class OverviewController extends Controller {
  @override
  Future<void> init() async {
    dependencies.setValue('overview', OverviewState.loading());

    final usage = await api.getUsage();

    dependencies.setValue('overview', OverviewState.fromUsage(usage));
  }

  @override
  void bindDependencies() {
    dependencies.setValue('refreshOverview', refreshOverview);
  }

  Future<void> refreshOverview() async {
    final usage = await api.getUsage();
    dependencies.setValue('overview', OverviewState.fromUsage(usage));
  }
}

This boundary keeps production UI flexible without turning remote resources into an unrestricted scripting environment.

Deploy Through Channels

XWidget Cloud treats UI bundles as versioned, immutable deployment artifacts.

A deployment is a bundle of XML fragments and value resources; each deploy of a version mints a new numbered revision. Channels are named targets such as development, staging, and production that point at the revision they serve — publishing moves the pointer.

# Deploy a candidate bundle (mints revision 0 of 1.4.0).
xc cloud deploy -v 1.4.0 -n "New project settings flow"

# Publish it to staging.
xc cloud publish -c staging -v 1.4.0 -r 0

# Inspect what staging is serving.
xc cloud deployment list -c staging

# Publish the same tested revision to production.
xc cloud publish -c production -v 1.4.0 -r 0

Your app resolves the channel and version it should load from remote configuration, feature flags, user cohorts, or a startup config API. That lets you steer beta users, internal testers, rollout groups, and A/B testing cohorts to different deployed UI bundles.

lib/main.dart
final remoteConfig = await fetchRemoteConfig();

await XWidget.initialize(
  register: registerXWidgetComponents,
  resources: CloudResources(
    projectKey: '<your-project-key>',
    storageKey: '<your-storage-key>',
    channel: remoteConfig.xwidgetChannel, // i.e. production
    version: remoteConfig.xwidgetVersion, // i.e. 1.5.0
  ),
);

Because channels and versions map to deployed bundles, teams can control what is available remotely while the app decides which users receive which UI.

That gives teams an operational workflow for production UI:

  1. Author fragments and values.
  2. Generate bindings and schema.
  3. Deploy to a non-production channel.
  4. Test the exact deployed bundle.
  5. Promote that same version to production.
  6. Use analytics and error data to see how the UI behaves in the wild.

What Can Change Remotely

XWidget is strongest when you use the remote layer for presentation and flow, while keeping sensitive logic compiled into the app.

Remote resources Compiled Flutter app
Layout and composition API clients and credentials
Copy and static values Authentication and authorization
Colors and style resources Business rules and validation
Fragment selection Native platform integrations
Navigation structure Custom widgets and controllers
Feature surfaces and experiments Payment, storage, and permissions

The app decides what is possible. The deployed resources decide how the allowed surface is presented.

Start Small, Scale Up

XWidget can be introduced gradually. Use it for one screen, one feature area, a settings flow, a marketing surface, or a full application shell. Local resources work well during development and for bundled fallback UI. Cloud resources add deployment, channels, downloads, analytics, and production visibility.

Use XWidget when you want:

  • production UI updates without rebuilding the app
  • the freedom to use real Flutter and third-party widgets
  • readable XML markup for UI documents
  • typed Dart controllers for behavior
  • structured models for runtime state
  • versioned deployments through channels
  • automatic render, download, error, and transition analytics

Next Steps