I have a map consisting of different types and strings:
const Map<Type, String> hiveTableNames = {
BreakTimeDto: "breaktime",
WorkTimeDto: "worktime"
};
And I want to loop through it because I want to call a function for each type which takes a type parameter:
Future<void> sendAll<T>(List item) async {
...
}
My attempt was to use the forEach-loop:
hiveTableNames.forEach((key, value) async {
final box = await Hive.openBox(value);
_helper.sendAll<key>(box.values.cast<key>().toList());
});
But the App throws an error: Error: 'key' isn*t a type.
Why is that? I declared the map to store types and from my understanding i pass these types in the function.
Dart separates actual types and objects of type Type. The latter are not types, and cannot be used as types, they're more like mirrors of types. A Type object can only really be used for two things: as tokens to use with dart:mirrors and comparing for equality (which isn't particularly useful except for very simple types).
The only things that can be used as type arguments to generic functions or classes are actual literal types or other type variables.
In your case, you have a Type object and wants to use the corresponding type as a type argument. That won't work, there is no way to go from a Type object to a real type.
That's a deliberate choice, it means that the compiler can see that if a type is never used as a type argument in the source code, then it will never be the type bound to a type parameter, so if you have foo<T>(T value) => ... then you know that T will never be Bar if Bar doesn't occur as a type argument, something<Bar>(), anywhere in the program.
In your case, what you can do is to keep the type around as a type by using a more complicated key object.
Perhaps:
class MyType<T> {
const MyType();
R use<R>(R Function<X>() action) => action<T>();
int get hashCode => T.hashCode;
bool operator==(Object other) => other is MyType && other.use(<S>() => T == S);
}
This allows you to store the type as a type:
final Map<MyType, String> hiveTableNames = {
const MyType<BreakTimeDto>(): "breaktime",
const MyType<WorkTimeDto>(): "worktime"
};
(I'm not making the map const because const maps must not have keys which override operator==).
Then you can use it as:
hiveTableNames.forEach((key, value) async {
final box = await Hive.openBox(value);
key.use(<K>() =>
_helper.sendAll<K>([for (var v in box.values) v as K]);
}
(If all you are using your map for is iterating the key/value pairs, then it's really just a list of pairs, not a map, so I assume you are using it for lookups, which is why MyType override operator==).
In general, you should avoid using Type objects for anything, they're very rarely the right tool for any job.
Related
I am trying to assign a List as a value to a field in a Map.
However, if I vary the types in the Map, the methods seem to be no longer available on the List.
Example:
var list = [1,2];
var myMap = {
'list' : list,
//'commentOut' : 3,
};
print("${myMap['list'].runtimeType}");
print("${myMap['list']!.length}");
Output
JSArray<int>
2
However, if I uncomment the line 'commentOut' : '
I get the error
Error: The getter 'length' isn't defined for the class 'Object'.
Is there a way to get this to work, or am I misunderstanding something fundamental we can't do with Maps ?
When you do:
var list = [1,2];
var myMap = {
'list' : list,
};
then the inferred type of myMap is Map<String, List<int>>.
When you instead do:
var myMap = {
'list' : list,
'commentOut' : 3,
};
your Map's values are now heterogeneous, so Dart infers the value type as the nearest common base type. In this case, the common base type of List<int> and int is Object, so myMap's inferred type is Map<String, Object>.
Consequently, static analysis tools (the Dart analyzer and the Dart compiler) know only that the static (known at compile-time) type of myMap['list'] is Object? even though the actual runtime type is List<int>. It therefore will be a compile-time error to attempt to directly access List methods on it, because calling List methods on an arbitrary Object is potentially unsafe.
You either must explicitly cast the value (myMap['list'] as List<int>) or disable static type checking by explicitly declaring myMap to be of type Map<String, dynamic>.
3 is a value type, not an object type. So the property "length" is not defined on a value type. Even though you defined the element 'list' to get the length, it doesn't know how to handle it.
Given a Map variable, how can I determine the type of Key and Value from it?
For example:
void doSomething(Map m){
print('m: ${m.runtimeType}');
print('keys: ${m.keys.runtimeType}');
print('values: ${m.values.runtimeType}');
print('entries: ${m.entries.runtimeType}');
}
void main() async {
Map<String, int> m = {};
doSomething(m);
}
This will print
m: _InternalLinkedHashMap<String, int>
keys: _CompactIterable<String>
values: _CompactIterable<int>
entries: MappedIterable<String, MapEntry<String, int>>
But how can I get the actual type of Key and Value (i.e. String and int), so that I can use them in type checking code (i.e. if( KeyType == String ))?
You cannot extract the type parameters of a class if it doesn't provide them to you, and Map does not.
An example of a class which does provide them is something like:
class Example<T> {
Type get type => T;
R withType<R>(R Function<X>() callback) => callback<T>();
}
If you have an instance of Example, you can get to the type parameter, either as a Type (which is generally useless), or as a type argument which allows you to do anything with the type.
Alas, providing access to types variables that way is very rare in most classes.
You can possibly use reflection if you have access to dart:mirrors, but most code does not (it doesn't work with ahead-of-time compilation, which includes all web code, or in Flutter programs).
You can try to guess the type by trying types that you know (like map is Map<dynamic, num>, then map is Map<dynamic, int> and map is Map<dynamic, Never>. If the first two are true, and the last one is false, then the value type is definitely int. That only works if you know all the possible types.
It does work particularly well for platform types like int and String because you know for certain that their only subtype is Never.
If you can depend on the static type instead of the runtime type, you could use a generic function:
Type mapKeyType<K, V>(Map<K, V> map) => K;
Otherwise you would need to have a non-empty Map and inspect the runtime types of the actual elements.
Given some nullable type T?, how do I get the corresponding non-nullable one T ?
For example:
T? x<T extends int?>(T? value) => value;
Type g<T>(T Function(T) t) => T;
Type type = g(x);
print(type); // Prints "int?"
Now I want to get the non-nullable type. How do I create the function convert so that:
Type nonNullableType = convert(type);
print(nonNullableType); // Prints "int"
If you have an instance of T?, and you're trying to do something where the expected type is T, you can use use T! wherever dart is showing an error. It is not exactly a conversion from T? to T, its just a shortcut to do a null check.
In general, you do not. There is no simple way to strip the ? of a type, or destructure types in other ways. (You also can't find the T of type you know is a List<T> at run--time)
If you have the type as a Type object, you can do nothing with it. Using Type object is almost never what you need.
If you have the type as a type parameter, then the type system don't actually know whether it's nullable. Example:
void foo<T>() { ... here T can be nullable or non-nullable ... }
Even if you test null is T to check that the type is actually nullable, the type system doesn't get any smarter, that's not one of the tests that it can derive type information from.
The only types you can improve on are variable types (or rather, the type of a single value currently stored in a variable). So, if you have T x = ...; and you do if (x != null) { ... x is not null here }, you can promote the variable to T&Object, but that's only an intermediate type to allow you to call members on the variable, it's not a real type that you can capture as a type variable or a variable type. It won't help you.
All in all, it can't be done. When you have the nullable type, it's too late, you need to capture it before adding the ?.
What problem are you actually trying to solve?
If you have an instance of T?, I think you could do:
Type nonNullableTypeOf<T>(T? object) => T;
void main() {
int x = 42;
int? y;
print(nonNullableTypeOf(x)); // Prints: int
print(nonNullableTypeOf(y)); // Prints: int
}
If you have only T? itself (the Type object), then I'm not confident that there's much you can do since what you can do with Type objects is very limited. (And given those limitations, it's not clear that nonNullableTypeOf ultimately would be very useful either.)
A related question: How do I check whether a generic type is nullable in Dart NNBD?
In JS, there is undefined and null. Undefined means "no value", null means "value equivalent to emptiness".
In Dart however (and possibly in other languages, I don't know, but right now I'm using Dart), there is no undefined, only null. Therefore it is impossible to make the distinction between a value equivalent to emptiness and the absence of value.
Is there a standard way of simulating this difference in Dart?
No. The null value (of type Null) is the only built-in value that represents the absence of a value. It does not distinguish on why the value is absent.
It's also (almost) the only type you can combine with another type directly in the type system. With null safety, you'll be able to write int? for int-or-Null.
If you want another marker for absence, you can use (or write) an Option type like:
abstract class Option<T> {
final T value;
bool get hasValue => true;
Option(this.value);
factory Option.none() => const _OptionNone();
}
class _OptionNone implements Option<Never> {
const _OptionNone();
bool get hasValue => false;
T get value => throw UnsupportedError("None option has no value");
}
Then you can represent either value or no value.
(There are existing implementations of such a class, for example in the quiver package).
Sample code that explains problem.
import "dart:mirrors";
void main() {
var type = getTypeFromDeclaration();
var typeArguments = getAnotherTypeArguments();
var myType = reflectType(type, typeArguments);
}
How to getting in Dart the type mirror with the specified type arguments through reflection?
P.S.
I think that I don't need to explain "Why this need?" because we all know that this functionality required for the data hydration.
Also this is very useful in data codecs that used reflection for the better data consistency.
As you noticed, I will not explain why.
First, I agree that it would be best if the mirror system allowed creating a type mirror of Map from the type mirrors of Foo and Bar and the class mirror of Map. That is currently not the case.
Without that, I don't think you can solve the problem as written.
There is no way to create a parameterized type with a type argument that is not known as a type at compile-time.
For completeness, I'll include a way to reflect a parameterized type if you know the type at compile time. If the type arguments can be represented as something else than a Type object or a TypeMirror object, you can build your own representation that allows operations like this.
If you can't use reflectType(Map<Foo,Baz>) because Map<Foo,Baz> is not a valid type literal, there is a small workaround to get a Type for any type: Have a class with a type parameter and a way to get the Type value of that parameter.
import "dart:mirrors";
class Typer<T> { Type get type => T; }
main() {
var mapStringInt = reflectType(new Typer<Map<String,int>>().type);
print(mapStringInt); // ClassMirror on 'Map'
print(mapStringInt.typeArguments); // [ClassMirror on 'String', ClassMirror on 'int']
// That is: it's a TypeMirror on Map<String,int>.
}