Class modifiers

Class modifiers

Dart adds a few new modifiers that you can place on class and mixin declarations. If you are the author of a library package, these modifiers give you more control over what users are allowed to do with the types that your package exports. This can make it easier to evolve your package, and easier to know if a change to your code may break users.

Dart 3.0 also includes a breaking change around using classes as mixins. This change might not break your class, but it could break users of your class.

This guide walks you through these changes so you know how to use the new modifiers, and how they affect users of your libraries.

The mixin modifier on classes

The most important modifier to be aware of is mixin. Language versions prior to Dart allow any class to be used as a mixin in another class’s with clause, UNLESS the class:

  • Declares any non-factory constructors.

  • Extends any class other than Object.

This makes it easy to accidentally break someone else’s code, by adding a constructor or extends clause to a class without realizing that others are using it in a with clause.

Dart 3.0 no longer allows classes to be used as mixins by default. Instead, you must explicitly opt-in to that behavior by declaring a mixin class:

mixin class Both {}

class UseAsMixin with Both {}
class UseAsSuperclass extends Both {}

If you update your package to Dart and don’t change any of your code, you may not see any errors. But you may inadvertently break users of your package if they were using your classes as mixins.

Migrating classes as mixins

If the class has a non-factory constructor, an extends clause, or a with clause, then it already can’t be used as a mixin. Behavior won’t change with Dart 3.0; there’s nothing to worry about and nothing you need to do.

In practice, this describes about 90% of existing classes. For the remaining classes that can be used as mixins, you have to decide what you want to support.

Here are a few questions to help decide. The first is pragmatic:

  • Do you want to risk breaking any users? If the answer is a hard “no”, then place mixin before any and all classes that could be used as a mixin. This exactly preserves the existing behavior of your API.

On the other hand, if you want to take this opportunity to rethink the affordances your API offers, then you may want to not turn it into a mixin class. Consider these two design questions:

  • Do you want users to be able to construct instances of it directly? In other words, is the class deliberately not abstract?

  • Do you want people to be able to use the declaration as a mixin? In other words, do you want them to be able to use it in with clauses?

If the answer to both is “yes”, then make it a mixin class. If the answer to the second is “no”, then just leave it as a class. If the answer to the first is “no” and the second is “yes”, then change it from a class to a mixin declaration.

The last two options, leaving it a class or turning it into a pure mixin, are breaking API changes. You’ll want to bump the major version of your package if you do this.

Did you find this article valuable?

Support Vinit Mepani (Flutter Developer) by becoming a sponsor. Any amount is appreciated!