Tips and Tricks
Regenerate inflaters after upgrading Flutter
Generated inflaters call the widget constructors of the Flutter version that produced them. After a Flutter upgrade, regenerate so changed signatures, new parameters, and removed deprecations are reflected — otherwise the mismatch surfaces as runtime errors in inflated fragments:
Or without the xc command:
Regenerate after config changes
Changing fragmentsPath or valuesPath in xwidget_config.yaml requires regeneration —
the paths are baked into registry.g.dart:
The registry regenerates with every run, even when --only specifies another component.
Your main() doesn't need to change.
Switch between local and cloud resources at runtime
For debugging cloud-delivered UI against a local override, or for conditional cloud usage,
pass different Resources instances based on configuration:
const useCloud = bool.fromEnvironment('USE_CLOUD', defaultValue: false);
await XWidget.initialize(
register: registerXWidgetComponents,
resources: useCloud
? CloudResources(
projectKey: '...',
storageKey: '...',
channel: 'production',
version: '1.0.0',
)
: LocalResources(),
);
Build with --dart-define=USE_CLOUD=true to opt into cloud.
Use --schema-docs html for IntelliJ-based IDEs
If you're developing with Android Studio or IntelliJ IDEA, generate the schema with HTML doc formatting for readable tooltips:
Or override for a single run:
See Code Generation Overview for all generator flags.
Use controllers to create reusable components
A controller bound to a fragment travels with it — inflate the same fragment from three screens and each gets the fragment's behavior without duplicating any Dart. Keep the logic in the controller and the fragment purely presentational, and the pair becomes a reusable component.
See Controllers.
Declare model collections as <String, dynamic>
When something listens to a value — <ValueListener> in a fragment, or
listenForChanges in Dart — XWidget wraps that value in a ModelValueNotifier and
stores the wrapper back into the enclosing collection. If Dart inferred a narrow type
for that collection (say Map<String, Map<String, String>>), the wrapper doesn't fit
and the write throws a runtime type error:
// avoid — Dart infers Map<String, Map<String, String>>
final model = Model({
"users": {
"user1": { "email": "@", "phone": "0" },
"user2": { "email": "@", "phone": "0" }
}
});
model.listenForChanges("users.user1", null, null); // throws — wrapper doesn't fit
Declare the collections loosely so the notifier wrapper always fits:
// ok — explicitly <String, dynamic>
final model = Model({
"users": <String, dynamic>{
"user1": <String, dynamic>{ "email": "@", "phone": "0" },
"user2": <String, dynamic>{ "email": "@", "phone": "0" }
}
});
model.listenForChanges("users.user1", null, null); // works
In practice you rarely call listenForChanges directly — <ValueListener> does it
for you — but the typing rule applies either way. Data loaded through registered
PropertyTransformers doesn't have this
problem; it's a concern for maps you build by hand.