Best practice for overriding class fields in Dart - flutter

I'm in the middle of creating a theming system for my app, where I wish to create a base theme class, with fields for colour and fonts:
abstract class BaseTheme {
late Color backgroundColor;
late Color appBarBackgroundColor;
late Color progressIndicatorColor;
late Color primaryColor;
late Color iconColor;
}
(I have to define them as late to keep the Null Safety happy! These have to be overridden anyway, so no problems there).
I would then implement this base class for each theme, as below:
import 'base_theme.dart';
class LightTheme implements BaseTheme {
Color backgroundColor = Colors.white;
Color appBarBackgroundColor = Color(0xff1a1818);
Color progressIndicatorColor = Color(0xffffc114);
Color primaryColor = Colors.red;
Color iconColor = Colors.white;
}
I would then use these values to populate the ThemeData:
BaseTheme _lightTheme = new LightTheme();
ThemeData _theme = ThemeData(
scaffoldBackgroundColor: _lightTheme.backgroundColor,
// etc...
}
Now, this appears to work fine. However, the page on overriding fields in the Linter for Dart states that overriding fields is bad practice. So, is there a better way to achieve something similar?
What's confusing is that, on that page, one example shows overriding fields when you extend a base class, which is considered bad. However, overriding fields when you implement a base class is good - But in this case, the field type has changed from Base to Derived (ie. they're not the same).

As the lint recommends, you should avoid overriding fields. Your abstract class should define an interface; as such, it should declare getters and setters and leave the implementation to the concrete, derived classes. Note that this avoids the need for late (which is an implementation detail that does not belong in the abstract base class).
abstract class BaseTheme {
Color get backgroundColor;
set backgroundColor(Color value);
Color get appBarBackgroundColor;
set appBarBackgroundColor(Color value);
// etc.
}
Dart 2.13 allows members to be declared as abstract, so you can use that to avoid the repetitiveness:
abstract class BaseTheme {
abstract Color backgroundColor;
abstract Color appBarBackgroundColor;
// etc.
}
Also see the GitHub issue: overridden_fields documentation is vague.

The reason it's not recommended as in the rule is because of memory allocation. When you override a field of an extended class you allocate more memory. For example:
class Base {
Object field = 'lorem';
Object something = 'change';
}
class Bad1 extends Base {
#override
final field = 'ipsum'; // LINT
}
In the example above when you create a new instance of Bad1 you will get field and super.field. So you allocated memory for two Object instances when you probably only needed one. You can read more about it here https://github.com/dart-lang/linter/issues/230 and here https://github.com/dart-lang/linter/issues/2428
You bring up a good point about the change in type in the docs in this example
abstract class BaseLoggingHandler {
Base transformer;
}
class LogPrintHandler implements BaseLoggingHandler {
#override
Derived transformer; // OK
}
These docs are old so this is pre null safety. Now this is a compile type error because to narrow the type you need to be more explicit and use the covaraint keyword. Like so
abstract class BaseLoggingHandler {
BaseLoggingHandler(this.transformer);
Base transformer;
}
class LogPrintHandler implements BaseLoggingHandler {
LogPrintHandler(this.transformer);
#override
covariant Derived transformer; // OK
}
Here is an example to illustrate the concept of a covaraint.
Given
abstract class Base {}
class Derived extends Base{}
the following
typedef Example = void Function(Derived);
Example e = (Base b) {};
This is valid because a Derived is a Base and if you were to give me a function (Base b) {} I can safely invoke your function passing an object of type Derived and I know operations you will take upon that object are safe.
But this is not valid for the opposite reason.
typedef Example = void Function(Base);
Example e = (Derived d) {};

Yes... Whatever you have done is correct. The link https://dart-lang.github.io/linter/lints/overridden_fields.html says its bad practice to override the class not the abstract class. When we are extending the abstract class, its compulsion to override its fields and methods. See the below screenshots.
Here are the more lint: rules https://dart.dev/tools/linter-rules

Related

Flutter with Equitable and ObjectBox: ToMany relationship not updating properly

