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!);
}
Related
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
When trying to assign a value of a wrong type to a variable in Dart, you get a _TypeError.
Example:
void main() {
dynamic x = 1;
String y = x;
}
Output: type 'int' is not a subtype of type 'String'
What exactly is a _TypeError? I can't find the documentation. I can't catch it (specifically) or expect it in a unit test.
Catching
The following is the only way I could catch it so far, but I don't want to catch 'em all. I want to use on ... catch(e)., but on _TypeError catch(e) doesn't work, because _TypeError is undefined.
void main() {
dynamic x = 1;
try {
String y = x;
} catch (e) {
print('catched: ' + e.runtimeType);
print(e.toString());
}
}
Output:
catched: _TypeError
type 'int' is not a subtype of type 'String'
Testing
How to expect it in a unit test? I expected this to work, but it doesn't:
test('throws a _TypeError', () {
dynamic x = 1;
String x;
expect(() => x = y, throwsException);
};
Output:
Expected: throws <Instance of 'Exception'>
Actual: <Closure: () => dynamic>
Which: threw _TypeError:<type 'int' is not a subtype of type 'String'>
_TypeError is an internal dart class used instead of TypeError, so in most cases, you can just use TypeError instead:
dynamic x = 1;
try {
String y = x;
} on TypeError catch (e) {
print('caught: ' + e.runtimeType);
print(e.toString());
}
Testing
Sadly, I don't know of any way to test for TypeError, I don't believe they made a matcher for it, but I could be wrong, but I guess you could always test before the cast itself
test('throws a _TypeError', () {
dynamic y = 1;
String x;
expect(y, isInstanceOf<String>());
};
if the above test fails, so will x = y;
A value of type 'String?' can't be assigned to a variable of type 'String'.
Try changing the type of the variable, or casting the right-hand type to 'String'.dart(invalid_assignment)
late String manufacturer;
late double fuelCapacity;
late double fuelRemaining;
String showInfo() =>
'$manufacturer: $fuelRemaining of $fuelCapacity (critical: $criticalFuelLevel)';
double get criticalFuelLevel => fuelCapacity * 0.1;
set newFuelRemaining(double val) => fuelRemaining = val;
// default constructor
Vehicle(
{required this.manufacturer,
required this.fuelCapacity,
required this.fuelRemaining});
// named constructor
Vehicle.fromMap(Map<String,String> map) {
this.manufacturer = map["manufacturer"];
this.fuelCapacity = double.parse(["fuelCapacity"]);
this.fuelRemaining = double.parse(['fuelRemaining']);
}
}
void main() {
var vehicle =
Vehicle(manufacturer: 'BMW', fuelCapacity: 55, fuelRemaining: 20);
vehicle.newFuelRemaining = 20;
var vehicle2 = Vehicle.fromMap(
{'manufacturer': 'KIA', 'fuelCapacity': '50', 'fuelRemaining': '20'});
print(vehicle2.showInfo());
}```
Its because map["manufacturer"]; can't be sure manufacturer is for sure inside the map. If its for sure there you can tell Dart that it cant be null like that (a ! at the end):
this.manufacturer = map["manufacturer"]!;
Tell me if you need anything else or if the problem is not there :)
I am modelling a Dart class with the new null safety types in mind. I believe there are two effective ways to initialize non-nullable properties, calculated from a parameter.
For this example, we will use the Favourite class.
This class uses the initializer list in the constructor.
class Favourite {
int favouriteId;
Favourite({required this.favouriteId});
Favourite.mapFromJson(dynamic json)
: this.favouriteId = json["favouriteId"];
}
This class uses the 'late' keyword.
class Favourite {
late int favouriteId;
Favourite({required this.favouriteId});
Favourite.mapFromJson(dynamic json) {
this.favouriteId = json["favouriteId"];
}
}
When would you use one over the other? Using 'late' feels risky. If I added another named constructor, the compiler would not complain about 'favouriteId' not being initialized.
Are there other options?
Thank you!
Neither.
Use a default constructor that initializes the fields themselves and a factory constructor that handles deserializing the json object:
class Favourite {
final int favouriteId;
Favourite({required this.favouriteId});
factory Favourite.fromMap(Map<String, dynamic> map) {
final favouriteId = json['favouriteId'];
assert(favouriteId != null && favouriteId is int);
return Favourite(
favouriteId: favouriteId,
);
}
}
The late keyword can be a source of headache if you don't handle it properly, so in general don't use it unless you have to.
If you're sure the json will always have a "favouriteId", you can write it like this:
class Favourite {
int favouriteId;
Favourite({required this.favouriteId});
Favourite.mapFromJson(Map<String, dynamic?> json):
assert(() {
final favouriteId = json["favouriteId"];
return favouriteId != null && favouriteId is int;
}()),
favouriteId = json["favouriteId"] as int;
}
void main() {
dynamic m = {"favouriteId":2};
final favourite = Favourite.mapFromJson(m);
print("favourite id: ${favourite.favouriteId}");
}
I tried to fetch data from the internet with moviedb API, I followed the tutorial at https://flutter.io/cookbook/networking/fetch-data/
but I'm getting the below error.
Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function 'createDataList':.)
This my code
Future<List<DataModel>> fetchData() async{
final response = await http.get("https://api.themoviedb.org/3/movie/now_playing?api_key=d81172160acd9daaf6e477f2b306e423&language=en-US");
if(response.statusCode == 200){
return compute(createDataList,response.body.toString());
}
}
List<DataModel> createDataList(String responFroJson) {
final parse = json.decode(responFroJson).cast<Map<String, dynamic>>();
return parse.map<DataModel> ((json) => DataModel.fromtJson(json)).toList();
}
Screenshot of the error message
compute can only take a top-level function, but not instance or static methods.
Top-level functions are functions declared not inside a class
and not inside another function
List<DataModel> createDataList(String responFroJson) {
...
}
class SomeClass { ... }
should fix it.
https://docs.flutter.io/flutter/foundation/compute.html
R is the type of the value returned. The callback argument must be a top-level function, not a closure or an instance or static method of a class.
As per today (2020. Aug) the compute is working fine with static methods.
For me, the issue was that I was trying to return a http.Response object from the compute() methods.
What I did is I've created a simplified version of this class, containing what I need:
class SimpleHttpResponse {
String body;
int statusCode;
Map<String, String> headers;
}
Then I've updated the original method from this:
static Future<http.Response> _executePostRequest(EsBridge bridge) async {
return await http.post(Settings.bridgeUrl, body: bridge.toEncryptedMessage());
}
to this:
static Future<SimpleHttpResponse> _executePostRequest(EsBridge bridge) async {
http.Response result = await http.post(Settings.bridgeUrl, body: bridge.toEncryptedMessage());
if (result == null) {
return null;
}
SimpleHttpResponse shr = new SimpleHttpResponse();
shr.body = result.body;
shr.headers = result.headers;
shr.statusCode = result.statusCode;
return shr;
}
Worked like charm after this change. Hope this helps somebody ranning into similar problem.