Dynamic Type Casting in Dart/Flutter - flutter

I am in the middle of writing a library to dynamically serialise/deserialise any object in Dart/Flutter (Similar in idea to Pydantic for Python). However, I am finding it impossible to implement the last component, dynamic type casting. This is required in order convert types from JSON, such as List to List (or similar). The types are retrieved from objects using reflection.
The below is the desired implementation (though as far as I understand this is not possible in Dart).
Map<String, Type> dynamicTypes = {"key": int };
// Regular casting would be "1" as int
int value = "1" as dynamicTypes["key"];
Is there some workaround which makes this possible to implement? Or have I reached a dead end with this (hence no other dynamic serialisation/deserialisation package already exists).

Conducting more research into this issue, it seems in Dart's current implementation this is impossible due to runtime reflection being disabled as referenced here in the official docs.
There are ongoing discussions about the support for this and the associated package dart:mirrors here on GitHub, but so far though there is some desire for such functionality it is highly unlikely to ever be implemented.
As a result, the only options are:
Use code generation libraries to generate methods.
Manual serialisation/deserialisation methods.
Implement classes with complex types such as lists and maps to be dynamic, enabling (all be it limited) automatic serialisation/deserialisation.

Your question does not specify how exactly dynamicTypes is built, or how its key is derived. So there is perhaps a detail to this that is not clear to me.
But what about something like this?
class Caster<T> {
final T Function(String) fromString;
Caster(this.fromString);
}
void main() {
Map<String, Caster> dynamicTypes = { "key": Caster<int>((s) => int.parse(s)) };
int v = dynamicTypes['key']!.fromString('1');
print(v);
}
Or, if you have the value as a dynamic rather than a string:
class Caster<T> {
T cast(dynamic value) => value as T;
}
void main() {
Map<String, Caster> dynamicTypes = { "key": Caster<int>() };
dynamic a = 1;
int v = dynamicTypes['key']!.cast(a);
print(v);
}
Or even more succinctly:
void main() {
dynamic a = 1;
int v = a;
print(v);
}

Related

json_serializable not generating code for final fields and getters

This is my class:
#JsonSerializable()
class Foo {
final int a = 0;
int get b => 42;
}
The generated code doesn't include any of the a or b field:
Foo _$FooFromJson(Map<String, dynamic> json) => Foo();
Map<String, dynamic> _$FooToJson(Foo instance) => <String, dynamic>{};
Note: Please don't write solutions for doing something like Foo(this.a) or Foo() : a = 0 etc. I need to keep my structure as it is.
Simple answer - this won't be possible as this is done intentionally by design.
If adjusting your model structure is out of the question, you can try manual conversion or a mix of these two.
After encountering the same issues myself, not long ago, I opted in for manual converting my models - and also a combination of two in some cases.
With #JsonSerializable, you can make use of the from/to generated methods, and then in addition, manually convert your final and getter fields.
Another option you can try, at least for the getter fields, is making use of #JsonSerializable(crateFactory: false). This will indicate the engine to convert getters as well.
I suggest reading Flutter JSON Docs in detail and figuring out what will suit you the most, depending on your models, what you want, or what you can compromise on.
Worth reading the Github thread on getter conversion.
There's no out of the box workaround for final fields though, but as per this Github thread on final fields, PRs are welcome :)
If you want easier json parsing, use Dart Data Class Generator extension in VS Code.
I think since a is final, it is considered a constant and not variable and b is a getter and not a variable, there are no variables (fields) for json_serializable to generate code for.
You need to add a default constructor before json_serializable can run. Since you have no fields, you wil have an empty constructor and empty methods generated.
class Foo {
final int a = 0;
int get b => 42;
const Foo();
factory Foo.fromJson(Map<String, dynamic> json) => Foo();
Map<String, dynamic> toJson() => <String, dynamic>{};
}

Is there a dart function annotation that makes the type checker do type narrowing or condition assertions

Is there a construct that communicates to the type checker a function's post-condition?
For example, in typescript it is possible to say
function assertIsNumber(value: any): asserts value is number {
if (typeof value !== 'number') {
throw new TypeError();
}
}
I would like to be able to do something like the following in dart:
class SomeClass {
int? value;
_checkPreconditions() {
if(value == null) {
throw MyPreconditionError()
}
// ...
}
somefunc() {
_checkPreconditions();
// here we know `value` is a non-null int.
final sum = value + 5;
}
}
I understand I could coerce the value to non-null sum = value! + 5, but I would prefer to allow the function to inform the type checker if possible.
It looks like the type system of Dart is not so powerful. The only thing that looks (from first glance) possible is to create a custom code analyzer package (or search for one that already exists).
Dart annotations don't actually do anything. They provide hints to tools such as the Dart analyzer (usually so that it can generate additional warnings), but they cannot change program behavior. Even if you could convince the analyzer to treat some variables as different types, you still wouldn't be able to compile and run your code.
Annotations can be used by code generation tools, so one possibility might be to generate a statement such as final value = this.value!; automatically. However, that would be a lot of trouble to go through (and would mean that code then would need to use this.value = 42; for assignments and would prevent your code from being analyzed directly).

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.

Is there no simple way to make a deep of lists in flutter?

