skip to content

In Dart, what does a mixin's `on` clause do, and when do you need it rather than an abstract member?

level: middleimportance: must knowfreq 48%

answer

  1. super inside a mixin
  2. restricts where it applies
  3. superclass must be a subtype
  4. concrete super member required
  5. abstract member: no super call

basics

~20 s

An on T clause lets a mixin make super calls resolved against T, and allows applying it only to classes whose superclass is a subtype of T. Use it to wrap an inherited method; an abstract member suffices when you only call the host.

solid answer

~40 s

The `on` clause names the type that `super` calls inside the mixin are resolved against. `mixin Retry on ApiClient` can override `get` and call `super.get(path)`, so it wraps whatever implementation sits below it. In exchange, `Retry` can only be applied where the superclass is a subtype of `ApiClient`, and that superclass must supply a **concrete** `get`; `class Plain with Retry` fails because `Object` is not an `ApiClient`. If the mixin only needs to *call* a member the host provides, an abstract member or an `implements` clause is enough and keeps the mixin usable anywhere. Reach for `on` only when you need `super`.

code

dart · 27 lines
dart
abstract class ApiClient {
  Future<String> get(String path);
}

class HttpApiClient extends ApiClient {
  @override
  Future<String> get(String path) async => 'body of $path';
}

mixin Retry on ApiClient {
  int get maxAttempts => 3;

  @override
  Future<String> get(String path) async {
    for (var attempt = 1; ; attempt++) {
      try {
        return await super.get(path);
      } on Exception {
        if (attempt >= maxAttempts) rethrow;
      }
    }
  }
}

class UserApi extends HttpApiClient with Retry {}
// class Plain with Retry {}                  // error: Object is not an ApiClient
// class Bare extends ApiClient with Retry {} // error: super.get has no concrete target

go deeper

for a junior

Recall that on restricts which classes can use a mixin and that it is what makes super calls legal inside the mixin.

for a middle

Explain both sides of the contract: super resolves against the on type, and the superclass at the application site must be a subtype with a concrete implementation.

for a senior

Choose between on and an abstract member on purpose: on only for wrappers that delegate to the inherited version, because it narrows where the mixin can be used.

for a principal

Judge whether a stack of on-constrained wrapper mixins over a base client is clearer than explicit decorator objects for the team that must maintain it.

## The problem `on` solves A plain Dart mixin is applied on top of whatever superclass the host has. Inside it, `super` refers to that superclass, but when the mixin is compiled the language does not know what that superclass will be, so it treats it as `Object`. That means a plain mixin cannot write `super.get(path)`: `Object` has no `get`. Many useful mixins are **wrappers** that need exactly that call. A retry mixin for API clients wants to override `get`, call the real implementation, and try again on failure. That requires `super.get`, and that requires the **`on` clause**. ## What `on T` means ```dart mixin Retry on ApiClient { ... } ``` The dart.dev docs put it plainly: the `on` clause exists to define the type that `super` calls are resolved against, and it forces any class that uses the mixin to also be a subclass of that type. Two consequences follow: - **Inside the mixin**, `super.get(...)` type-checks against `ApiClient`, and `this` is known to have every `ApiClient` member. - **At the application site**, the class's superclass (the thing to the left of this mixin in the `extends ... with ...` chain) must be a subtype of `ApiClient` and must provide a **concrete** implementation of each member the mixin calls through `super`. ```dart abstract class ApiClient { Future<String> get(String path); } class HttpApiClient extends ApiClient { @override Future<String> get(String path) async => 'body of $path'; } mixin Retry on ApiClient { int get maxAttempts => 3; @override Future<String> get(String path) async { for (var attempt = 1; ; attempt++) { try { return await super.get(path); } on Exception { if (attempt >= maxAttempts) rethrow; } } } } class UserApi extends HttpApiClient with Retry {} // OK // class Plain with Retry {} // error: Object is not an ApiClient // class Bare extends ApiClient with Retry {} // error: super.get has no concrete target ``` The third line fails even though `ApiClient` is the right type, because its `get` is abstract. The super call would have nothing to run. ## `on` versus the alternatives A mixin that only needs to **call** something on its host does not need `on`. Two lighter options exist: 1. **An abstract member** declared in the mixin (`Future<String> get(String path);`). Every host must implement it; the mixin calls `get(...)` on `this`. 2. **An `implements` clause** on the mixin (`mixin Logging implements Tagged`) without implementing the interface, which again pushes the obligation onto the host. Neither lets you call `super`, so neither can **wrap** an inherited implementation. The choice comes down to one question: does the mixin replace the member and delegate to the one underneath? If yes, use `on`; if it only calls a member, prefer an abstract member. | Need | Tool | Cost | |---|---|---| | Call a host member | abstract member | none; mixin applies anywhere | | Guarantee an interface on the host | `implements` on the mixin | none; host must implement it | | Override and call the inherited version | `on T` | only classes whose superclass is a `T` can apply it | ## Details worth knowing - A mixin may list **several `on` types**, separated by commas, when its `super` calls span members of more than one type. - An `on` mixin still cannot declare an `extends` clause or a constructor; `on` is not inheritance. - A `mixin class` cannot have an `on` clause, because a class cannot have one. - Before the `mixin` declaration existed (Dart 2.1), super calls in mixins needed an experimental flag. Since Dart 2.18, mixing in a class that extends anything other than `Object` (the old `class Retry extends ApiClient` trick) is no longer supported; the SDK changelog prescribes `mixin Retry on ApiClient` instead. ## Where it shows up in Flutter Flutter ships framework mixins constrained with `on`, for example state mixins used with animation controllers; those specifics belong to the animation topic. For your own code, the typical case is exactly the API-client wrapper above: logging, retry, timing or caching layered over a base client's `send` or `get` method.

  • In Dart, why does `class Bare extends ApiClient with Retry {}` fail when ApiClient declares get abstractly?
    Because `Retry.get` calls `super.get`, and the superclass the mixin is applied on, `ApiClient`, has no concrete `get`. The `on` type only makes the call type-check; at the application site the superclass must actually implement every member the mixin super-invokes, or the compiler rejects the class.
  • In Dart, can a mixin declare more than one type in its on clause?
    Yes. `mixin M on A, B` is legal: super calls may target members of either type, and the class it is applied on must have a superclass that is a subtype of both. It is rare, and usually a hint that the mixin is doing too much.

saying these in an interview costs you the question

  • The on clause makes the mixin extend that class.
  • You need on whenever a mixin calls any host method.
  • A mixin can call super without an on clause.
  • Any subclass of the on type works, even if the member is abstract.
  • A mixin class can declare an on clause.