Dart, overriding not equal operator gives error - dart

I've been developing dart(flutter) for some while and I came upon this Error. I was creating a custom class that had several operator overrides. For explanation purposes, the class looks like this.
class CustomObject {
int big;
int small;
CustomObject(this.big, this.small);
#override
bool operator ==(Object other) {
if (other is CustomObject) {
return big == other.big && small == other.small;
}
return false;
}
#override
int get hashCode => big.hashCode ^ small.hashCode;
#override
bool operator !=(Object other) {
// Error: The string '!=' isn't user-definable operator.
if (other is CustomObject) {
return big != other.big || small != other.small;
}
return false;
}
}
The error occurs on the != operator override. Looking at this website https://www.geeksforgeeks.org/operator-overloading-in-dart/ it says that you can override != operator. I could not find any other source that documents overriding this operator.
My question is, 1. are you supposed to override != in the first place? 2. if so, is are any restrictions on overriding != operators?

Related

dart nullability checking method [duplicate]

This question already has answers here:
"The operator can’t be unconditionally invoked because the receiver can be null" error after migrating to Dart null-safety
(3 answers)
Closed 12 months ago.
I have migrated my Dart code to NNBD / Null Safety. Some of it looks like this:
class Foo {
String? _a;
void foo() {
if (_a != null) {
_a += 'a';
}
}
}
class Bar {
Bar() {
_a = 'a';
}
String _a;
}
This causes two analysis errors. For _a += 'a';:
An expression whose value can be 'null' must be null-checked before it can be dereferenced.
Try checking that the value isn't 'null' before dereferencing it.
For Bar() {:
Non-nullable instance field '_a' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
In both cases I have already done exactly what the error suggests! What's up with that?
I'm using Dart 2.12.0-133.2.beta (Tue Dec 15).
Edit: I found this page which says:
The analyzer can’t model the flow of your whole application, so it can’t predict the values of global variables or class fields.
But that doesn't make sense to me - there's only one possible flow control path from if (_a != null) to _a += 'a'; in this case - there's no async code and Dart is single-threaded - so it doesn't matter that _a isn't local.
And the error message for Bar() explicitly states the possibility of initialising the field in the constructor.
The problem is that class fields can be overridden even if it is marked as final. The following example illustrates the problem:
class A {
final String? text = 'hello';
String? getText() {
if (text != null) {
return text;
} else {
return 'WAS NULL!';
}
}
}
class B extends A {
bool first = true;
#override
String? get text {
if (first) {
first = false;
return 'world';
} else {
return null;
}
}
}
void main() {
print(A().getText()); // hello
print(B().getText()); // null
}
The B class overrides the text final field so it returns a value the first time it is asked but returns null after this. You cannot write your A class in such a way that you can prevent this form of overrides from being allowed.
So we cannot change the return value of getText from String? to String even if it looks like we checks the text field for null before returning it.
An expression whose value can be 'null' must be null-checked before it can be dereferenced. Try checking that the value isn't 'null' before dereferencing it.
It seems like this really does only work for local variables. This code has no errors:
class Foo {
String? _a;
void foo() {
final a = _a;
if (a != null) {
a += 'a';
_a = a;
}
}
}
It kind of sucks though. My code is now filled with code that just copies class members to local variables and back again. :-/
Non-nullable instance field '_a' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
Ah so it turns out a "field initializer" is actually like this:
class Bar {
Bar() : _a = 'a';
String _a;
}
There are few ways to deal with this situation. I've given a detailed answer here so I'm only writing the solutions from it:
Use local variable (Recommended)
void foo() {
var a = this.a; // <-- Local variable
if (a != null) {
a += 'a';
this.a = a;
}
}
Use ??
void foo() {
var a = (this.a ?? '') + 'a';
this.a = a;
}
Use Bang operator (!)
You should only use this solution when you're 100% sure that the variable (a) is not null at the time you're using it.
void foo() {
a = a! + 'a'; // <-- Bang operator
}
To answer your second question:
Non-nullable fields should always be initialized. There are generally three ways of initializing them:
In the declaration:
class Bar {
String a = 'a';
}
In the initializing formal
class Bar {
String a;
Bar({required this.a});
}
In the initializer list:
class Bar {
String a;
Bar(String b) : a = b;
}
You can create your classes in null-safety like this
class JobDoc {
File? docCam1;
File? docCam2;
File? docBarcode;
File? docSignature;
JobDoc({this.docCam1, this.docCam2, this.docBarcode, this.docSignature});
JobDoc.fromJson(Map<String, dynamic> json) {
docCam1 = json['docCam1'] ?? null;
docCam2 = json['docCam2'] ?? null;
docBarcode = json['docBarcode'] ?? null;
docSignature = json['docSignature'] ?? null;
}
}

Test stream with wrapped array

I'm trying to test that the function emitArray emits a Response.Success an its value is ['test'].
If I emit a List<String> everything works as expected, but once I wrap the result list in a Response<List<String>> the test fails.
The result is emitted, but it fails when comparing with the expected result.
I'm wondering if it's related to the implementation of == in Response.Success, I'm using the default implementation that the IDE provides.
This is not the real code I have, it's just a simple example that is easier to understand to try to identify the issue.
This is my class to test:
class ListResponse {
final _array = BehaviorSubject<Response<List<String>>>();
Stream<Response<List<String>>> get array => _array.stream;
Future<void> emitArray() async {
_array.add(Response.success(['test']));
}
void dispose() {
_array.close();
}
}
This is my test:
void main() {
ListResponse underTest;
setUp(() {
underTest = ListResponse();
});
test('It should emit array', () {
final array = Response.success(['test']);
expect(
underTest.array,
emitsInOrder([
array,
emitsDone,
]),
);
underTest.emitArray();
underTest.dispose();
});
}
This is the error it throws:
Expected: should do the following in order:
• emit an event that SuccessResponse<List<String>>:<SuccessResponse{value: [test]}>
• be done
Actual: <Instance of 'BehaviorSubject<Response<List<String>>>'>
Which: emitted • SuccessResponse{value: [test]}
x Stream closed.
which didn't emit an event that SuccessResponse<List<String>>:<SuccessResponse{value: [test]}>
This is the Response class
class Response<T> {
Response._();
factory Response.success(T value) = SuccessResponse<T>;
factory Response.error(Exception error) = ErrorResponse<T>;
}
class ErrorResponse<T> extends Response<T> {
ErrorResponse(this.error): super._();
final Exception error;
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ErrorResponse &&
runtimeType == other.runtimeType &&
error == other.error;
#override
int get hashCode => error.hashCode;
#override
String toString() {
return 'ErrorResponse{error: $error}';
}
}
class SuccessResponse<T> extends Response<T> {
SuccessResponse(this.value): super._();
final T value;
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is SuccessResponse &&
runtimeType == other.runtimeType &&
value == other.value;
#override
int get hashCode => value.hashCode;
#override
String toString() {
return 'SuccessResponse{value: $value}';
}
}
I'm wondering if it's related to the implementation of == in Response.Success
Exactly. This particular test is failing because you can't compare Lists with ==:
abstract class List<E> implements EfficientLengthIterable<E> {
...
/**
* Whether this list is equal to [other].
*
* Lists are, by default, only equal to themselves.
* Even if [other] is also a list, the equality comparison
* does not compare the elements of the two lists.
*/
bool operator ==(Object other);
}
As a workaround you can change the implementation to compare objects' string representations instead:
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is SuccessResponse &&
runtimeType == other.runtimeType &&
value.toString() == other.value.toString();
Interestingly, passing unwrapped List<String>s objects passes test. That happens because StreamMatcher uses equals() from matcher package to match events, and equals() can match lists and maps. It first tries to match objects with ==, then checks whether they are Iterable/Set/Map (and deep matches them recursively), and then reports mismatch error.

Safe way to access List index

I'm newbee to Dart.
I have troubles to find an easy to read way to "safely" access a List element at any index
final List<String> myList = <String>[]
myList.add("something")
// ...
String myGetter(int index) {
// "heavy" way
if (index < myList.length) {
return myList[index]
}
return null;
}
If I go with regular [index] or elementAt(index) and index is out of boundaries, it throws a RandeError
Is there a method that returns null when the index cannot be reached?
Sorry if double posted, but I try to find the info without any success, + not sure if there is an (un)official slack / discord to ask this kind of "easy" questions
Dart lists do not allow invalid indices. There is no built-in way to get a null when trying. Not in the platform libraries.
You can create your own helper function (like you already do):
T? tryGet<T>(List<T> list, int index) =>
index < 0 || index >= list.length ? null : list[index];
(Remember to check for negative indices too).
As suggested, you can also add it as an extension method:
extension ListGetExtension<T> on List<T> {
T? tryGet(int index) =>
index < 0 || index >= this.length ? null : this[index];
}
which may make it more pleasant to work with.
(I recommend against doing something bad and then catching the error, at least when you can easily check up-front whether it's bad or not).
You can defined an extension method to catch the RangeError and return null:
void main() {
print([1, 2].get(3)); // display null
}
extension SafeLookup<E> on List<E> {
E get(int index) {
try {
return this[index];
} on RangeError {
return null;
}
}
}
You can try this
void main() {
List<int> teste = [1, 2, 3, 4];
print(teste.get(1));
}
extension ListExtension<E> on List<E> {
dynamic get(int value) {
return this.contains(value) ? value : null;
}
}
According to the documentation:
throws a RangeError if index is out of bounds.
So you can use the try-catch block:
String myGetter(int index) {
try {
return myList[index];
}
on RangeError {
// Called when the index is out of bounds
return null;
}
}
If you want to be extra cautious I guess you could put a generic catch at the end (to catch all kinds of throws that are not RangeError), but in a simple getter like this I think that would not be necessary:
[...]
}catch (e) {
// No specified type, handles all other types of error/exceptions
return null;
}
[...]

Limit a generic type argument only to be a int, double or custom class

I trying make the following code but T only can be int, double or a custom class. I couldn't find how to restrict the type in Dart or something that work like where from C#. How can I do that in Dart?
class Array3dG<T> extends ListBase<T> {
List<T> l = List<T>();
Array3dG(List<T> list) {
l = list;
}
set length(int newLength) { l.length = newLength; }
int get length => l.length;
T operator [](int index) => l[index];
void operator []=(int index, T value) { l[index] = value; }
}
There is no way to constrain the type variable at compile-time. You can only have one bound on a type variable, and the only bound satisfying both int and your custom class is Object.
As suggested by #Mattia, you can check at run-time and throw in the constructor if the type parameter is not one of the ones you supprt:
Array3dG(this.list) {
if (this is! Array3dG<int> &&
this is! Array3dG<double> &&
this is! Array3dG<MyClass>) {
throw ArgumentError('Unsupported element type $T');
}
}
This prevents creating an instance of something wrong, but doesn't catch it at compile-time.
Another option is to have factory methods instead of constructors:
class Array3dG<T> {
List<T> list;
Array3dG._(this.list);
static Array3dG<int> fromInt(List<int> list) => Array3dG<int>._(list);
static Array3dG<int> fromDouble(List<double> list) => Array3dG<double>._(list);
static Array3dG<MyClass> fromMyClass(List<MyClass> list) => Array3dG<MyClass>._(list);
...
}
which you then use as Array3dG.fromInt(listOfInt). It looks like a named constructor, but it is just a static factory method (so no using new in front).
You can check at runtime the type with the is keyword:
Array3dG(List<T> list) {
if (list is List<int>) {
//Handle int
}
else if (list is List<double>) {
//Handle double
}
else if (list is List<MyClass>) {
//Handle MyClass
}
else {
throw ArgumentError('Unsupported $T type');
}
}
Note that if you are handling int and double in the same way you can just check for num
You can check the progress of the Union types here: https://github.com/dart-lang/sdk/issues/4938

Cannot create proper Equatable function for usage with Array's .contains()

I am trying to check whether an array of tuples contains a certain tuple using the native contains() method of Array.
I have declared my two "equatable" functions as
public func ==(a: (clip1: Clip?, clip2: Clip?), b: (clip1: Clip?, clip2: Clip?)) -> Bool {
let clipa1 = a.clip1
let clipa2 = a.clip2
let clipb1 = b.clip1
let clipb2 = b.clip2
if clipa1 != nil && clipa2 != nil && clipb1 != nil && clipb2 != nil {
return (clipa1! == clipb1!) && (clipa2! == clipb2!)
}
else if clipa1 != nil && clipa2 == nil && clipb1 != nil && clipb2 == nil {
return (clipa1! == clipb1!)
} else {
return false
}
}
public func ==(a: Clip, b: Clip) -> Bool {
return a.id == b.id
}
However when I try these in this manner
for clip in tmp {
if !_filteredClips?.contains((clip1: clip.clip1, clip2: clip.clip2)) {
_filteredClips?.append(clip)
}
}
I am getting Cannot convert value of type '(clip1: Optional<Clip>, clip2: Optional<Clip>)' to expected argument type '#noescape ((clip1: Clip?, clip2: Clip?)) throws -> Bool'
What am I missing here?
The version of contains you are trying to use is only available for elements that adopt Equatable:
extension SequenceType where Generator.Element : Equatable {
/// Return `true` iff `element` is in `self`.
#warn_unused_result
public func contains(element: Self.Generator.Element) -> Bool
}
The tuple type (Clip?, Clip?) does not and you can't extend it. Since you've implemented ==, you can use the version of contains that takes a predicate like this.
for clip in tmp {
if !_filteredClips!.contains({ $0 == clip }) {
_filteredClips?.append(clip)
}
}
See How do I check if an array of tuples contains a particular one in Swift? for other ideas.
== can be used to test the equality of custom types such as structures, classes and tuples as the caller is doing here. The choice between == and === depends on whether your code needs to test for equality or identity, not on whether you are working with a class, struct, etc

Resources