9

I'm currently attempting to abstract making different HTTP requests by using generics. I'm using json_serializale for generating fromJson() and toJson() methods.

Here is a simple model file:

import 'package:json_annotation/json_annotation.dart';

part 'article.g.dart';

@JsonSerializable()
class Article {
  int id = 0;
  String title = "";

  Article({this.id, this.title});

  factory Article.fromJson(Map<String, dynamic> json) =>
      _$ArticleFromJson(json);
  Map<String, dynamic> toJson() => _$ArticleToJson(this);
}

I have a generic class that needs to be passed the fromJson-method, see:

typedef CreateModelFromJson = dynamic Function(Map<String, dynamic> json);

class HttpGet<Model> {
  CreateModelFromJson createModelFromJson;

  HttpGet({
    this.createModelFromJson,
  });

  Future<Model> do() async {
    // [... make HTTP request and do stuff ...]
    return createModelFromJson(jsonData);
  }
}

And finally, here is the ArticleService:

class ArticleService {
  Future<Model> read() => HttpGet<Article>({
    createModelFromJson: Article.fromJson,
  }).do();
}

This underlines-in-red fromJson in createModelFromJson: Article.fromJson,, with the error being:

The getter 'fromJson' isn't defined for the class 'Article'. Try importing the library that defines 'fromJson', correcting the name to the name of an existing getter, or defining a getter or field named 'fromJson'.dart(undefined_getter)

Obviously the compiler believes .fromJson to be a static field. However, it's, as can be seen above, a static factory method.

1.) Can you help with how to pass the constructor to the generic class?

ALSO:
2.) I'm not sure whether I'll actually get an Article back, or if I have to type cast it first? I'm kind of worried about the dynamic return type of the typedef.

3.) I'm also open for better ideas for what I'm attempting to do.

Sven
  • 335
  • 4
  • 10

1 Answers1

13

As of Dart 2.15, Dart now supports constructor tear-offs. Named constructors can be used as expected. The default, unnamed constructor for a class named ClassName can be referenced with ClassName.new. (ClassName by itself would be a reference to the corresponding Type object.)


Dart 2.14 and earlier did not allow using constructors as tear-offs. For those versions, I recommend using a static method instead.

Alternatively you could just create an explicit closure:

class ArticleService {
  Future<Model> read() => HttpGet<Article>({
    createModelFromJson: (json) => Article.fromJson(json),
  }).do();
}
jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • 3
    This is absolutely amazing. Man, I was tearing my hair out over this. I went with the static method, like you recommended. However, the explicit closure is a neat trick to have in one's bag of tools. Thank you very much, @jamesdlin! – Sven May 03 '19 at 02:14