I tried List.from and [...orginalObject], but neither does a deep copy. In both cases it's clearly just copying the references. Which means if I change any data in the list in which I copied the data, the original data gets changed too.
It seems to me that the only way to do a for loop and define each of the entries the copied list with a new operator and the data from each of corresponding entries from the original list. Something as shown in the following image.
Seems like quite a tedious approach. Is there any simpler approach?
Thanks.
In vanilla Dart, not really, since this would require the ability to copy the leaf objects, and there is no way for the language to know how to do that for arbitrary objects.
However, the built_value and built_collection packages may solve your issue. One of their main features is deep immutability. First, you write your classes like this:
class Item {
final String foo;
final int bar;
Item(this.foo, this.bar);
}
// becomes
part 'item.g.dart';
abstract class Item extends Built<Item, ItemBuilder> {
Item._();
factory Item([void Function(ItemBuilder) updates]) = _$Item;
// boilerplate, can use tooling to auto-generate
String get foo;
int get bar;
}
This lets you create an immutable object using a mutable Builder and then finalizing it, e.g.:
final item1 = Item((b) {
// here b is an ItemBuilder, the mutable version of Item
b.foo = 'Hello';
b.bar = 123;
});
final item2 = item1.rebuild((b) => b.foo = 'New Value'); // Item(foo: 'New Value', bar: 123)
You can then use built_collection to work with deeply immuatble collections of built values, e.g.:
final list = BuiltList<Item>([item1, item2, item3]);
final deepCopy = list.rebuild((b) {}); // rebuild with no changes
final withNewElement = list.rebuild((b) => b.add(item4));
The important thing with all of these is that they are all immutable, and are "value objects", meaning (among other things) that they are considered equal if all their corresponding values are equal. You can consider each rebuild call to be returning you a brand new list with all the values copied over.
In fact, the library is smart enough to only create new objects for values that have actually changed, but because it is immutable, it acts like a deep copy, without the potential performance costs.
The main downside with these libraries is that they rely on code generation, so it requires a bit more setup than just putting it in your dependencies. In return, you get a lot of useful features in addition to having deeply immutable objects (like json serialization, null checking in pre-null-safety code, #memoized to cache results of expensive computations`, etc)
Dart does not have copy constructors nor have required clone() methods on objects, so there is no way to copy an arbitrary object, and therefore there is no way to make a deep copy of a collection of arbitrary objects.
cameron1024's answer suggesting the use of package:built_value and package:built_collection is good. If they're not suitable for you, however, you could make a function that copies elements via a callback:
List<T> copyList<T>(Iterable<T> items, T Function(T element) copier) {
return [
for (var item in items) copier(item),
];
}
class Foo {
int i;
String s;
Foo(this.i, this.s);
#override
String toString() => 'Foo($i, "$s")';
}
void main() {
var list = [Foo(1, 'one'), Foo(2, 'two'), Foo(3, 'three')];
print('Original: $list');
var copy = copyList<Foo>(list, (foo) => Foo(foo.i, foo.s));
list[0].i = 100;
print('Mutated: $list');
print('Copy: $copy');
}

How to specify generic type such that we can use some operation on the generic type in Flutter?

For example, I expect <T extends type> can works like this:
Class Parent {
String data;
Parent({ this.data });
}
Class Child extends Parent {
Child({ this.data }) : Parent(data: data);
void showData() { print(data); }
}
T wrapper<T extends Parent>(String value) {
var result = T(data: value);
return result;
}
void main() {
var trial = wrapper<Child>("Hello world");
trial.showMessage(); // print "Hello world"
}
But turns out it gives me error at var result = T(data: value);, saying that T is not a function. When I specified , I expect that T can be operated like Parent class, and if I supplied its descendant like Child, the operation done will be Child instead. But the constructor will work either way because T extends Parent. Is such thing possible?
Constructors are not inherited. You know that already, because you wrote one yourself in your child class that does nothing but call the base class with the same parameters.
One could as well write a different constructor. So "X extends Y" says a lot about X, but it does not say anything about how the constructor of X looks (or whether it even has a constructor accessible in that scope). So a constructor call is not in the properties available to you when you specify your generic to "extend Y", because Y can do exactly nothing to make sure all it's derivates follow a specific construction method.
Different languages deal with the problem of "but how do I construct a new instance of my generic type" in different ways, but the underlying concept is common to almost all concepts of generics where the generic code is compiled before knowing the specific types of all T's handled. A constructor is not inherited in most OOP languages, therefor it is not guaranteed to be there for any "X extends Y" even if Y has it.
It might be easy to overlook when you have all your code in one compilation unit. The compiler should be able to figure it out, right? But your code might not be in a single compilation unit:
Codebase one:
Class Parent {
String data;
Parent({ this.data });
}
T wrapper<T extends Parent>(String value) {
var result = T(data: value);
return result;
}
At this point, the compiler has no idea what "Child" might look like. It cannot possibly determine that the child class that will be used in the Future has a constructor like that.
Codebase 2:
Class Child extends Parent {
Child({ this.data }) : Parent(data: data);
void showData() { print(data); }
}
void main() {
var trial = wrapper<Child>("Hello world");
trial.showMessage(); // print "Hello world"
}
Now, at this point, a compiler could figure out that the program it's given would actually work. Some concepts of generics do that, where generics cannot be compiled into independent libraries, they always come as source code, because only the final compiler producing the executable can determine whether it would work with a specific class. Flutter does not do this. Flutter needs the generic itself be valid for the constraints given.
All newer language's versions of generics have followed the path of knowing the constraints beforehand and only allowing code operating inside those constraints. And I think it's good because while it has it's shortcomings, it leaves less room for errors or cryptic error messages.