In Dart, what does a mixin's `on` clause do, and when do you need it rather than an abstract member?
answer
- super inside a mixin
- restricts where it applies
- superclass must be a subtype
- concrete super member required
- abstract member: no super call
basics
~20 sAn 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 sThe `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 linesabstract 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 targetgo deeper
Recall that on restricts which classes can use a mixin and that it is what makes super calls legal inside the mixin.
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.
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.
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.