Interface or instance of a Generic

Asked

Viewed 264 times

2

Interface

I wanted to use interface in Dart, but after a lot of searching, discover that there is not, so I did it this way, I created an abstract class:

abstract class IModel {
  String _uid;

  Map<String, dynamic> toJson();

  IModel.fromJson(Map<String, dynamic> json);

  String get uid => _uid;

  set uid(String value) {
    _uid = value;
  }
}

And I implemented it this way:

import 'package:whatsapp/services/model/imodel.dart';

class UserModel implements IModel {

  @override
  String uid;

  String _name;
  String _email;

  @override
  Map<String, dynamic> toJson() => {"name": _name, "email": _email};

  @override
  UserModel.fromJson(Map<String, dynamic> json) {
    uid = json["uid"];
    _name = json["name"];
    _email = json["email"];
  }

  String get email => _email;

  set email(String value) {
    _email = value;
  }

  String get name => _name;

  set name(String value) {
    _name = value;
  }

}

Generic

So I created the following class to manage Firebase:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:whatsapp/services/model/imodel.dart';

import '../firestore_service.dart';

class BaseService<T extends IModel> extends FirestoreService {
  BaseService(String path) : super(path);

  @override
  Future<List<T>> GetAll() async {
    List<DocumentSnapshot> documentSnapshotList = await GetAllDocuments();
    documentSnapshotList.map((item) => _fromJson(item));
  }

  Future<T> GetById(String id) async {
    return _fromJson(await GetDocumentById(id));
  }

  Future<String> Create(T model) async =>
      (await CreateDocument(model.toJson())).documentID;

  Future<String> CreateOrUpdate(T model) => model.uid == null
      ? Create(model)
      : CreateOrUpdateDocument(model.uid, model.toJson());

  Future<void> Delete(String id) => DeleteDocument(id);

  T _fromJson(DocumentSnapshot snapshot) {
    var newItem = snapshot.data;
    newItem["uid"] = snapshot.documentID;
    return T.fromJson(newItem);
  }
}

At first I wanted to use an interface to solve this problem, instead of "T" I would use the interface, so decide to use Generic, everything worked fine until I need to instantiate "T":

return T.fromJson(newItem);

I have the following mistake:

Compiler message:
lib/services/model/base_service.dart:31:14: Error: The method 'fromJson' isn't defined for the class 'Type'.
 - 'Type' is from 'dart:core'.
Try correcting the name to the name of an existing method, or defining a method named 'fromJson'.
    return T.fromJson(newItem);
             ^^^^^^^^

Summary

I searched and did not find any way to instantiate a Generic or create an interface, my idea is to create a base class to manage Firebase and only pass the class that will be persisted.

Note: The funny thing is that I inform that my "T" is an "Imodel":

BaseService<T extends IModel>

And yet he doesn’t recognize "fromJson" from "Imodel".

  • Flutter does not have Reflection, it seems that even the to implement, but it does not get very cool.

  • I even tried, but it didn’t work.

2 answers

3

I was looking for something like this and I found another way, in this method the instance is created via Funcion and called via callback.

In the tests I noticed that the Intellisense (autocomplete) is impaired in the methods where the generic type is used (T getById(String id) {), but it doesn’t get in the way that much.

typedef S ObjectCreator<S>();
//typedef ObjectCreator<S> = S Function(); 

class BaseService<T extends IModel> {
  BaseService(this.creator);
  final ObjectCreator<T> creator;

  //get _t => creator();

  T getById(String id) {
    final _t = creator();
.......

The instance via Generic looks like this:

BaseService<UserModel>(() => UserModel());

Follow code example or see running on Dartpad:

class FirestoreServiceMock {}

abstract class IModel {
  String _uid;

  Map<String, dynamic> toJson();

  fromJson(Map<String, dynamic> json);

  String get uid => _uid;

  set uid(String value) {
    _uid = value;
  }
}

class UserModel implements IModel {
  @override
  String uid;

  String _name;
  String _email;

  @override
  Map<String, dynamic> toJson() => {"name": _name, "email": _email};

  @override
  fromJson(Map<String, dynamic> json) {
    uid = json["uid"];
    _name = json["name"];
    _email = json["email"];
  }

  String get email => _email;

  set email(String value) {
    _email = value;
  }

  String get name => _name;

  set name(String value) {
    _name = value;
  }

  @override
  String _uid;
}

typedef S ObjectCreator<S>();

class BaseService<T extends IModel> {
  BaseService(this.creator);
  final ObjectCreator<T> creator;

  T getById(String id) {
    final t = creator();

    final fakeData = {
      'uid': '5',
      'name': 'Maria',
      'email': '[email protected]',
    };

    t.fromJson(fakeData);
    return t;
  }
}

void main() {
  var b = BaseService<UserModel>(() => UserModel());
  var user = b.getById('1');
  print('Id: ${user.uid} Usuario: ${user.name} Email: ${user.email}');
  //
}

Based on this answer: https://stackoverflow.com/questions/23112130/creating-an-instance-of-a-generic-type-in-dart

3

Warning

This is not the answer I’m looking for, this is the best solution I’ve found so far, I’m going to post as an answer that might help someone, not this good, but it’s better than nothing.

Partial solution

I do the extends of the "Baseservice" class and step a static method in the constructor, this method creates a new instance of "Usermodel".

import 'package:whatsapp/services/model/user_model.dart';

import 'model/base_service.dart';

class UserService extends BaseService<UserModel> {
  UserService._constructor() : super("usuarios", NewInstance);

  static final UserService _instance = UserService._constructor();

  factory UserService() {
    return _instance;
  }

  static UserModel NewInstance(Map<String, dynamic> json) => UserModel.fromJson(json);

}

And in the "Baseservice" class I call this method as a Function, solving the problem of the new instance:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:whatsapp/services/model/imodel.dart';

import '../firestore_service.dart';

class BaseService<T extends IModel> extends FirestoreService {
  Function(Map<String, dynamic> json) _newInstance;

  BaseService(String path, Function(Map<String, dynamic> json) newInstance)
      : super(path) {
    _newInstance = newInstance;
  }

  @override
  Future<List<T>> GetAll() async {
    List<DocumentSnapshot> documentSnapshotList = await GetAllDocuments();
    documentSnapshotList.map((item) => _fromJson(item));
  }

  Future<T> GetById(String id) async {
    return _fromJson(await GetDocumentById(id));
  }

  Future<String> Create(T model) async =>
      (await CreateDocument(model.toJson())).documentID;

  Future<String> CreateOrUpdate(T model) => model.uid == null
      ? Create(model)
      : CreateOrUpdateDocument(model.uid, model.toJson());

  Future<void> Delete(String id) => DeleteDocument(id);

  T _fromJson(DocumentSnapshot snapshot) {
    var newItem = snapshot.data;
    newItem["uid"] = snapshot.documentID;
    return _newInstance(newItem);
  }
}

Summary

This is not the best solution, it is not as intuitive as an interface or Generic, however it helps not to repeat the same code for each service.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.