Dart Factory class for creating variables with type - flutter

The problem is the following.
I had a typescript factory class that I attempted to do in Dart:
class FactoryClass{
factory FactoryClass(dynamic types, String className, dynamic defaultValue){
if(types[className] != null ){
return types[className](defaultValue);
}
else{
throw Exception("");
}
}
}
In TS it was used like this:
let variable= new FactoryClass([String, Number, etc...], "Number", "42")
That in TypeScript would give back a Number type variable with the value 42
However, it's not gonna work in Dart since types have no constructor for this. So I can't do something like
final myString = new String("def_value")
So the question arises, how can I go about it in dart?

You can do similar in Dart with just functions:
typedef Factory = dynamic Function(dynamic value);
dynamic create(Map<String, Factory> types, String className, dynamic defaultValue) {
if (types.containsKey(className)) {
return types[className]!(defaultValue);
} else {
throw Exception("no factory for $className");
}
}
final factories = <String, Factory>{
'String': (s) => s.toString(),
'int': (i) => i is int ? i : int.parse('$i'),
'bool': (b) => b is bool ? b : ('$b' == 'true'),
};
show(v) => print('Value $v has type ${v.runtimeType}');
main() {
show(create(factories, 'String', 'foo'));
show(create(factories, 'int', '42'));
show(create(factories, 'bool', 'false'));
}
Prints:
Value foo has type String
Value 42 has type int
Value false has type bool

Related

How to prevent Function cast Exceptions in Dart

I couldn't figure out how to store functions while keeping the storage agnostic about the functions' parameter type. I try to implement a json registry used for encoding/decoding.
Decoding works fine but when it comes to encoding I get this error on runtime
E/flutter ( 4637): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: type '(QuotationRequest) => Map<String, dynamic>' is not a subtype of type '(Object) => Map<String, dynamic>'
E/flutter ( 4637): #0 JsonifyRegistry.encode (package:ridecube/src/commons/io/jsonify.dart:31:21)
...
when I call this:
final registry = JsonifyRegistry()
..register<QuotationRequest>(encoder: (_) => _.toJson(), decoder: (_) => QuotationRequest.fromJson(_));
final QuotationRequest data = QuotationRequest();
registry.encode(data); // <-- issue here
Hence, I'm curious how to do proper casting when it comes to dart function objects if possible.
// jsonify.dart
typedef Json = Map<String, dynamic>;
typedef JsonifyEncoder<T> = Json Function(T object);
typedef JsonifyDecoder<T> = T Function(Json encoded);
class JsonifyRegistryEntry<T> {
JsonifyRegistryEntry({required this.encoder, required this.decoder});
final JsonifyEncoder<T> encoder;
final JsonifyDecoder<T> decoder;
}
class JsonifyRegistry {
final Map<Type, JsonifyRegistryEntry<Object>> _reg = {};
void register<T>({required JsonifyEncoder<T> encoder, required JsonifyDecoder<T> decoder}) =>
registerEntry(JsonifyRegistryEntry<T>(encoder: encoder, decoder: decoder));
void registerEntry<T>(JsonifyRegistryEntry<T> entry) {
_reg[T] = entry as JsonifyRegistryEntry<Object>;
}
T decode<T>(Json encoded) {
assert(_reg.containsKey(T), 'unknown type for encoding/decoding. You need to register $T first!');
return _reg[T]!.decoder(encoded) as T;
}
Json encode<T>(T object) {
assert(_reg.containsKey(T), 'unknown type for encoding/decoding. You need to register $T first!');
return _reg[T]!.encoder(object!); //line 31
}
}
Note
I know why this is prevented of course but I'm still trying to find a solution for my implementation.
simple explanation:
typedef Fu1 = int Function(Object);
typedef Fu2 = int Function(int);
void main() {
final Fu2 fu2 = (x) => x;
final Fu1 fu1 = fu2 as Fu1; // error here Closure 'main_closure': type '(int) => int' is not a subtype of type '(Object) => int'
// if this wasn't the case you would get
// an error as soon as you make a call like
// this: fu1('string');
}
this is one possible solution but I don't like that I had to replace the property encoder by a method.. Maybe you guys can come up with a better advice.
class JsonifyRegistryEntry<T> {
JsonifyRegistryEntry({required JsonifyEncoder<T> encoder, required this.decoder}): _encoder = encoder;
final Function _encoder;
JsonifyEncoder<E> encoder<E>() => _encoder as JsonifyEncoder<E>;
final JsonifyDecoder<T> decoder;
}
// in JsonifyRegistry:
Json encode<T>(T object) {
assert(_reg.containsKey(T), 'unknown type for encoding/decoding. You need to register $T first!');
return _reg[T]!.encoder<T>()(object!);
}

