How can I initialize a mixin's immutable data in Dart?

Viewed 10204

I am programming in Flutter using Dart 2.1.0, and come across this situation:

mixin Salt {
  final int pinches;  // Immutable, and I want to delay initialization.

  // Cannot declare constructors for mixin
}

class Meat with Salt {
  Meat(int pinches) ... // How to initialize it?
}

Salt has no constructor, so I cannot use initializer list. pinches is final, so I cannot set it in Meat's constructor.

I don't want to make Salt a class because Meat may need to extend from something else.

And I want to keep pinches immutable.

Any way to do it? Thanks in advance.

5 Answers

You can change the declaration of your mixin to:

mixin Salt {
  int get pitches;
}

And then define the field inside the implementation class

class Meat with Salt {
  final int pitches;
  Meat(this.pitches);
} 

By design it is not possible to declare a final member into a mixin because it is not possible to declare a constructor for initializing the final member, citing the docs:

However, in this proposal, a mixin may only be extracted from a class that has no declared constructors. This restriction avoids complications that arise due to the need to pass constructor parameters up the inheritance chain.

A compromise may be to declare a private member and implement only a getter.
_pinches is visible only inside the library, it is read-only for library users.

mixin Salt {
  int _pinches;

  get pinches => _pinches;

}

class Meat with Salt {

  Meat(int pinches)  {
   _pinches = pinches;
  }
}

Note: the above pattern, because of the visibility rules, works only if the mixin and the mixed classes reside in the same library.

Similar to attdona's suggestion, but a little bit closer to what you really wanted, you could do it like

mixin Salt {
  int _pinches;
  int get pinches => _pinches;
  void initSalt(int pinches) {
    assert(_pinches == null);
    _pinches = pinches;
  }
}

class Meat with Salt {
  Meat(int pinches) {
    initSalt(pinches);
  }
}

It's still not strictly final, but (so long as the mixin's in a different library so you can't change the private member directly) it's immutable at runtime. Not as good as if it could be properly final, but maybe close enough.

The following method allows you to set the data at a later time, and gets rid of the warning:

This class (or a class that this class inherits from) is marked as '@immutable', but one or more of its instance fields aren't final

mixin Salt {
  final SaltData _saltData = SaltData();

  int get pinches => _saltData.pinches;

  set pinches(int extraData) {
    _saltData.pinches = extraData;
  }
}

class SaltData {
  int pinches = 0;
}

So what I did is create a class SaltData. This will store all the variables you need.

The private _saltData variable is final, this will stop the warning.

Then use a getter and setter to retrieve and update the data.

int get pinches => _saltData.pinches;

set pinches(int extraData) {
  _saltData.pinches = extraData;
}

If you want you can could expose the entire saltData object as well:

SaltData get saltData => _saltData;

I offer my take on a solution to this. By marking the variable late you can make it final. No warning will appear if you fail to initialize it so use with caution.

mixin Salt {
  late final int pinches;
}

class Vegetable with Salt {
  Vegetable(int pinches) {
    this.pinches = pinches;
  }
}
Related