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