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:
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:
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.yamlfor generator configurationlib/xwidget/inflater_spec.dartfor Flutter widgets and helper classeslib/xwidget/icon_spec.dartfor iconsresources/fragments/for XML UI fragmentsresources/values/for XML value resourcespubspec.yamlasset entries for the resource folderslib/xwidget/generated/for generated registry output
2. Generate Bindings And Register The Schema
Before editing fragment XML, generate the Flutter bindings and XML schema:
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:
<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:
<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.
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:
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:
@override
Widget build(BuildContext context) {
return XWidget.inflateFragment('my_app', Dependencies());
}
Run the app on any Flutter target:
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:
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:
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:
On the first deployment from a project directory, the CLI:
- Creates a cloud project.
- Creates the project's Cloud keys.
- Writes
xwidget_cloud.yamlto the project root with the cloud project ID. - Bundles your XML fragments and value resources.
- Uploads the bundle and mints the version's first revision (
rev 0). - 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:
List what staging is serving:
8. Retrieve Cloud Keys
After the first deploy, retrieve the Cloud keys for the project:
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.
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:
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
- Server-Driven UI - understand the runtime model
- Inflaters - expose Flutter widgets to XML
- Controllers - keep behavior in Dart
- Models - structure runtime state
- Cloud Deployments - deploy and publish UI bundles
- Analytics - understand automatic production signals