I have the following classes:
import 'package:equatable/equatable.dart';
import 'package:objectbox/objectbox.dart';
#Entity()
/*
All fields of a class which extends Equatable should be immutable, but ObjectBox
requires the `id` field to be mutable because its value is set after an instance of
the class has been created. Because of this, we ignore the linter rule
"must_be_immutable" on all ObjectBox entities.
*/
// ignore: must_be_immutable
class Foo extends Equatable {
int id;
final String fooProp;
// I don't need a backlink yet, but very likely will in the future
// #Backlink()
// final ToMany<Bar> bars;
Foo(
this.fooProp,
{
this.id=0,
}
);
#override
List<Object> get props => [fooProp];
}
import 'package:equatable/equatable.dart';
import 'package:objectbox/objectbox.dart';
#Entity()
/*
All fields of a class which extends Equatable should be immutable, but ObjectBox
requires the `id` field to be mutable because its value is set after an instance of
the class has been created. Because of this, we ignore the linter rule
"must_be_immutable" on all ObjectBox entities.
*/
// ignore: must_be_immutable
class Bar extends Equatable {
int id;
final String barProp;
final ToMany<Foo> foos;
Bar(
this.barProp,
this.foos,
{
this.id=0,
}
);
#override
List<Object> get props => [barProp, foos];
}
And here is what I'm trying to do:
import 'package:foo_bar/objectbox/objectbox.dart';
// Get previously stored instance of Foo
Foo foo = ObjectBox.fooBox.get(1);
// Print foo.fooProp
print(foo.fooProp); // Prints "asdf"
// Change foo.fooProp to something else
foo.fooProp = 'fdsa';
// Update foo
ObjectBox.fooBox.put(foo);
// Get the same instance of Foo again
foo = ObjectBox.fooBox.get(1);
// Check foo.fooProp to make sure it updated
print(foo.fooProp); // Prints "fdsa", good so far
// Get previously stored instance of Bar which has Foo instance with ID of 1 in its foos
Bar bar = ObjectBox.barBox.get(1);
// Get our foo from bar.foos
foo = bar.foos[0];
// Verify the ID of foo to make sure it is the same object
print(foo.id); // Prints "1", exactly what we expect
// Print foo.fooProp
print(foo.fooProp); // Prints "asdf", not the expected "fdsa"
The documentation has the following to say on the subject:
Note that to-many relations are resolved lazily on first access, and then cached in the source entity inside the ToMany object. So subsequent calls to any method, like size() of the ToMany, do not query the database, even if the relation was changed elsewhere. To get the latest data fetch the source entity again or call reset() on the ToMany.
The reset() method doesn't appear to be available in the Flutter flavor of ObjectBox, and we can see from my example that even fetching both sides of the ToMany relationship did not result in the expected update.
What am I missing here?
Failed Workaround:
I tried to workaround this problem with the following awful bit of code, but even this does not work. ObjectBox just completely ignores the actual bar.foos and whatever was persisted for foos remains there and doesn't get updated.
final List<Bar> oldBars = ObjectBox.barBox.getAll();
List<Bar> newBars = [];
for(Bar oldBar in oldBars) {
if(oldBar.foos.isNotEmpty) {
List<int> oldFooIds = oldBar.foos.map((foo) => foo.id).toList();
List<Foo> newFoos = foos.where((foo) => oldFooIds.contains(foo.id)).toList();
Bar newBar = oldBar.copy(foos: ToMany<Foo>(items: newFoos));
newBars.add(newBar);
}
}
ObjectBox.barBox.putMany(newBars);
This makes me think there is something wrong with the way I have the relationship setup, but there are no errors when the ObjectBox generator runs
CALL flutter pub run build_runner build --delete-conflicting-outputs
Update:
I have this working now, but clean it is not. I had my Bar constructor set up to accept a collection of Foo objects, but passing the instances of Foo in is what was causing the relations to break. If I instead create an instance of Bar, then use bar.foos.add(foo), the result is as expected. For what it is worth, that is how the examples in the docs show interactions with relations happening, I just didn't think it was that literal, because creating new objects with relations in this manner is a hassle. I think some work can be done in the constructor to make things a bit easier still.
A "solution" (and I use that term loosely) to my problem was the following modification to the Bar class. This allows me to initialize instances of Bar with a pre-built list of instances of Foo.
import 'package:equatable/equatable.dart';
import 'package:objectbox/objectbox.dart';
#Entity()
/*
All fields of a class which extends Equatable should be immutable, but ObjectBox
requires the `id` field to be mutable because its value is set after an instance of
the class has been created. Because of this, we ignore the linter rule
"must_be_immutable" on all ObjectBox entities.
*/
// ignore: must_be_immutable
class Bar extends Equatable {
int id;
final String barProp;
final ToMany<Foo> foos = ToMany<Foo>();
Bar(
this.barProp,
{
this.id=0,
foos=const <Foo>[],
}
) {
this.foos.addAll(foos);
}
#override
List<Object> get props => [barProp, foos];
}
This works fine, for creating new objects, but because I want to use Equatable I'm must make all properties which are used to determine equality final. When a class is an #Entity which will persisted to ObjectBox, most of its properties will typically be used to determine equality, so this requirement of Equatable makes it at odds with ObjectBox when it is time to update an object. For instance, if I have an instance of Bar, I can't update barProp; I have to create a new instance of Bar which is initializes barProp to the desired value. If I create a new instance of Bar which had the desired value for barProp, foos, and has the same ID as an already persisted instance of Bar, then I try to persist that new instance, barProp will be updated as expected, but foos will not. All that to say, I first have to take the heavy handed approach of calling ObjectBox.barBox.remove() or ObjectBox.barBox.removeAll() (depending on the application) before persisting the new instance of Bar.
Foo fooA = Foo('this is Foo A');
Foo fooB = Foo('this is Foo B');
List<Foo> firstFooList = <Foo>[fooA, fooB];
ObjectBox.fooBox.putMany(firstFooList);
Foo fooC = Foo('this is Foo C')
Foo fooD = Foo('this is Foo D')
List<Foo> secondFooList = <Foo>[fooC, fooD];
ObjectBox.fooBox.putMany(secondFooList);
Bar barA = Bar('this is bar A', foos=firstFooList)
ObjectBox.barBox.put(barA); // ObjectBox assigns ID 1 to this Bar
Bar barB = Bar('this is bar B', id=barA.id, foos=secondFooList) // Use the `id` from barA which we just persisted, but initialize `foos` with `secondFooList`
// Without this, the next line which persists `barB` would result in the
// instance of Bar which has ID 1 having a `barProp` value of 'this is bar B',
// but a `foos` value equal to `firstFooList`, not the expected `secondFooList`.
ObjectBox.barBox.remove(barB.id);
ObjectBox.barBox.put(barB);
Time will tell if calling remove and removeAll like this are a bigger performance hit than not using Equatable, and for others reading this, that determination will depend on your specific application (i.e. does your app have more UI interactions that benefit from reduced builds because of the inclusion of Equatable, or does your app have more ObjectBox interactions where excessive calls to remove and removeAll cause a performance hit).

