Home > Software engineering >  How can we get object index based on object property from a generic function? | Dart
How can we get object index based on object property from a generic function? | Dart

Time:04-17

I've got a nicely setup set of generic functions for my database crud actions. I need a little more fine grained control for a few specialized functions. I want to be able to search through list of database objects by property. Seems impossible, with one caveat- the fact that all objects will have a property of uuid, which is what I want to search by. Sooo... it must be possible with some genius minds from SO.

Of course, I want to do something like this:

Future<int> getExampleIndexByUUID({required String uuid}) async 
  => await Hive.openBox<Example>('Example_Box')
     .then((box) => box.values.toList().indexWhere(example)
       => example.uuid == uuid);

But this above is not possible for generic types:

Future<T> getExampleIndexByUUID<T>({
  required T objectType,
  required String uuid,
  }) async => await Hive.openBox<T>(objectDatabaseNameGetter(objectType))
  .then((box) => box.values.toList().indexWhere(example)
    => example... );                    // Dead end- no property access here

PS I am aware that I can create methods outside the generic function to handle this. I can also create yet another large switch case to handle this, but this is what I want to avoid. I want to learn to abstract my code better in such a scenario. Any help or pointers appreciated! If my only option is to have a switch case or do the work outside the function, so be it.

CodePudding user response:

  • Define a common interface.

    The best approach would be to make all of your classes that have a uuid property share a common base class, and then your generic function could restrict its type parameter to subtypes of that class:

    abstract class HasUuid {
      String get uuid;
    }
    
    class Example implements HasUuid {
      @override
      String uuid;
    
      Example(this.uuid);
    }
    
    Future<int> getExampleIndexByUUID<T extends HasUuid>({
      required T objectType,
      required String uuid,
    }) async {
      var box = await Hive.openBox<T>(objectDatabaseNameGetter(objectType));
      return box.values.toList().indexWhere(
        (example) => example.uuid == uuid),
      );
    }
    
  • Use callbacks.

    If you don't control the classes you want to use, you could have your generic function accept a callback instead to retrieve the desired property. This would be more work for callers, but it also would be more flexible since callers can choose which property to access.

    Future<int> getExampleIndexByUUID<T>({
      required T objectType,
      required String Function(T) getUuid,
      required String uuid,
    }) async {
      var box = await Hive.openBox<T>(objectDatabaseNameGetter(objectType));
      return box.values.toList().indexWhere(
        (example) => getUuid(example) == uuid),
      );
    }
    

    You could generalize that further:

    Future<int> getExampleIndex<T, PropertyType>({
      required T objectType,
      required PropertyType Function(T) getProperty,
      required PropertyType propertyValue,
    }) async {
      var box = await Hive.openBox<T>(objectDatabaseNameGetter(objectType));
      return box.values.toList().indexWhere(
        (example) => getProperty(example) == propertyValue),
      );
    }
    
  • Use duck-typing.

    If you can guarantee that all supplied types have a uuid member, another alternative would be to use dynamic and duck-typing (forgoing static type-safety):

    Future<int> getExampleIndexByUUID<T>({
      required T objectType,
      required String Function(T) getUuid,
      required String uuid,
    }) async {
      var box = await Hive.openBox<T>(objectDatabaseNameGetter(objectType));
      return box.values.toList().indexWhere(
        (dynamic example) => example.uuid == uuid),
      );
    }
    

As an aside, it's bad style to mix async/await with Future.then. Just use async/await.

  • Related