One extension for several types - dart

In project we have a lot of enum that need to be serializable and
deserializable in/from String. So I wrote the extension:
extension EnumSerializer on MyEnum {
String getString() => this.toString().split('.').last;
static MyEnum fromString(String str) => MyEnum.values.firstWhere( (v) => v.getString() == str, orElse: () => throw SerializerError('bad string');
}
But problem is I should write new extension with the same content for every enum.
Is it possible to do something like this:
extension EnumSerializer on MyEnum, OtherEnum, AnotherEnum { ...

As a direct answer to your question, no, that is not possible.
As a side note, the lack of native parsing functionality for enums is a sticking point for a lot of people. The extension approach helps, but it's hampered by both the lack of a base class for enum types (forcing an extension be made for every enum type) and the lack of support for static type extensions.
I would argue that it's saner to instead forgo extensions altogether and create a generic static utility method somewhere that can handle all enum types:
class EnumHelper {
static String asString<T>(T value) {
final str = value.toString();
return str.substring(str.indexOf('.') + 1);
}
static T parse<T>(String str, List<T> values) {
for (var v in values) {
if (toString(v) == str) {
return v;
}
}
return null;
}
}
// Usage
enum Foo { a, b }
EnumHelper.asString(Foo.a); // "a"
EnumHelper.parse("a", Foo.values); // Foo.a
EDIT: As of Dart 2.15, you no longer need extension methods or custom utilities for this functionality. Enums have builtin helper methods for parsing and serializing:
enum Foo { a, b }
Foo.a.name; // "a"
Foo.values.byName("a"); // Foo.a
Foo.values.asNameMap(); // { "a": Foo.a, "b": Foo.b }

I've created feature request in dart repository:
It's much more likely that we'll introduce a supertype of enums, than that we will allow an extension on multiple types.
The latter would mean that this inside the extension cannot be typed, which is a no-go in typed language like Dart 2.
One alternative is that If we had union types, you could do on (Type1 | Type2 | Type3) and let that union type be the type of this inside the extension. We don't have union types yet, and likely won't any time soon, it'd be a large change to the Dart type system.
So, a "no" to multiple extension on types from me. You have to find a common supertype for them.

Related

How to override enum's "name" property?

Minimal reproducible code:
enum Foo {
a,
b;
String get name {
switch (this) {
case Foo.a: return 'A';
case Foo.b: return 'B';
}
}
}
void main() {
printEnum<Foo>(Foo.values);
}
void printEnum<T extends Enum>(List<T> list) {
for (var e in list) {
print(e.name);
}
}
The for loop prints
a
b
But I wanted it to print
A
B
So, how do I override the name property in the enum?
Note:
Using (e as Foo).name will solve the issue, but I have many enums in my project, so I can't cast them like this.
Also, please don't post answers like, use toUpperCase(), etc, because I just provided a simple example, but in real world, things are quite different.
You cannot override the name getter "on Enum" because it's an extension getter, not an instance getter.
Overriding, aka. late binding, of instance members only apply to actual (virtual) instance members.
Extension members are statically resolved, so a call of .name either hits the extension member, or it doesn't, and it depends entirely on the static type of the receiver. If you have an Enum e; ... e.name ... then it will call the extension member, and there is absolutely no way to change that, or change what it does.
If you want to have a custom and overridable name getter, I'd introduce an interface like
abstract class NamedEnum extends Enum {
String get name;
}
and then let all your enums implement NamedEnum.
Then you can do (enumValue as NamedEnum).name for any of your enums.
It won't interact with other enum types' extension name getter.
Casting e as dynamic works, as long as you ensure that the name property exists on the enum you are printing. Ex:
void printEnum<T extends Enum>(List<T> list) {
for (var e in list) {
print((e as dynamic).name);
}
}

Multiple types for a single variable (parameter/return type)

I am very new to Dart so excuse me if I didnt see this part.
I want to make a union type e.g. for a function input. In TS this would be:
let variableInput: string | number
typedef doesnt really define types but functions and enums dont really help too.
On the other side how should it look like when a function return either one or the other of two types? There must be something I dont see here.
There are no union types in Dart.
The way to do this in Dart is returning/accepting dynamic as a type:
dynamic stringOrNumber() { ... }
void main() {
final value = stringOrNumber();
if (value is String) {
// Handle a string value.
} else if (value is num) {
// Handle a number.
} else {
throw ArgumentError.value(value);
}
}
See also: https://dart.dev/guides/language/sound-dart

How to get the subtypes of a generic type using `DartType` from `analyzer` package?

How can I get the subtypes of an element using the class DartType from the analyzer package?
for example if the type is List<String>, I would like to get String. Also will be useful to get if the type is generic.
Another more complex example would be Map<String, String> where I want to get a list of the subtypes, in this case: [String, String].
This one is a little tricky - because DartType actually itself has some super types - the one that will interest you here is ParameterizedType:
import 'package:analyzer/dart/element/type.dart';
Iterable<DartType> getGenericTypes(DartType type) {
return type is ParameterizedType ? type.typeArguments : const [];
}
I don't know if it's possible to know if the type is generic - after all, it's just a type. But you can check if the type accepts generic parameters, again, using ClassElement:
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
bool canHaveGenerics(DartType type) {
final element = type.element;
if (element is ClassElement) {
return element.typeParameters.isNotEmpty;
}
return false;
}
Hope that helps!

Syntax error when trying to determine if a variable is of a certain type

Pretty much as the title says: If you have a Type stored in a variable, there's no way to compare your actual object to this type variable, as far as I can tell. I can probably accomplish what I'm trying to do with mirrors, but I'd prefer not to if at all possible.
void example() {
Type myType = String;
String myExample = "Example";
//Syntax error here: The name 'myType' is not a type and cannot be used in an 'is' expression
if (myExample is myType) {
}
}
You can't generally test if a value is of a type using the Type object.
Type objects are reflected types, not real types. They represent the real type, but you can't use them in the code where you need a type: as type assertions, as generic type parameters or with the is/as operators. You must use the name of a type in those places, and not the name of a normal variable that happens to hold a Type object.
Clever stuff using mirrors might get there, but it's likely overkill for most cases (and I understand that you don't want it).
What you might be able to do instead, is to not pass around raw Type objects. You could instead make your own type abstraction, something like:
class MyType<T> {
const MyType();
Type get type => T;
bool isA(Object object) => object is T;
}
Then you can use that to represent types, not a Type object, and do something like:
void main(List<String> args) {
MyType myType = const MyType<String>();
String myExample = "Example";
if(myType.isA(myExample)) {
print('is');
} else {
print('is not');
}
}
That does require that your entire program uses your type objects to pass around types, but it also gives you a lot of control over those objects, so you can implement the functionality that you need.
I tried
library x;
void main(List<String> args) {
Type myType = String;
String myExample = "Example";
if(myExample.runtimeType == myType) {
print('is');
} else {
print('is not');
}
}
and it worked.
I have not much experience with such code in Dart though. Maybe that is not a fail-safe approach.
import 'package:reflection/reflection.dart';
void main() {
var childType = typeInfo(Child);
var baseType = typeInfo(Base);
if(childType.isA(baseType)) {
print("Child is Base");
}
if(baseType.isAssignableFrom(childType)) {
print("Base is assignable from Child");
}
}
class Base {
}
class Child extends Base {
}
Child is Base
Base is assignable for Child
P.S.
The "reflection" package incompatible with dart2js. It work only when used in Dart language.

How to create private variables in Dart?

I want to create a private variable but I cannot.
Here is my code:
void main() {
var b = new B();
b.testB();
}
class A {
int _private = 0;
testA() {
print('int value: $_private');
_private = 5;
}
}
class B extends A {
String _private;
testB() {
_private = 'Hello';
print('String value: $_private');
testA();
print('String value: $_private');
}
}
When I run this code, I get the following result:
String value: Hello
int value: Hello
Breaking on exception: type 'int' is not a subtype of type 'String' of 'value'.
Also I not get any error or warnings when editing this source code.
How can I create a private variable in Dart?
From Dart documentation:
Unlike Java, Dart doesn’t have the keywords public, protected, and private. If an identifier starts with an underscore _, it’s private to its library.
Libraries not only provide APIs, but are a unit of privacy: identifiers that start with an underscore _ are visible only inside the library.
A few words about libraries:
Every Dart app is a library, even if it doesn’t use a library directive. The import and library directives can help you create a modular and shareable code base.
You may have heard of the part directive, which allows you to split a library into multiple Dart files.
Dart documentation "libraries-and-visibility"
Privacy in Dart exists at the library, rather than the class level.
If you were to put class A into a separate library file (eg, other.dart), such as:
library other;
class A {
int _private = 0;
testA() {
print('int value: $_private'); // 0
_private = 5;
print('int value: $_private'); // 5
}
}
and then import it into your main app, such as:
import 'other.dart';
void main() {
var b = new B();
b.testB();
}
class B extends A {
String _private;
testB() {
_private = 'Hello';
print('String value: $_private'); // Hello
testA();
print('String value: $_private'); // Hello
}
}
You get the expected output:
String value: Hello
int value: 0
int value: 5
String value: Hello
In dart '_' is used before the variable name to declare it as private. Unlike other programming languages, here private doesn't mean it is available only to the class it is in, private means it is accessible in the library it is in and not accessible to other libraries. A library can consists of multiple dart files as well using part and part of. For more information on Dart libraries, check this.
The top answer as of now is definitely correct.
I'll try to go into more detail in this answer.
I'll answer the question, but lead with this: That's just not how Dart is intended to be written, partly because library-private members make it easier to define operators like ==. (Private variables of a second object couldn't be seen for the comparison.)
Now that we've got that out of the way, I'll start out by showing you how it's meant to be done (library-private instead of class-private), and then show you how to make a variable class-private if you still really want that. Here we go.
If one class has no business seeing variables on another class, you might ask yourself whether they really belong in the same library:
//This should be in a separate library from main() for the reason stated in the main method below.
class MyClass {
//Library private variable
int _val = 0;
int get val => _val;
set val(int v) => _val = (v < 0) ? _val : v;
MyClass.fromVal(int val) : _val = val;
}
void main() {
MyClass mc = MyClass.fromVal(1);
mc.val = -1;
print(mc.val); //1
//main() MUST BE IN A SEPARATE LIBRARY TO
//PREVENT MODIFYING THE BACKING FIELDS LIKE:
mc._val = 6;
print(mc.val); //6
}
That should be good. However if you really want private class data:
Though you technically aren't allowed to create private variables, you could emulate it using the following closure technique. (HOWEVER, you should CAREFULLY consider whether you really need it and whether there is a better, more Dart-like way to do what you're trying to accomplish!)
//A "workaround" that you should THINK TWICE before using because:
//1. The syntax is verbose.
//2. Both closure variables and any methods needing to access
// the closure variables must be defined inside a base constructor.
//3. Those methods require typedefs to ensure correct signatures.
typedef int IntGetter();
typedef void IntSetter(int value);
class MyClass {
IntGetter getVal;
IntSetter setVal;
MyClass.base() {
//Closure variable
int _val = 0;
//Methods defined within constructor closure
getVal = ()=>_val;
setVal = (int v) => _val = (v < 0) ? _val : v;
}
factory MyClass.fromVal(int val) {
MyClass result = MyClass.base();
result.setVal(val);
return result;
}
}
void main() {
MyClass mc = MyClass.fromVal(1);
mc.setVal(-1); //Fails
print(mc.getVal());
//On the upside, you can't access _val
//mc._val = 6; //Doesn't compile.
}
So yeah. Just be careful and try to follow the language's best-practices and you should be fine.
EDIT
Apparently there's a new typedef syntax that's preferred for Dart 2. If you're using Dart 2 you should use that. Or, even better, use inline function types.
If you use the second, it will be less verbose, but the other problems remain.

Resources