Why is it necessary to use constructors in dart programming language classes?

I'm a beginner learning dart from the book dart apprentice and I reached where they were discussing constructors in dart classes, the book was implying that constructors create instances of the class which I understood but I needed more info about constructors. So I googled and some results repeated what was already in the book about it being used to create instances of a class while others also showed that it's used to instantiate class properties, but my problem is with the other answer which I found that they are used to instantiate properties of a class, but my question is: I instantiate all class properties when I create the class by declaring the property variables, like this:
class UserClass{
userClassProperty = "";
anotherUserClassProperty = ""; }
why is the constructor also needed to instantiate class properties?
Often, values are unique to every class instance.
Consider the following example:
class Point {
final int x;
final int y;
const Point(this.x, this.y);
double get distanceToOrigin => sqrt(x * x + y * y);
}
If the x and y values were defined inside the class, it would be pretty useless. Instead, different Point objects can be instantiated with different values, which means the same code can be used for different situations.
Ok, so constructors instantiate or start a class by collecting all the data the class needs to start to start working. Constructors are so important that the dart compiler provides one even if you don't explicitly create one. For example, you create a class for mammals like this :
class Mammal{
String name = "cat";
int numberOfLegs = 2;
}
Although you don't explicitly add a constructor the dart compiler adds a default constructor like this :
class Mammal{
Mammal(); //This is added by dart during the class instantiation by default.
String name = "cat";
int numberOfLegs = 2;
}
Yeah, that's how crucial constructors are to the dart compiler.
And on the topic of why are they necessary even when you declare all the properties by yourself in the class, as hacker1024 said it would make the class pretty useless, as the point of the existence of classes is to create variants but with different properties. Not adding a constructor to your class and defining all the properties in the class would mean that your class doesn't take property arguments which in turn also means that different variants of your class can't be created. Again this goes directly against the point of the existence of dart classes. For example, you have a class like this :
class Mammals{
Strig name = "Human";
int numberOfLegs = 2;
bool hasFur = false;
}
final cat = Mammal();
final human = Mammal();
print(cat.numberOfLegs); //Prints 2
//2
print(human.numberOfLegs); //Also prints 2
//2
print(cat.hasFur);
// false
Yeah, this class is problematic. Cats with 2 legs? You would agree with me that that's not how things are in reality. And also the class is pretty useless in the sense that it's not modular, no matter which kind of mammal we create be it a cat, a sheep or even a cow the name property is going to be the default one we set, that is "Human". When we create a class to simulate mammals we want to be able to define what kind of properties it has, not use some fixed values. So you want to create a class which has a constructor like this :
class Mammals{
Mammals(String name,int noOfLegs, bool hasFur){
this.name = name;
this.noOfLegs = noOfLegs;
this.hasFur = hasFur;
}
String name = "";
int noOfLegs = 0;
bool hasFur = False;
}
final cat = Mammal("Cat", 4, True); //Now you can pass in the properties ou want.
final human = Mammal("Human", 2, false);
print(cat.name); //This prints the customized name of the object cat instead of some fixed value
//Cat
print(human.name); //This prints the customized name of the object human
Now we have two instances of the class with separate property values.
Although this adds a little more code, the modularity benefit is worth it.