How do i invoke method on a generic type in dart

There are several models have a same structure, { type: xxx, settings: xxx}, so i would like to use a parent class "WidgetConfig" with a generic type "T" to implement this, but problem occurs when i add "fromJson" methods. How can i invoke method on a generic type or any other ways to implement this?
class BannerWidgetViewModel extends ChangeNotifier {
WidgetConfig<BannerWidgetConfig> config;
BannerWidgetViewModel(String configJson){
config = WidgetConfig.fromJson(configJson);
}
}
class BannerWidgetConfig {
String imgUrl;
String padImgUrl;
String lessonId;
BannerWidgetConfig.fromJson(json){
if (json != null) {
this.imgUrl = json['imgUrl'];
this.padImgUrl = json['padImgUrl'];
this.lessonId = json['lessonId'];
}
}
}
class WidgetConfig<T> {
WidgetType type;
WidgetConfig.fromJson(json){
if (json != null) {
this.type = json['type'];
// this.settings = T.fromJson(json['settings']); // T doesn't have fromJson method
}
}
}
then i use a abstract class but still not working.
abstract class BaseWidgetConfig {
BaseWidgetConfig.fromJson(dynamic json);
}
class WidgetConfig<T extends BaseWidgetConfig> {
WidgetType type;
T settings;
WidgetConfig.fromJson(json){
if (json != null) {
this.type = json['type'];
this.settings = T.fromJson();
}
}
}
code picture
Directly show the function as a reference.
send function as a reference here.
FireStoreHelper.getList<ModelLesson>('grade4', ModelLesson.fromJson);
get the method here.
static Future<List<T>> getList<T>(String path, Function fromJson)

differentiate undefined and null in Dart

Consider the following function:
BasicClass copyWith({
String id,
}) {
// some code behaving differently for 1) id is undefined and 2) id is explicit null
}
And consider the two parameters below:
Nothing (id is undefined)
copyWith();
Null (id is null)
copyWith(id: null);
in the copyWith method, is there any way I can make it behave differently for 1) and 2)
There is no way to differentiate null from "no parameter passed".
The only workaround (which is used by Freezed to generate a copyWith that supports null) is to cheat using a custom default value:
final undefined = Object();
class Example {
Example({this.param});
final String param;
Example copyWith({Object param = undefined}) {
return Example(
param: param == undefined ? this.param : param as String,
);
}
}
This requires typing your variables as Object though.
To fix that issue, you can use inheritance to hide the Object under a type-safe interface (again, see Freezed):
final undefined = Object();
class Example {
Example._();
factory Example({String param}) = _Example;
String get param;
void method() {
print('$param');
}
Example copyWith({String param});
}
class _Example extends Example {
_Example({this.param}): super._();
final String param;
#override
Example copyWith({Object param = undefined}) {
return Example(
param: param == undefined ? this.param : param as String,
);
}
}

Default value for parameter of Function type in Dart

