Skip to content

Guided Setup

Set up XWidget from generated starter resources, run it locally, deploy the same UI resources to staging, then configure the app to load that deployed bundle.

Use the Quick Start when you only want to see XWidget run. Use this guide when you want to understand the generated files, the local-to-cloud flow, and the pieces you would adjust when adding XWidget to an existing app.

Estimated time: 20-30 minutes for a new starter app, a little longer when adding XWidget to an existing app.

This guide uses the long-form CLI commands so it works without installing a global shortcut. If you install the xc shortcut later, replace dart run xwidget_builder:xc with xc. See CLI Setup to install the short-form command.

Prerequisites:

  • Flutter: >=3.35.0
  • Dart SDK: >=3.9.0 <4.0.0

1. Start With An XWidget App

If you are starting from a blank slate, create a Flutter project, add the XWidget Builder dev dependency, and initialize it with the starter app:

flutter create my_app
cd my_app
flutter pub add dev:xwidget_builder
dart run xwidget_builder:xc init --new-app

The --new-app option replaces Flutter's generated starter app with a working XWidget starter app. This is the recommended path for learning the SDUI flow.

If you already have a Flutter app, add the XWidget Builder dev dependency and initialize the starter resources without replacing your current app entry point:

flutter pub add dev:xwidget_builder
dart run xwidget_builder:xc init

This creates the same XWidget starter resources as --new-app — inflater spec, fragments, value resources, controllers, generated registry structure, and resource asset paths in pubspec.yaml — but it does not overwrite existing project files such as main.dart.

For an existing app, add XWidget initialization to main.dart:

lib/main.dart
import 'package:flutter/material.dart';
import 'package:xwidget/xwidget.dart';

import 'xwidget/generated/registry.g.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await XWidget.initialize(register: registerXWidgetComponents);

  runApp(const MyApp());
}

XWidget provides the runtime. XWidget Builder provides project initialization, code generation, schema generation, and the cloud/analytics CLI.

After either path, you should have the baseline in place. Treat the generated resources as your first working SDUI surface, not an empty template.

init replaces most manual setup. It creates or updates:

  • .xwidget/xwidget_config.yaml for generator configuration
  • lib/xwidget/inflater_spec.dart for Flutter widgets and helper classes
  • lib/xwidget/icon_spec.dart for icons
  • resources/fragments/ for XML UI fragments
  • resources/values/ for XML value resources
  • pubspec.yaml asset entries for the resource folders
  • lib/xwidget/generated/ for generated registry output

2. Generate Bindings And Register The Schema

Before editing fragment XML, generate the Flutter bindings and XML schema:

dart run xwidget_builder:xc generate

The generator creates:

  • widget inflaters for the Flutter classes in your inflater spec
  • controller registration
  • icon lookups
  • the generated XWidget registry
  • XML schemas and a schema catalog in .xwidget/ for completion and validation

xwidget_config.yaml controls spec sources, generated output paths, and resource paths. The defaults point at resources/fragments and resources/values; update the config and pubspec.yaml together if you move those folders.

The IDE plugins register the generated schemas automatically — no manual setup in IntelliJ or VSCode.

Important

Generate and register the schema before modifying fragments. That gives you completion, validation, and inline docs while you author XML.

3. Review Or Modify A Fragment

Fragments are XML UI documents. They live under your configured fragments directory, usually resources/fragments.

The specification files created by init define what fragments can use. Add Flutter widgets or helper classes to lib/xwidget/inflater_spec.dart, and add icons to lib/xwidget/icon_spec.dart. After changing either file, run generate again so the registry and schema know about the new entries. If you installed an XWidget IDE plugin, you can enable auto-generate so spec file changes run the appropriate generation command automatically.

The starter project already includes a complete app fragment. You can use it as-is, or modify it after schema registration:

resources/fragments/my_app.xml
<MaterialApp xmlns="https://xwidget.dev/fragments" title="Flutter Demo">
    <ThemeData for="theme" useMaterial3="true">
        <ColorScheme.fromSeed for="colorScheme" seedColor="@color/primary"/>
    </ThemeData>
    <Controller for="home" name="AppController">
        <Scaffold>
            <AppBar for="appBar" centerTitle="true">
                <Text for="title" data="@string/title"/>
            </AppBar>
            <Center for="body">
                <Column mainAxisAlignment="center">
                    <Text>You have pushed the button this many times:</Text>
                    <ValueListener varName="count">
                        <fragment name="count"/>
                    </ValueListener>
                </Column>
            </Center>
            <FloatingActionButton
                for="floatingActionButton"
                tooltip="increment"
                onPressed="${onPressed}">
                <Icon icon="Icons.add"/>
            </FloatingActionButton>
        </Scaffold>
    </Controller>
</MaterialApp>

The starter app also includes a smaller reusable fragment to showcase nested fragments:

resources/fragments/count.xml
<Text xmlns="https://xwidget.dev/fragments" data="${toString(count)}">
    <TextStyle for="style" fontSize="32" fontWeight="bold"/>