instantiate object with *new* keyword and use property inside class with *this* keyword

i'm coming from mainly JS/TS world (NestJS/Angular) and recently i start to building Flutter apps..
i have 2 main questions
there is any difference when instantiate object with or without new keyword?
i saw examples in flutter when people use new Row(children: [Text('Foo'), Text('Bar'),],) instead of just Row(...)
if there is a difference which one is better to use?
inside of my Dart classes in flutter app, i can both use this.property and property again there is any difference and if so which one is better and why?
example:
class Person {
final String name;
final int age;
Person(this.name, this.age);
getNameAge() => '${this.name} is ${this.age}';
getNameAge2() => '$name is $age';
}
both looks the same to me
void main() {
final p = Person('dan', 22);
final p2 = new Person('ben', 20);
print(p.getNameAge()); // dan is 22
print(p2.getNameAge2()); // ben is 20
}
The new keyword is optional in Dart and I think the general consensus is, today, to not use it.
The use of this is useful if you have multiple variables with the same name but in different scope. E.g. (this is just an example. You would not make a setA method in Dart but use properties):
class A {
int a;
A(this.a);
void setA(int a) {
this.a = a;
}
}
Here we use this to distinguish between the argument a and the class variable a. But if you don't have variables with the same name (but in different scope), the use of this is optional. In some projects, you still use this to make it more clear that you are referring to a class variable even if it is not needed.

How to declare final class in Dart to prevent extending from it?

