Line data Source code
1 : part of flutter_data; 2 : 3 : /// Hive implementation of [LocalAdapter] and Hive's [TypeAdapter]. 4 : // ignore: must_be_immutable 5 : abstract class HiveLocalAdapter<T extends DataModel<T>> extends LocalAdapter<T> 6 : with TypeAdapter<T> { 7 11 : HiveLocalAdapter(Reader read) 8 22 : : _hiveLocalStorage = read(hiveLocalStorageProvider), 9 11 : super(read); 10 : 11 : final HiveLocalStorage _hiveLocalStorage; 12 : 13 : final _hiveAdapterNs = '_adapter_hive'; 14 33 : String get _hiveAdapterKey => 'key'.namespaceWith(_hiveAdapterNs); 15 : 16 11 : @protected 17 : @visibleForTesting 18 33 : Box<T>? get box => (_box?.isOpen ?? false) ? _box : null; 19 : Box<T>? _box; 20 : 21 : @override 22 11 : Future<HiveLocalAdapter<T>> initialize() async { 23 11 : if (isInitialized) return this; 24 22 : final hive = _hiveLocalStorage.hive; 25 : 26 22 : if (!hive.isBoxOpen(internalType)) { 27 22 : if (!hive.isAdapterRegistered(typeId)) { 28 0 : hive.registerAdapter(this); 29 : } 30 : } 31 : 32 : try { 33 22 : if (_hiveLocalStorage.clear) { 34 0 : await _hiveLocalStorage.deleteBox(internalType); 35 : } 36 55 : _box = await _hiveLocalStorage.openBox<T>(internalType); 37 : } catch (e, stackTrace) { 38 0 : print('[flutter_data] Box failed to open:\n$stackTrace'); 39 : } 40 : 41 : return this; 42 : } 43 : 44 11 : @override 45 11 : bool get isInitialized => box != null; 46 : 47 10 : @override 48 : void dispose() { 49 20 : box?.close(); 50 : } 51 : 52 : // protected API 53 : 54 5 : @override 55 : List<T>? findAll() { 56 5 : if (_isLocalStorageTouched) { 57 15 : return box?.values.toImmutableList(); 58 : } 59 : return null; 60 : } 61 : 62 11 : @override 63 : T? findOne(String? key) { 64 : if (key == null) return null; 65 22 : return box?.get(key); 66 : } 67 : 68 : @override 69 11 : Future<T> save(String key, T model, {bool notify = true}) async { 70 11 : if (box == null) return model; 71 : 72 11 : _touchLocalStorage(); 73 : 74 22 : final keyExisted = box!.containsKey(key); 75 22 : final save = box!.put(key, model); 76 : if (notify) { 77 22 : graph._notify( 78 11 : [key], 79 : type: keyExisted 80 : ? DataGraphEventType.updateNode 81 : : DataGraphEventType.addNode, 82 : ); 83 : } 84 : 85 11 : await save; 86 : return model; 87 : } 88 : 89 : @override 90 5 : Future<void> delete(String key, {bool notify = true}) async { 91 5 : if (box == null) return; 92 10 : final delete = box!.delete(key); // delete in bg 93 : // id will become orphan & purged 94 10 : graph.removeKey(key); 95 5 : await delete; 96 : } 97 : 98 : @override 99 2 : Future<void> clear() async { 100 6 : await box?.clear(); 101 : } 102 : 103 : // Touching local storage means the box has received data; 104 : // this is used to know whether `findAll` should return 105 : // null, or its models (possibly empty) 106 : 107 : // _boxMetadata: { 108 : // '_boxMetadata:touched': ['_'], 109 : // } 110 : 111 5 : @override 112 : bool get _isLocalStorageTouched { 113 10 : return graph._hasEdge('_boxMetadata', metadata: '_boxMetadata:touched'); 114 : } 115 : 116 11 : @override 117 : void _touchLocalStorage() { 118 22 : graph._addEdge('_boxMetadata', '_', 119 : metadata: '_boxMetadata:touched', addNode: true, notify: false); 120 : } 121 : 122 : // hive adapter 123 : 124 11 : @override 125 : int get typeId { 126 : // _adapter_hive:key: { 127 : // '_adapter_hive:posts': ['_adapter_hive:1'], 128 : // '_adapter_hive:comments': ['_adapter_hive:2'], 129 : // '_adapter_hive:houses': ['_adapter_hive:3'], 130 : // } 131 : 132 : final typesNode = 133 33 : graph._getNode(_hiveAdapterKey, orAdd: true, notify: false)!; 134 : 135 44 : final edge = typesNode[internalType.namespaceWith(_hiveAdapterNs)]; 136 : 137 2 : if (edge != null && edge.isNotEmpty) { 138 : // first is of format: _adapter_hive:1 139 6 : return int.parse(edge.first.denamespace()); 140 : } 141 : 142 : // get namespaced indices 143 11 : final index = typesNode.values 144 : // denamespace and parse single 145 55 : .map((e) => int.parse(e.first.denamespace())) 146 : // find max 147 22 : .fold(0, math.max) + 148 : 1; 149 : 150 22 : graph._addEdge( 151 44 : _hiveAdapterKey, index.toString().namespaceWith(_hiveAdapterNs), 152 33 : metadata: internalType.namespaceWith(_hiveAdapterNs), notify: false); 153 : return index; 154 : } 155 : 156 0 : @override 157 : T read(reader) { 158 : // read attributes (no relationships stored) 159 0 : final total = reader.readByte(); 160 0 : final map = <String, dynamic>{ 161 0 : for (var i = 0; i < total; i++) reader.read().toString(): reader.read(), 162 : }; 163 : 164 0 : final model = deserialize(map); 165 : 166 : // deserialize (local or remote) should not auto-init 167 : // as there are cases that should not be init, but 168 : // hive local deserialization should always be init: 169 0 : initModel(model); 170 : 171 : return model; 172 : } 173 : 174 0 : @override 175 : void write(writer, T obj) { 176 0 : final map = serialize(obj, withRelationships: false); 177 : 178 0 : final keys = map.keys; 179 0 : writer.writeByte(keys.length); 180 0 : for (final k in keys) { 181 0 : writer.write(k); 182 0 : writer.write(map[k]); 183 : } 184 : } 185 : }