Skip to content

Core Classes

XWidget EL includes two foundational classes that power expression evaluation and data management: Dependencies and Model. These classes are part of the xwidget_el package and can be used independently of XWidget.

Dependencies

The Dependencies class is a key-value store that serves as the data context for expression evaluation. When an EL expression references a variable like ${user.name}, the value is resolved from a Dependencies instance.

Basic Usage

final deps = Dependencies();
deps.setValue("user.name", "Mike");
deps.setValue("user.email", "[email protected]");

final parser = ELParser();
final name = parser.evaluate("user.name", deps);  // -> "Mike"

Dot/Bracket Notation

Values can be read and written using dot and bracket notation for nested access. Reads resolve to null if the path doesn't exist. Writes create the necessary structures automatically.

final deps = Dependencies();
deps.setValue("users[0].name", "Alice");
deps.setValue("users[1].name", "Bob");

deps.getValue("users[0].name");  // -> "Alice"
deps.getValue("users[2].name");  // -> null

Global Data

Prefix a key with global. to store data that is shared across all Dependencies instances.

final deps1 = Dependencies();
deps1.setValue("global.theme", "dark");

final deps2 = Dependencies();
deps2.getValue("global.theme");  // -> "dark"

Change Notifications

Wrap a value in a ValueNotifier to receive change notifications. This is the mechanism behind XWidget's reactive <ValueListener> component.

final deps = Dependencies();
deps.setValue("count", 0);

final notifier = deps.listenForChanges("count");
notifier.addListener(() {
  print("count changed to ${notifier.value}");
});

deps.setValue("count", 1);  // triggers listener

Custom Functions

Any Dart function stored in Dependencies becomes callable from EL expressions. Functions can only accept positional arguments.

deps.setValue("greet", (String name) => "Hello, $name!");
${greet('Alice')}  // -> "Hello, Alice!"

For full documentation, see Dependencies.

Model

The Model class is a structured data container built on top of Map<String, dynamic>. It provides property access, null safety, data transformation, type conversion, and instance management.

Basic Usage

class Profile extends Model {
  String get username => getValue("username!");
  String get email => getValue("email!");
  String? get name => getValue("name");

  Profile(super.data, {super.translation, super.immutable});
}

final profile = Profile({
  "username": "mike.smith",
  "email": "[email protected]",
  "name": "Mike Smith",
});

The ! suffix on getValue("username!") asserts the value is non-null — an exception is thrown if it's missing.

Property Transformers

Register models with PropertyTransformers to define the target structure and enable automatic type conversion when loading data.

Models.register<Profile>(Profile.new, const [
  PropertyTransformer<String>("username"),
  PropertyTransformer<String>("email"),
  PropertyTransformer<String?>("name"),
  PropertyTransformer<DateTime?>("lastLogin"),
]);

Property Translation

Map source data structures to your model when the source keys don't match your model's properties.

final profile = Profile({
  "user_name": "mike.smith",
  "user_email": "[email protected]",
}, translation: PropertyTranslation({
  "user_name": "username",
  "user_email": "email",
}));

Type Converters

Register custom type converters for types not covered by the built-in converters (String, int, double, bool, DateTime, Duration, Color).

TypeConverters.register<Money>((value) {
  if (value is Money) return value;
  if (value is String) return Money.parse(value, isoCode: 'USD');
  if (value is int) return Money.fromInt(value, isoCode: 'USD');
  throw Exception("Unable to convert to Money: $value");
});

Instance Management

Use singleInstance or keyedInstance to prevent duplicate model objects representing the same data.

// Singleton — one instance per type
final profile = Model.singleInstance<Profile>(
  factory: Profile.new,
  data: data,
);

// Keyed — one instance per unique key
final topic = Model.keyedInstance<Topic>(
  factory: Topic.new,
  data: data,
);

For full documentation, see Models.

ELParser

The ELParser class parses and evaluates expression strings. It can be used standalone without XWidget.

Basic Usage

final parser = ELParser();
final dependencies = Dependencies({
  "users": [
    {"name": "Mike Jones"},
    {"name": "Sally Smith"},
  ],
  "indexes": [1, 0, 2],
});

Parse and Evaluate Separately

final result = parser.parse("users[indexes[0]].name + ', ' + users[0].name");
result.value.evaluate(dependencies);  // -> "Sally Smith, Mike Jones"

Evaluate in One Step

parser.evaluate("users[4 % length(indexes)].name", dependencies);
// -> "Sally Smith"

Evaluate Embedded Expressions

For expressions embedded in a larger string:

parser.evaluateEmbedded(
  "Hello, \${users[0].name}!",
  dependencies
);
// -> "Hello, Mike Jones!"

For full expression syntax, see Rules.