</Text>

4. Keep Behavior In Dart

The fragment owns presentation. The controller owns behavior.

Controllers are Dart classes. They load data, call services, enforce rules, and publish values/functions into Dependencies for XML to read.

lib/xwidget/controllers/app_controller.dart
import 'package:xwidget/xwidget.dart';

class AppController extends Controller {
  var count = 0;

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

  void onPressed() {
    dependencies.setValue('count', ++count);
  }
}

For shaped API data or form state, use Models. For glue values and callbacks, raw Dependencies are usually enough.

If you add new widgets, controllers, or icons, run the generator again:

dart run xwidget_builder:xc generate

5. Run Locally

During development, XWidget loads fragments from local assets by default. The --new-app starter is already wired to render resources/fragments/my_app.xml from lib/main.dart:

lib/main.dart
@override
Widget build(BuildContext context) {
  return XWidget.inflateFragment('my_app', Dependencies());
}

Run the app on any Flutter target:

flutter run

If you are adding XWidget to an existing app, use the same API from one route, tab, or screen body instead of making the whole app server-driven:

XWidget.inflateFragment('home', Dependencies());

At this point you have a Flutter screen whose layout is authored in XML and whose behavior is still compiled Dart.

6. Log In To XWidget Cloud

Cloud commands require authentication with your Google account:

dart run xwidget_builder:xc cloud login

The login flow opens a browser and stores credentials for future CLI commands.

7. Deploy And Publish To Staging

Deploy your local fragments and value resources before switching the app to CloudResources:

dart run xwidget_builder:xc cloud deploy -v 1.0.0 -n "First server-driven home screen"

On the first deployment from a project directory, the CLI:

  1. Creates a cloud project.
  2. Creates the project's Cloud keys.
  3. Writes xwidget_cloud.yaml to the project root with the cloud project ID.
  4. Bundles your XML fragments and value resources.
  5. Uploads the bundle and mints the version's first revision (rev 0).
  6. Offers to publish the new revision to a channel — choose staging (you can create the channel right there).

Deploying alone doesn't change what apps receive; a revision goes live on a channel only when published. If you skipped the publish prompt, publish explicitly:

dart run xwidget_builder:xc cloud publish -c staging -v 1.0.0 -r 0

List what staging is serving:

dart run xwidget_builder:xc cloud deployment list -c staging

8. Retrieve Cloud Keys

After the first deploy, retrieve the Cloud keys for the project:

dart run xwidget_builder:xc cloud project keys

These values are passed to CloudResources as projectKey and storageKey. Store them in your app's remote configuration, feature flag system, secure configuration service, or startup config API. Do not leave them as literals in source code.

9. Configure CloudResources

Now switch the app to CloudResources.

Resolve the Cloud keys, channel, and version from remote configuration, feature flags, user cohorts, or a startup config API. This lets you steer beta users, internal testers, rollout groups, and A/B testing cohorts to different deployed UI bundles.

lib/main.dart
import 'package:flutter/material.dart';
import 'package:xwidget/xwidget.dart';

import 'xwidget/generated/registry.g.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final xwidgetConfig = await loadXWidgetConfig();

  await XWidget.initialize(
    register: registerXWidgetComponents,
    resources: CloudResources(
      projectKey: xwidgetConfig.projectKey,
      storageKey: xwidgetConfig.storageKey,
      channel: xwidgetConfig.channel, // i.e. staging
      version: xwidgetConfig.version, // i.e. 1.0.0
    ),
  );

  runApp(const MyApp());
}

With remote config resolving to staging / 1.0.0, the next app start downloads that bundle, stores it locally, and renders the deployed fragments. Once a bundle has been downloaded, XWidget continues rendering from that local copy. It checks for a newer bundle the next time the app starts.

Local resources are still available as a safety net. If the network request or bundle download fails, CloudResources first tries the downloaded bundle. If no downloaded bundle is available, it falls back to the fragments and values bundled in the app's assets. Keep a baseline resource set in the app binary so first launch and offline startup still have UI to render.

See CloudResources for caching, fallback, and key-rotation guidance.

10. Publish To Production

After testing the exact staging bundle, publish the same revision to production:

dart run xwidget_builder:xc cloud publish -c production -v 1.0.0 -r 0

Publishing points production at the tested revision — nothing is copied or re-uploaded, so production serves byte-for-byte what you verified on staging. Your production users receive it when their app resolves the production channel and version and restarts.

11. Monitor The Update

XWidget Cloud tracks downloads, renders, errors, and navigation events for cloud-loaded resources.

dart run xwidget_builder:xc analytics downloads
dart run xwidget_builder:xc analytics renders
dart run xwidget_builder:xc analytics errors
dart run xwidget_builder:xc analytics transitions

Use these signals to confirm that users are receiving the bundle, screens are rendering, and any fragment or download errors are visible.

Next Steps