Dmitri Borohhov
Dmitri Borohhov

Reputation: 1603

Dart generic function for map to object conversion

Is there a way to create a generic parseFromMap() function that will output a correct object type?

I have multiple data classes that I deserialise from Firebase DocumentSnapshot. I'm trying to create an abstract class that the data classes will inherit or implement. All classes should implement a parseFromMap() function that will convert the Json into an object. Here's my attempt at the abstract class:

abstract class FirebaseConverter<T> {

  FirebaseConverter(Map<String, dynamic> snapshot, String id);

  Map toJson();

  T parseFromMap(Map<String, dynamic> snapshot, String id);
}

Next, I want to create a generic function that will parse a DocumentSnapshot into the final object type.

  Future<T> getDocumentById(String id) async {
    DocumentSnapshot snap = firebaseRef.doc(id).get();
    return T.parseFromMap(snap.data(), snap.id);
  }

Obviously, this is failing because parseFromMap(snap, snap.id) is not static. Abstract classes cannot have static functions.

What I've done so far is that I have a specific Future<SomeSpecificFirebaseObject> getDocumentById() in each of the specific classes, but this is a lot of code duplication for a basic necessity.

Upvotes: 0

Views: 1562

Answers (1)

Arvind
Arvind

Reputation: 724

The current getDocumentById(...) implementation will fail, because you cannot use a Generic Type as an Instance Type. Generics are not Objects.

Here's one good method I'm referring upon: https://stackoverflow.com/a/28556110/6735934

Let's use the ItemCreator custom function from there.

tl;dr: You basically create an empty instance of the object where you want your data to be filled on, and return it to the ItemCreator function.


Let's begin

Consider a class FirebaseSerializer with a similar method in the question

typedef S ItemCreator<S>();

class FirebaseSerializer {
  static T serializeThis<T extends FirebaseConverter>(ItemCreator<T> itemCreator, String id) {
    // (1) Casting your specific object to its base class
    FirebaseConverter item = itemCreator();
    // Your Firebase Specfics
    DocumentSnapshot snap = firebaseRef.doc(id).get();
    // Calling your base class' method, now possible
    return item.parseFromMap(snap, id);
  }
}

This method accepts an ItemCreator of Type "T" which extends FirebaseConverter, which would return an instance of T.

Why extend? For the cast to FirebaseConverter to work (refer (1) in above snippet)

Now, for an example:

class A implements FirebaseConverter {
  @override
  parseFromMap(Map<String, dynamic> snapshot, String id) {
    // TODO ...
  }

  @override
  Map toJson() {
    // TODO ...
  }
}

FirebaseSerializer in action!

A anInstanceOfA = FirebaseSerializer.serializeThis<A>(() => new A(), "your snap ID");

Upvotes: 2

Related Questions