Consider a function in Dart file
void myFunction({int input = 1, Function(int, String) callback}) {
// ...
}
So, I wonder is it possible at all to specify a default value for the callback parameter, for instance it can be something like (_, _) => { }.
P.S. I know it has null as default value and ?? can help to avoid NPE, I'm just curious is it possible at all. Cheers.
You can do something like:
dynamic func(int i, String s) {
print(i.toString() + s);
}
void myFunction({int input = 1, Function(int, String) callback = func}) {
callback(input, " .");
}
void main() {
myFunction(input: 2);
}
The default value of an optional parameter must be constant.
This is what the documents said
This thing can be bypassed like this:
dynamic myCallback(int a,String b) {
}
void myFunction({int input = 1, Function(int, String) callback }) {
if (callback == null) callback = myCallback;
}
Edit:
Alternatively, you can use anonymos functaion with out myCallback funcation like this:
void myFunction({int input = 1, Function(int, String) callback }) {
if (callback == null) callback = (a,b){};
}

How to compare the type variable in "is" operator in Dart

I couldn't find a way to store the Type value in Map so that I could use it in is operator to check the validity of type using this map later on. Also, can is operator accept Type as a variable?
For eg, Below is hypothetical code solving the problem but it's invalid.
Map<String, Type> map = {
"sku": String,
"price": double,
"quantity": int,
};
dynamic value = 10;
if(value is map["quantity"]){
print("value is of type int and int is expected for quantity value");
}
You can do something like this:
class TypeCheck<T> {
const TypeCheck();
bool typeCheck(dynamic value) => value is T;
}
void main() {
Map<String, TypeCheck> map = {
"sku": TypeCheck<String>(),
"price": TypeCheck<double>(),
"quantity": TypeCheck<int>(),
};
dynamic value = 10;
if (map["quantity"]!.typeCheck(value)) {
print("value is of type int and int is expected for quantity value");
}
}
Im not sure I fully understand I understand what you are trying to do but why don't you try something like.
bool _validate(Map productDetails){
if (productDetails.containsKey("sold_individually") && productDetails["sold_individually"] is bool) {
//return true or false
}
else if (productDetails.containsKey("stock_quantity") && productDetails["stock_quantity"] is int){
//return true or false
}
else if (productDetails.containsKey("tax_class") && productDetails["tax_class"] is String && productDetails["tax_class"].isNotEmpty) {
//return true or false
} else {
//return true or false
}
}
As for the other part of your question you wont get an error but you will always return false. In contrast if you check if a variable is dynamic it will always return true.
I don't really understand your end goal. But from what you have, I don't think you are taking advantage of the strongly-typed nature of dart.
Assuming you are getting your map from an API, you could enforce
typing manually in your code as follows;
Map<String, Type> map = {
"sku": json['key'] as String,
"price": json['key'] as double,
"quantity": json['key'] as int,
};
And avoid using dynamic when declaring variables.
OR
In the case you have a user-defined type you what to compare, you can use the equatable package on a class for instance as follows;
class CustomMap extends Equatable {
String sky;
double price;
int quantity;
// here you put the fields of a class you want for two instances of a class to be equal.
#overide
List<Object> get props => [sky, price, quantity];
}
Update from your comment
You should have a custom class for the API objects for instance;
class Item extends Equatable {
String sku;
double price;
int quantity;
Item({this.sky, this.price, this.quantity});
// factory constructor
factory Item.fromMap(Map<String, dynmic> json) {
final sku = json['sku'] as String,
final price = (json['price'] as num) as double,
final quantity = json['quantity'] as num,
return Item(sku: sku, price: price, quantity: quantity);
}
// define equatable objects
#override
List<Object> get props => [sku, price, quantity];
}
Now you can use it as follows;
Future<Item> objectsFromService(Map<String, dynamic> json ) async {
http.Response response = http.get(url);
if(response.status == 200) {
final decodedJson = json.decode(response.body);
return Item.fromJson(decodedJson);
}else{
print('Error fetch data');
return null;
}
}
Hope it helps