In Java\Kotlin we have a String class that is final and immutable.
I tried to mark the class with final keyword but looks like it's not allowable.
So, I'm a little bit confusing, how to declare final class in Dart ?
Note: the case is - I want to instantiate this class outside, but forbid to extending it. So using the private constructor - it's not my case.
You can achieve this final effect from java by having a private constructor for your class, it will prevent the class from being extended, BUT it will also prevent the class from being instantiated (only in the same file both will be possible):
class MyString {
MyString._(); // use _ for private constructor.
static void print(String s) {
print(s);
}
}
Call with
String message = "Hello World";
MyString.print(message);
Dart considers that we are all adults, preventing class extension is hence part of the design and responsability of the developers to have clear class names, and not part of the language:
AVOID extending a class that isn’t intended to be subclassed.
If a constructor is changed from a generative constructor to a factory constructor, any subclass constructor calling that constructor will break. Also, if a class changes which of its own methods it invokes on this, that may break subclasses that override those methods and expect them to be called at certain points.
Difference of meaning for final with Java
Dart has a very simple definition of what is final: a variable in dart can only be set once, id est: is immutable.
Final and const
If you never intend to change a variable, use final or const, either instead of var or in addition to a type.
A final variable can be set only once; a const variable is a compile-time constant. (Const variables are implicitly final.) A final top-level or class variable is initialized the first time it’s used.
Additionally to the approach of making the constructor private and instantiating your object via a static factory, you could use the package meta and
annotate your final class as sealed:
#sealed
class Z{}
This will signal users of your package that this class should not be extended or implemented. For example in vscode trying to extend the class Z:
class Z1 extends Z{}
results in the following warning:
The class 'Z' shouldn't be extended, mixed in,
or implemented because it is sealed.
Try composing instead of inheriting, or refer
to its documentation for more information.dart(subtype_of_sealed_class)
The issue will also be picked up by the dart analyzer:
$ dart analyze
Analyzing test... 0.8s
info • lib/src/test_base.dart:3:1 •
The class 'Z' shouldn't be extended, mixed in, or implemented because it
is sealed. Try composing instead of inheriting, or refer to its
documentation for more information. • subtype_of_sealed_class
You can use the factory unnamed constructor along with private named constructor, like this:
class NonExtendable {
NonExtendable._singleGenerativeConstructor();
// NonExtendable();
factory NonExtendable() {
return NonExtendable._singleGenerativeConstructor();
}
#override
String toString(){
return '$runtimeType is like final';
}
}
In a client code, in the same library, or another library, an instance can be created, an example:
// Create an instance of NonExtendable
print ('${NonExtendable()}');
Trying to extend it, something like
class ExtendsNonExtendableInSameLibrary extends NonExtendable {
ExtendsNonExtendableInSameLibrary._singleGenerativeConstructor() : super._singleGenerativeConstructor();
factory ExtendsNonExtendableInSameLibrary() {
return ExtendsNonExtendableInSameLibrary._singleGenerativeConstructor();
}
}
will work in the same library (same 'source file') but not in another library, making the class NonExtendable same as 'final' in Java from the perspective of any client code.

What's the rationale behind not inheriting static variables, in Dart?

In Dart, if one class extends another, the extended class inherits all of the super classes non-static variables, but inherits none of its static variables.
For example
class TestUpper {
static final String up = 'super';
String upup = 10;
}
class TestLower extends TestUpper {
static final String low = 'lower';
String lowlow = 11;
}
var lower = new TestLower();
print( lower.lowlow ); // <== 11
print( lower.upup ); // <== 10
print( TestLower.low ); // <== "lower"
print( TestLower.up ); // <== No static getter 'get:up' declared in class 'TestLower'
Is this the normal behavior? If so, I would appreciate if someone explained the rationale behind it.
Yes, there's no inheritance of static members. See Static Methods section of the language specification :
Inheritance of static methods has little utility in Dart. Static methods cannot be overridden. Any required static function can be obtained from its declaring library, and there is no need to bring it into scope via inheritance. Experience shows that developers are confused by the idea of inherited methods that are not instance methods.
Of course, the entire notion of static methods is debatable, but it is retained here because so many programmers are familiar with it. Dart static methods may be seen as functions of the enclosing library.