Flutter create Generic Function with T extending an interface

Viewed 2652

Context

In my code, I have an interface called AbstactDataModel that is used as a starting point for all the data model classes. This is implemented so that i know that whatever xyzModel class I may need, it will have a fromJson(Map<String, dynamic> json) function that will return a new instance of the class built using the given json and a getter for the type.

// In abstract_model.dart
abstract class AbstractDataModel {
  ///
  /// returns a String containing the class name
  /// For example, the class ColumnModel will return 'column'
  ///
  String get type;

  ///
  /// Will call the [.fromJson] constructor and return a new instance of the
  /// object
  ///
  dynamic fromJson(Map<String, dynamic> json);
}

And here is an example of a DataModel class that extends the AbstractDataModel:

// in group_model.dart
class GroupModel extends AbstractDataModel {
  int _id;
  String _name;

  // Constructors
  GroupModel.empty();
  GroupModel.fromJson(Map<String, dynamic> json) {
    _id = parseToInt(json['id'].toString());
    _name = parseToString(json['name'].toString());
  }

  // Getters for private fields
  int get id => _id;
  String get name => _name;
  @override
  String get type => 'group';

  @override
  GroupModel fromJson(Map<String, dynamic> json) => GroupModel.fromJson(json);
}

'Broken' code

I made a function that sends an API request to a server, and then would parse the json response into a DataModel type of object. In order to avoid code duplication and make the source code easier to maintain, i wanted to create a generic function, like so:

// inside the API class (which is a singleton)
Future<T> getObject<T extends AbstractDataModel>({
@required String command,
@required Map<String, dynamic> params,
}) async {
    final Response response = await _sendRequest(
        command: command,
        params: params,
    );
    T object;
    final Map<String, dynamic> body =
        jsonDecode(response.body)['result'] as Map<String, dynamic>;
    if (body == null) {
        print('Request failed.');
        throw const Failure('Failed request to fetch object.');
    } else {
        object = (T as AbstractDataModel).fromJson(body) as T;
        print('Successfully fetched ${object.type}');
        return object;
    }
}

And the way I expected to use this, is as follows:

GroupModel group = await API().getObject<GroupModel>();

Expected behaviour

I expect the getObject function to know that, since T extends AbstractDataModel, then the fromJson(...) function exists, and use it to create a new GroupModel object, all while only accepting objects that extend the AbstractDataModel, i.e. throwing an error for getObject<int>() saying something like 'int does not extend AbstractDataModel`.

Actual behaviour

During execution, I get an unhandled error that says the type _Type is not a subtype of AbstractDataModel and as such, the cast (T as AbstractDataModel) is invalid.

Also, setting a breakpoint and checking some values via the debugger, i tested the following statements:

T is AbstractDataModel: false
T is GroupModel: true
T is AnyOtherDataModelThatExtendsAbstractDataModel: true

So I wouldn't even be able to handle it via a switch statement, since even if i call getObject<GroupModel>(), the statement T is ProjectModel for example, is still evaluated as being true. Any pointers on what i am doing wrong?

0 Answers
Related