skip to Main Content

I have a basic FirebaseManager, currently it parses and returns only a list of User(). How can I make it parse and return any class?

It needs to be something like this:


  final result = await manager.read<User>();
  final result = await manager.read<Address>();
  final result = await manager.read<Contacts>();


Usage of current manager.


Future _firebaseFirestoreTest() async {
  final manager = FirebaseManager(CollectionName.newAction);
  final result = await manager.read();
  print(result);
}


class FirebaseManager implements DataSourceRepository {
  final CollectionReference _collection;

  FirebaseManager(String collectionName)
      : _collection = FirebaseFirestore.instance.collection(collectionName);

  @override
  Future<List<User>> read() async {
    final snapshot = await _collection.get();
    final data = <User>[];

    for (final document in snapshot.docs) {
      final json = document.data() as Map<String, dynamic>;
      final object = User.fromJson(json);
      data.add(object);
    }

    return data;
  }
}


2

Answers


  1. You can achieve this with generics. Check out the documentation here:
    https://dart.dev/language/generics

    For your example, you can include a type T when calling the read function, so it’ll know to return this type (if no type is passed, it will just use and return dynamic, which would fail in the fromJson I guess).

    Future<List<T>> read<T>() async {
      ...
      final data = <T>[];
      ...
        final object = T.fromJson(json);
      ...
      return data;
    }
    

    Which you would indeed call like this:

    final result = await manager.read<User>(); // type of result becomes List<User>
    

    Update

    My first thought given your update: since isJson isn’t defined for a generic type T, perhaps you can define a shared supertype for all of your serializable classes? The problem is that fromJson is a static method, and these are not inherited by subclasses…

    So I’m not sure how I’d get this to work. Let’s hope someone more knowledgeable on the topic comes around and can help us to figure this out.

    Update #2

    Looking at this other SO post, it’s not possible to call a static method on a generic type in Dart: Calling a static method from a generic constraint Dart

    Login or Signup to reply.
  2. Sadly, it’s not possible to do this in Dart (or at least in Flutter) in a generic way. Static members of a class are not part of the type parameter available to generic functions, so there’s no way of calling a factory constructor or static method on a generic type. The most straightforward way of achieving this functionality is to pass a parsing callback to the function:

    Future<List<T>> read<T>(T Function(Map<String, dynamic>) parser) async {
      final snapshot = await _collection.get();
      final data = <User>[];
    
      for (final document in snapshot.docs) {
        final json = document.data() as Map<String, dynamic>;
        final object = parser(json);
        data.add(object);
      }
    
      return data;
    }
    
    final result = await manager.read<User>(User.fromJson);
    final result = await manager.read<Address>(Address.fromJson);
    final result = await manager.read<Contacts>(Contacts.fromJson);
    

    There is a slightly more convenient way, although it requires some setup and can’t be used dynamically. Instead of passing the parsing methods to the read function, keep a lookup table of types with known parsing functions. You can then use them in the read function directly using the generic type as the key:

    typedef T _Parser<T>(Map<String, dynamic> map);
    final Map<Type, _Parser> _parsers = {
      User: User.fromJson,
      Address: Address.fromJson,
      Contacts: Contacts.fromJson,
    };
    
    class FirebaseManager implements DataSourceRepository {
      // ...
      
      @override
      Future<List<T>> read<T>() async {
        final parser = _parsers[T];
        if (parser == null) {
          throw ArgumentError('Unsupported parsable type $T');
        }
        
        final snapshot = await _collection.get();
        final data = <T>[];
    
        for (final document in snapshot.docs) {
          final json = document.data() as Map<String, dynamic>;
          final object = parser(json) as T;
          data.add(object);
        }
    
        return data;
      }
    }
    
    final result = await manager.read<User>();
    final result = await manager.read<Address>();
    final result = await manager.read<Contacts>();
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search