Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with generic type and abstraction #1296

Open
SexyBeast007 opened this issue Jun 28, 2024 · 0 comments
Open

Issue with generic type and abstraction #1296

SexyBeast007 opened this issue Jun 28, 2024 · 0 comments

Comments

@SexyBeast007
Copy link

I'm trying to abstract my Hive box opening logic in a Flutter app using a custom helper function and an abstract class, but I'm running into issues when passing types. The non-abstracted, verbose code works, but my abstracted approach fails. How can I properly pass types to my abstracted helper function?

In all other places, I have been explicitly calling Hive boxes with their types, such as Hive.openBox(), and this works fine. However, I'm trying to refactor my code to be more dynamic and avoid repetition.

Why am I facing this issue here? I have tried numerous variations of type checking and different approaches, but nothing seems to work. Any guidance on how to achieve this abstraction correctly would be greatly appreciated.

I suspect this is a dart framework issue but im not experienced enough to make that judgement call. So...

I have created a minimum reproducible example with two simple custom classes to feed hive and its causing the exact same issues as my massive project:

abstract class HiveDB {
  static Future<Box<dynamic>> secureEncryptedBox(
    dynamic object, {
    required String boxName,
  }) async {
    const secureStorage = FlutterSecureStorage();
    // if key not exists return null
    final encryptionKeyString = await secureStorage.read(key: 'key');
    if (encryptionKeyString == null) {
      final key = Hive.generateSecureKey();
      await secureStorage.write(
        key: 'key',
        value: base64UrlEncode(key),
      );
    }
    final key = await secureStorage.read(key: 'key');
    final encryptionKeyUint8List = base64Url.decode(key!);
    // Check if the box is already open, and open it with the correct type
    if (Hive.isBoxOpen(boxName)) {
      return TypeFilter.openHiveBoxExplicitly(object, boxName, encryptionKeyUint8List);
    }
    return TypeFilter.openHiveBoxExplicitlyAlternate(object, boxName, encryptionKeyUint8List);
  }
}

void main() async {
  await Hive.initFlutter();
  Hive.registerAdapter(UserAdapter());
  Hive.registerAdapter(ItemAdapter());
  await Hive.openBox('secureStorage'); // This box is used for secure keys
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  Future<void> _saveData() async {
    final user = User(name: 'John Doe', age: 30);
    final item = Item(description: 'Sample Item', price: 19.99);

    Box<dynamic> userBox = await HiveDB.secureEncryptedBox(user, boxName: 'user_box');
    Box<dynamic> itemBox = await HiveDB.secureEncryptedBox(item, boxName: 'item_box');

    userBox.add(user);
    itemBox.add(item);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Hive Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: _saveData,
          child: Text('Save Data'),
        ),
      ),
    );
  }
}

// Abstract class TypeFilter
abstract class TypeFilter {
  static Future<Box<dynamic>> openHiveBoxExplicitly<T>(
    T object,
    String boxName,
    List<int> encryptionKey,
  ) async {
    Box<dynamic> encryptedBox;
    if (object == User) {
      encryptedBox = await Hive.openBox<User>(
        boxName,
        encryptionCipher: HiveAesCipher(encryptionKey),
      );
    } else if (object == Item) {
      encryptedBox = await Hive.openBox<Item>(
        boxName,
        encryptionCipher: HiveAesCipher(encryptionKey),
      );
    } else {
      throw Exception("Unknown object type");
    }
    return encryptedBox;
  }

  static Future<Box<dynamic>> openHiveBoxExplicitlyAlternate(
    Type object,
    String boxName,
    List<int> encryptionKey,
  ) async {
    late Box<dynamic> encryptedBox;
    switch (object) {
      case User:
        encryptedBox = await Hive.openBox<User>(
          boxName,
          encryptionCipher: HiveAesCipher(encryptionKey),
        );
        break;
      case Item:
        encryptedBox = await Hive.openBox<Item>(
          boxName,
          encryptionCipher: HiveAesCipher(encryptionKey),
        );
        break;
      default:
        throw Exception("Unknown object type");
    }
    return encryptedBox;
  }
}

Error:

[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: type 'User' is not a subtype of type 'Type'
#0      HiveDB.secureEncryptedBox
main.dart:30
<asynchronous suspension>
#1      _MyHomePageState._saveData
main.dart:61
<asynchronous suspension>

WORKING NON-ABSTRACTED HIVE DB CODE:

note - in my actual project i have a massive if and switch since I've got over 15 classes Im trying to abstract into this method. Also, for info, in my main project, i need both the if statements with "is" operator as well as the switch with object switching. Why I don't know, thats just what turned out to work. I tried using reflectable with no luck, thought that could solve this. It seems in my main project the object switch works when initializing the app and when saving in a different config the "is" operator works since only when saving or performing actual actions am i passing an actual object. Nonetheless, same issue here so I'm doing something wrong or theres an issue somewhere...

abstract class HiveDB {
  static Future<Box<dynamic>> secureEncryptedBox(
    dynamic object, {
    required String boxName,
  }) async {
    const secureStorage = FlutterSecureStorage();
    // if key not exists return null
    final encryptionKeyString = await secureStorage.read(key: 'key');
    if (encryptionKeyString == null) {
      final key = Hive.generateSecureKey();
      await secureStorage.write(
        key: 'key',
        value: base64UrlEncode(key),
      );
    }
    final key = await secureStorage.read(key: 'key');
    final encryptionKeyUint8List = base64Url.decode(key!);
    late Box<dynamic> encryptedBox;
    // Check if the box is already open, and open it with the correct type
    if (Hive.isBoxOpen(boxName)) {
      if (object is User) {
        encryptedBox = await Hive.openBox<User>(
          boxName,
          encryptionCipher: HiveAesCipher(encryptionKeyUint8List),
        );
      } else if (object is Item) {
        encryptedBox = await Hive.openBox<Item>(
          boxName,
          encryptionCipher: HiveAesCipher(encryptionKeyUint8List),
        );
      }
    } else {
      // Open the box with the correct type if not already open
      switch (object.runtimeType) {
        case User:
          encryptedBox = await Hive.openBox<User>(
            boxName,
            encryptionCipher: HiveAesCipher(encryptionKeyUint8List),
          );
          break;
        case Item:
          encryptedBox = await Hive.openBox<Item>(
            boxName,
            encryptionCipher: HiveAesCipher(encryptionKeyUint8List),
          );
          break;
        default:
          throw Exception("Unknown object type");
      }
    }
    log(encryptedBox.runtimeType.toString());
    return encryptedBox;
  }
}

CLASSES FOR MIN EXAMPLE:

import 'package:hive/hive.dart';

import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

part 'item.g.dart';

@HiveType(typeId: 1)
class Item {
  @HiveField(0)
  final String description;

  @HiveField(1)
  final double price;

  Item({required this.description, required this.price});
}

// user.dart

import 'package:hive/hive.dart';

import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';


part 'user.g.dart';

@HiveType(typeId: 0)
class User {
  @HiveField(0)
  final String name;

  @HiveField(1)
  final int age;

  User({required this.name, required this.age});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant